État des lieux des actions EmberJS

Note: cet article est une traduction de mon guest-post publié sur le blog d’EmberSherpa.

Depuis fin 2014, Ember a traversé de nombreuses évolutions, probablement pour le mieux. Bon ou mauvais, cela a pourtant laissé des cicatrices et la façon d’approcher certains de ses aspects est devenue moins claire.

Un des aspects que je trouve particulièrement flou est l’utilisation des actions. Depuis l’ajout des closure actions, il n’est pas toujours facile de savoir quelle est la meilleure façon de les utiliser.

Dans cet article, j’aimerais apporter quelques éclaircissements l’usage des actions dans Ember et ce qu’il est possible de faire avec celles-ci.

Bubbling actions

La façon historique d’utiliser les actions dans Ember est d’appeler le helper action en lui passant le nom d’une action de Route ou de Controller.

<button {{action "someAction"}}>GO!</button>

Mais dès lors, une question se pose : “Où dois-je coder cette action ?”

Et bien… ça dépend. Il n’y a pas de règle établie que l’on peut suivre sans avoir à réfléchir. Vous pouvez cependant vous poser les questions suivantes :

  • Cette action est-elle globale ? (pensez “se déconnecter” par exemple)
  • Un modèle est-il manipulé ? (sauvegarde, etc)
  • L’état de la vue est-il modifié ? (passage du mode affichage au mode édition)
  • Cette action doit-elle est transmise à un composant via une closure action ?

En fonction de vos réponses, la destination de votre code peut être influencée.

De façon générale, lorsqu’une action est globale, je ne réfléchis pas plus que ça, elle va directement dans une Route. Elle n’est pas liée à la page actuellement affichée et a un impact sur toute l’application.

Il en va de même pour les actions qui manipulent un modèle. Puisque ma Route est responsable de récupérer les données, il me semble logique de lui donner la responsabilité de les sauvegarder.

Lorsque c’est uniquement l’état de la vue qui est affecté, par exemple passer d’un mode “édition” à un mode “affichage” ou encore ouvrir / replier un élément, cela consiste généralement en la modification d’une variable dans le Controller. Cela fait donc sens de garder le code de l’action proche des informations modifiées et je la place donc dans le Controller.

C’est au moment de s’attaquer à la dernière question que les complications commencent.

Closure actions

Il est assez facile de passer une action à un Composant. Il suffit d’assigner le nom d’une action à une propriété du Composant.

{{audio-player action="playMusic"}}

On peut ensuite appeler l’action depuis le Composant :

export default Ember.Component.extend({
  click() {
    this.sendAction();
  }
});

La propriété action est celle appelée par défaut par sendAction mais il est possible de préciser explicitement quelle action appeler. Si par exemple on appelle le Composant comme ceci :

{{audio-player play="playMusic" stop="stopMusic"}}

Dans son code il nous est possible d’appeler l’action choisie en passant simplement le nom de la propriété à sendAction :

export default Ember.Component.extend({
  click() {
    if (this.get("isPlaying") {
      this.sendAction("stop");
    }
    else {
      this.sendAction("play");
    }
  }
});

Il y a cependant plusieurs soucis avec cette approche :

  • l’action ne peut être passée à un sous-Composant ;
  • impossible d’obtenir une valeur de retour de l’action ;
  • l’action doit impérativement être dans le Controller.

Mettons de côté la dernière contrainte pour le moment et concentrons-nous sur les deux premières.

Ne pas pouvoir passer directement l’action aux sous-Composants n’a rien d’amusant et force à définir une nouvelle action dans chaque Composant afin de faire remonter un événement à la main.

Ne pas avoir accès à la valeur retournée par l’action peut également être contraignant dans certains cas.

Le helper action peut nous permettre de contourner ces deux problèmes assez facilement. Lorsqu’on l’utilise pour assigner une action à une propriété du Composant, cela crée une closure action qui peut être transmise de Composant en Compposant.

{{audio-player play=(action "playMusic")}}

On peut ensuite l’utiliser dans le template du Composant.

<button {{action play}}>Play</button>

De cette façon, l’action peut être passée autant de fois qu’on le souhaite sans avoir à toucher au code des Composants intermédiaires.

Un autre avantage des closure actions est qu’il est possible d’obtenir la valeur qu’elles retournent depuis le Composant appelant ; cela peut être très pratique lors de l’appel d’actions déclenchant un appel asynchrone et retournant une Promise.

Par exemple, un Composant qui affiche une icône de chargement pendant qu’un Modèle est sauvegardé peut attendre la résolution de la Promise retournée par l’action pour masquer cette icône. Vous pouvez faire preuve de créativité.

export default Ember.Component.extend({
  showSpinner: false,

  actions: {
    save(article) {
      this.set("showSpinner", true);

      this.get("saveArticle")(article).then(() => {
        this.set("showSpinner", false);
      });
    }
  }
});

Uniquement dans le Controller ?

En ce qui concerne la dernière contrainte vue précédemment, “l’action doit impérativement être dans le Controller”, il existe une solution.

L’addon ember-route-action-helper, proposé par la super équipe de DockYard, expose un helper route-action permettant d’écrire les actions dans les Routes tout en permettant de les passer aux Composants sous forme de closure actions.

{{audio-player play=(route-action "playMusic")}}

Un peu de curry ?

Une autre utilisation, moins connue, des closure actions d’Ember est la possiblité de curryfication, c’est-à-dire passer des arguments au futur appel de l’action.

Définissons une simple action qui va afficher ses arguments en console :

export default Ember.Controller.extend({
  actions: {
    logArgs() {
      console.log("arguments", ...arguments);
    }
  }
});

On peut ensuite passer l’action tout en lui ajoutant de futurs arguments :

{{audio-player play=(action "logArgs" "in the view")}}

Puis, dans le Composant, on peut ajouter des arguments supplémentaires :

{{cool-button action=(action play "in the player")}}

Enfin, on peut passer le dernier argument depuis le sous-Composant cool-button :

export default Ember.Component.extend({
  click() {
    this.attrs.action("in the button");
  }
});

Après un clic sur le bouton, voici ce que l’on voit dans la console du navigateur :

arguments in the view in the player in the button

Sympa, non ?

En conclusion

Comme on a pu le voir, il existe plusieurs façons d’atteindre des résultats similaires avec pour chacune des pours et des contres en fonction de ce que l’on cherche à faire.

Gardez les choses simples autant que possible. Très souvent les bubbling actions suffisent mais dès qu’il faut un peu de puissance, les closure actions peuvent être de précieuses alliées.

Publié le 11 septembre 2016

Notre vision des choses vous correspond ? Vous avez envie de travailler avec nous ?