Éléments fondamentaux de NgRx : magasin, actions, réducteurs, sélecteurs, effets

Le magasin est l’élément clé de tout le processus de gestion de l’état. Il détient l’état et facilite l’interaction entre les composants et l’état. Vous pouvez obtenir une référence au magasin via l’injection de dépendance Angular, comme indiqué ci-dessous.

constructor(private store: Store<AppState>) {}

Cette référence au magasin peut être ensuite utilisée pour deux opérations primaires :

  • Pour dispatcher des actions vers le magasin via la méthode store.dispatch(…), qui déclenchera à son tour des réducteurs et des effets
  • Pour récupérer l’état de l’application via des sélecteurs

Structure d’un arbre d’objets d’état

Supposons que votre application se compose de deux modules de fonctionnalités appelés Utilisateur et Produit. Chacun de ces modules gère différentes parties de l’état global. Les informations sur le produit seront toujours maintenues dans la section products de l’état. Les informations sur l’utilisateur seront toujours maintenues dans la section user de l’état. Ces sections sont également appelées tranches.

Actions

Une action est une instruction que vous expédiez au magasin, éventuellement avec certaines métadonnées (charge utile). En fonction du type d’action, le magasin décide des opérations à exécuter. En code, une action est représentée par un vieil objet JavaScript avec deux attributs principaux, à savoir type et payload. payload est un attribut facultatif qui sera utilisé par les réducteurs pour modifier l’état. L’extrait de code et la figure suivants illustrent ce concept.

{ "type": "Login Action", "payload": { userProfile: user }}

La version 8 de NgRx fournit une fonction utilitaire appelée createAction pour définir des créateurs d’actions (pas des actions, mais des créateurs d’actions). Voici un exemple de code pour cela.

Vous pouvez ensuite utiliser le créateur d’action login (qui est une fonction) pour construire des actions et les dispatcher vers le magasin comme indiqué ci-dessous. user est l’objet payload que vous passez dans l’action.

this.store.dispatch(login({user}));

Réducteurs

Les réducteurs sont chargés de modifier l’état et de retourner un nouvel objet d’état avec les modifications. Les réducteurs prennent en entrée deux paramètres, l’état actuel et l’action. En fonction du type d’action reçu, les réducteurs vont effectuer certaines modifications de l’état actuel et produire un nouvel état. Ce concept est présenté dans le diagramme ci-dessous.

Similaire aux actions, NgRx fournit une fonction utilitaire appelée createReducer pour créer des réducteurs. Un appel de fonction createReducer typique ressemblerait à ce qui suit.

Comme vous pouvez le voir, il prend l’état initial (l’état au démarrage de l’application) et des fonctions de changement d’état un à plusieurs qui définissent comment réagir à différentes actions. Chacune de ces fonctions de changement d’état reçoit l’état actuel et l’action comme paramètres, et renvoie un nouvel état.

Effets

Les effets vous permettent d’effectuer des effets secondaires lorsqu’une action est envoyée au magasin. Essayons de comprendre cela à travers un exemple. Lorsqu’un utilisateur se connecte avec succès à une application, une action avec type Login Action sera envoyée au magasin avec les informations de l’utilisateur dans le payload. Une fonction reducer écoutera cette action et modifiera l’état avec les informations de l’utilisateur. En outre, en tant qu’effet secondaire, vous souhaitez également enregistrer les informations de l’utilisateur dans le stockage local du navigateur. Un effet peut être utilisé pour effectuer cette tâche supplémentaire (effet secondaire).

Il existe plusieurs façons de créer des effets dans NgRx. Ce qui suit est une manière brute et auto-explicative de créer des effets. Veuillez noter que vous n’utilisez généralement pas cette méthode pour créer des effets. Je l’ai seulement prise comme exemple pour expliquer ce qui se passe derrière le rideau.

  • actions$ L’observable émettra des actions reçues par le magasin. Ces valeurs vont passer par une chaîne d’opérateurs.
  • ofType est le premier opérateur utilisé. C’est un opérateur spécial fourni par NgRx (Not RxJS) pour filtrer les actions en fonction de leur type. Dans ce cas, seules les actions de type login seront autorisées à passer par le reste de la chaîne d’opérateurs.
  • tap est le deuxième opérateur utilisé dans la chaîne pour stocker les informations de l’utilisateur dans le stockage local du navigateur. L’opérateur tap est généralement utilisé pour effectuer des effets secondaires dans une chaîne d’opérateurs.
  • Enfin, nous devons nous abonner manuellement à l’observable login$.

Cependant, cette approche présente quelques inconvénients majeurs.

  • Vous devez vous abonner manuellement à l’observable, ce qui n’est pas une bonne pratique. De cette façon, vous devrez toujours vous désabonner manuellement, ce qui entraîne un manque de maintenabilité.
  • Si une erreur surgit dans la chaîne d’opérateurs, l’observable se trompera et cessera d’émettre les valeurs suivantes (actions). Par conséquent, l’effet secondaire ne sera pas exécuté. Par conséquent, vous devez avoir un mécanisme en place pour créer manuellement une nouvelle instance d’observable et vous réabonner si une erreur se produit.

Pour surmonter ces problèmes, NgRx fournit une fonction utilitaire appelée createEffect pour créer des effets. Un appel typique à la fonction createEffect ressemblerait à ce qui suit.

La méthode createEffect prend dans une fonction qui renvoie un observable et (optionnellement) un objet de configuration comme paramètres.

