7 erreurs JavaScript fréquentes chez les débutants Angular

L’un des prérequis de la formation Angular que nous proposons est de connaître JavaScript. (Techniquement, nous utilisons TypeScript, mais TypeScript reprend toutes les syntaxes de JavaScript.) Pourtant, il est fréquent que les participants buttent sur certains points qui sont propres à JavaScript.

Erreur 1 - Ne pas savoir quand utiliser var, let ou const

On peut commencer par évacuer var, qui ne devrait plus être utilisé, car ses règles de scoping sont sources d’erreur par rapport au fonctionnement de let et const.

Les variables déclarées avec let et const sont scopées à leur bloc de déclaration, c’est-à-dire qu’elles n’existent que dans ce bloc.

const permet de déclarer une variable qui doit avoir une valeur initiale et ne peut pas être réassignée :

const a = 'foo';
a = 'bar';  // Erreur, car `a` ne peut pas être réassignée

ATTENTION. Cela ne veut pas dire qu’une variable déclarée avec const est une constante. Par exemple, si la variable contient un objet, on peut modifier les propriétés de cet objet.

let permet de déclarer une variable qui peut ne pas avoir de valeur initiale et qui peut être réassignée :

let html;                          // `html` est undefined
html = '<p>';                      // OK, `html` peut être réassignée
html = html + message + '</p>';    // OK, `html` peut être réassignée

Pour approfondir ce point, consultez la recette #49 Quand utiliser var, let ou const ?.

Erreur 2 - Ne pas savoir manipuler les tableaux

Dans Angular, les tableaux sont partout. La plupart des collections de données qu’on manipule se présentent sous forme de tableaux. Il est donc indispensable de savoir créer un tableau, ajouter ou retirer des valeurs dans un tableau, etc.

Voici quelques exemples :

Ajouter un élément à la fin d’un tableau - Array.push() :

const contacts = ['Pierre', 'Paul', 'Joe'];
contacts.push('Marie');                        // Modifie le tableau original
const newContacts = contacts.concat('Marie');  // Renvoie un nouveau tableau

Retirer un élément d’un tableau - Array.splice() :

const contacts = ['Pierre', 'Paul', 'Joe'];
contacts.splice(2, 1);  // Retire "Joe" (1 élément à la position 2)

Pour approfondir ce point, consultez la recette #51 Syntaxes fréquentes pour manipuler un tableau (Array).

Erreur 3 - Ne pas savoir manipuler les objets

Object.assign() ?

Pour approfondir ce point, consultez la recette #52 Syntaxes fréquentes pour manipuler un objet.

Erreur 4 - Croire que les noms d’argument d’une fonction de callback sont “magiques”

const items = ['Fraise', 'Pomme', 'Banane'];

// CORRECT
items.forEach(function(item, index) {
  console.log(item);
});
// CORRECT AUSSI
items.forEach(function(toto, titi) {
  console.log(toto);
});

Dans une fonction de callback, ou plus précisément une expression de fonction, c’est la position des arguments qui est importante, pas leur nom.

Dans l’exemple ci-dessus, la documentation de Array.prototype.forEach() nous dit que la fonction qu’on lui passe en paramètre recevra la valeur de l’itération en cours en 1er argument, et l’indice de l’élément en cours en 2ème argument. Peu importe le nom de ces arguments.

Ça paraît souvent évident dans un exemple simple comme ci-dessus, mais cela devient moins clair quand on passe à du code plus complexe, comme les opérateurs RxJS qui permettent de transformer un observable :

fetchIdsFromBackend()
  .mergeMap(id => fetchEntity(id))         // fonction
  .map(entity => entity.title)             // fonction
  .map(title => `Le titre est ${title}.`)  // fonction
  .subscribe();

Ici, chaque opérateur reçoit une expression de fonction, même si la syntaxe des fonctions fléchées fait parfois oublier qu’on a affaire à des fonctions. Par conséquent, j’aurais aussi bien pu appeler les arguments de ces fonctions autrement que id, entity et title (j’aurais pu les appeler pif, paf et pouf…). En général, on choisit des noms parlants qui améliorent la compréhension du code.

Erreur 5 - Confondre le fait de référencer et d’invoquer une fonction

Une erreur fréquente est de ne pas faire la différence entre passer le nom d’une fonction en argument et appeler une fonction.

function hey() {
  alert('coucou');
}

// CORRECT (référence)
setTimeout(hey, 500);

// INCORRECT (invocation)
setTimeout(hey(), 500);

Dans le premier cas, on ne sait pas quand la fonction sera appelée. Dans le 2e cas, la fonction est appelée au moment où le code est exécuté.

Bien-sûr, dans le 2e cas, il se pourrait qu’on veuille appeler tout de suite une fonction dont le rôle est de renvoyer une fonction, mais c’est un usage avancé et ce n’est pas ce qu’on veut quand mes stagiaires font l’erreur.

On trouve un exemple dans l’article http://www.datchley.name/promise-patterns-anti-patterns/ ; il appelle les fonctions sans parenthèses des “factories”. Dans la doc Angular, le nom d’une classe est appelé un “symbole” (source).

Erreur 6 - Ne pas connaître la différence entre passage par valeur et passage par référence

En JavaScript, les valeurs primitives sont passées PAR VALEUR. Ainsi, si une fonction change une valeur primitive qu’elle a reçue, le changement n’est pas reflété en dehors de la fonction :

function double(number) {
  number = number * 2;
}

En revanche, les objets sont passés PAR RÉFÉRENCE. Si une fonction change un objet qu’elle a reçu, le changement est reflété en dehors de la fonction :

function myFunc(theObject) {
  theObject.make = 'Toyota';
}

var mycar = {make: 'Honda', model: 'Accord', year: 1998};

var x = mycar.make; // x reçoit la valeur "Honda"
myFunc(mycar);
var y = mycar.make; // y reçoit la valeur "Toyota"

Erreur 7 - Ne pas comprendre this et la notion de scope

Une erreur courante lorsqu’on débute en JavaScript est de mal comprendre le fonctionnement de this.

Erreur 7A - Perdre la valeur de this dans une méthode de classe

Typiquement, cette erreur survient quand on veut passer une méthode de classe à l’emplacement d’une expression de fonction.

On le voit dans cet exemple avec mergeMap(EXPRESSION_FONCTION) — NB. VÉRIFIER L’EXEMPLE :

@Component({...})
export class AppComponent {
  constructor(private http: Http) {}

  ngOnInit() {
    this.loadUserIds()
      .switchMap(this.loadUser)  // Ce `this` pose problème
      .subscribe(users => ...);
  }

  loadUserIds() {
    return this.http.get('https://api.monsite.com/userids');
  }

  loadUser(userId: string) {
    return this.http.get('https://api.monsite.com/user/' + userId);
  }
}

La ligne .switchMap(this.loadUser) va planter car la méthode loadUser sera appelée en dehors du contexte de l’instance de classe, et le this qui se trouve à l’intérieur n’aura plus la bonne valeur (this.http.get(...)).

La solution est de binder explicitement la méthode au bon this — avec bind — avant de la passer en callback.

this.loadUserIds()
  .switchMap(this.loadUser.bind(this))  // La méthode est correctement bindée
  .subscribe(users => ...);

Erreur 7B - Perdre la valeur de this dans une expression de fonction

Pour éviter cette erreur, il faut remplacer l’expression de fonction par une fonction fléchée.

Voir #79 Pourquoi this fonctionne correctement dans une fonction fléchée ?.

Informations

Tags : javascripterrors

Dernière mise à jour :

Auteur : AngularChef

Qualité : Pas finalisé