NgRx gère l’abonnement à l’observable renvoyé par la fonction de support, et donc vous n’avez pas à vous abonner ou à vous désabonner manuellement. En outre, si une erreur se produit dans la chaîne d’opérateurs, NgRx créera un nouvel observable et se réabonnera pour s’assurer que l’effet secondaire est toujours exécuté.

Si dispatch est true (valeur par défaut) dans l’objet de configuration, la méthode createEffect renvoie un Observable<Action>. Sinon, elle renvoie un Observable<Unknown>. Si la propriété dispatch est true, NgRx s’abonnera à l’observable retourné de type Observable<Action> et distribuera les actions reçues au magasin.

Si vous ne mappez pas l’action reçue à un type d’action différent dans la chaîne d’opérateurs, vous devrez définir dispatch à false. Sinon, l’exécution se traduira par une boucle infinie, car la même action sera distribuée et reçue dans le flux actions$ encore et encore. Par exemple, vous n’avez pas à définir dispatch à false dans le code ci-dessous parce que vous mappez l’action originale à un type d’action différent dans la chaîne d’opérateurs.

Dans le scénario ci-dessus,

  • L’effet reçoit les actions de type loadAllCourses.
  • Une API est invoquée et des cours sont chargés en tant qu’effet secondaire.
  • La réponse de l’API à une action de type allCoursesLoaded est mappée et les cours chargés sont passés en tant que payload à l’action.
  • Et enfin, l’actionallCoursesLoaded qui a été créée est distribuée au magasin. Ceci est fait par NgRx sous le capot.
  • Un réducteur écoutera l’action allCoursesLoaded entrante et modifiera l’état avec les cours chargés.

Sélecteurs

Les sélecteurs sont des fonctions pures utilisées pour obtenir des tranches de l’état du magasin. Comme indiqué ci-dessous, vous pouvez interroger l’état même sans utiliser de sélecteurs. Mais cette approche s’accompagne à nouveau de quelques inconvénients majeurs.

const isLoggedIn$ = this.store.pipe(map(state => !!state.user));
  • store est un observable auquel vous pouvez vous abonner. Chaque fois que le magasin reçoit une action, store poussera l’objet d’état sur ses abonnés.
  • Vous pouvez utiliser des fonctions de mappage pour obtenir des tranches d’état et effectuer tout calcul si nécessaire. Dans l’exemple ci-dessus, nous obtenons la tranche user dans l’arbre de l’objet d’état et la convertissons en un booléen pour déterminer si l’utilisateur s’est connecté ou non.
  • Vous pouvez soit vous abonner manuellement à l’observable isLoggedIn$, soit l’utiliser dans un template Angular avec un pipe asynchrone pour lire les valeurs émises.

Cependant, cette approche présente un inconvénient majeur. En général, le magasin reçoit fréquemment des actions de différentes parties de l’application. Selon l’implémentation ci-dessus, chaque fois que le magasin reçoit une action, un objet d’état sera émis par le magasin. Et cet objet d’état passera à nouveau par la fonction de mappage et mettra à jour l’IU.

Cependant, si le résultat de la fonction de mappage n’a pas changé par rapport à la dernière fois, il n’est pas nécessaire de mettre à jour l’IU à nouveau. Par exemple, si le résultat de la map(state => !!state.user) n’a pas changé depuis la dernière exécution, nous n’avons pas besoin de pousser à nouveau le résultat sur l’IU/l’abonné. Pour ce faire, NgRx (pas RxJS) a introduit un opérateur spécial appelé select. Avec l’opérateur select, le code ci-dessus changera comme suit.

const isLoggedIn$ = this.store.pipe(select(state => !!state.user));

L’opérateur select empêchera les valeurs d’être poussées vers les IU/abonnés si le résultat de la fonction de mappage n’a pas changé par rapport à la dernière fois.

Cette approche peut encore être améliorée. Même si l’opérateur select ne pousse pas les valeurs inchangées vers l’interface utilisateur/les abonnés, il doit toujours prendre l’objet d’état et faire le calcul pour dériver le résultat à chaque fois.

Comme déjà expliqué ci-dessus, un state sera émis par l’observable lorsque le magasin reçoit une action de l’application. Une action ne met pas toujours à jour l’état. Si l’état n’a pas changé, le résultat du calcul de la fonction de mapping ne changera pas non plus. Par conséquent, nous n’avons pas besoin de refaire le calcul si l’objet state émis n’a pas changé depuis la dernière fois. C’est là que les sélecteurs entrent en jeu.

Un sélecteur est une fonction pure qui maintient une mémoire des exécutions précédentes. Tant que l’entrée n’a pas changé, la sortie ne sera pas recalculée. Au lieu de cela, la sortie sera retournée à partir de la mémoire. Ce processus est appelé mémorisation.

NgRx fournit une fonction utilitaire appelée createSelector pour construire des sélecteurs avec la capacité de mémorisation. Voici un exemple de la fonction utilitairecreateSelector.

La fonction createSelector prend en charge des fonctions de mappage un à plusieurs qui donnent différentes tranches de l’état et une fonction de projecteur qui effectue le calcul. La fonction projecteur ne sera pas invoquée si les tranches d’état n’ont pas changé depuis la dernière exécution. Afin d’utiliser la fonction sélecteur créée, vous devez la passer comme argument à l’opérateur select.

this.isLoggedIn$ = this.store .pipe( select(isLoggedIn) );

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.