Recherche…


Introduction

Ngrx est une bibliothèque puissante que vous pouvez utiliser avec Angular2 . L'idée est de fusionner deux concepts qui fonctionnent bien ensemble pour avoir une application réactive avec un conteneur d'état prévisible: - [Redux] [1] - [RxJs] [2] Les principaux avantages: - Partager des données dans votre application entre vos composants va plus facile - Tester la logique de votre application consiste à tester des fonctions pures, sans aucune dépendance sur Angular2 (très simple!) [1]: http://redux.js.org [2]: http: // reactivex. io / rxjs

Exemple complet: Connexion / Déconnexion d'un utilisateur

Conditions préalables

Ce sujet ne concerne pas Redux et / ou Ngrx:

  • Vous devez être à l'aise avec Redux
  • Au moins comprendre les bases de RxJs et le motif observable

Tout d'abord, définissons un exemple dès le début et jouons avec du code:

En tant que développeur, je veux:

  1. Avoir une interface IUser qui définit les propriétés d'un User
  2. Déclarez les actions que nous utiliserons plus tard pour manipuler l' User dans le Store
  3. Définir l'état initial du UserReducer
  4. Créer le réducteur UserReducer
  5. Importez notre UserReducer dans notre module principal pour créer le Store
  6. Utilisez les données du Store pour afficher des informations à notre vue

Alerte Spoiler : Si vous voulez essayer la démo immédiatement ou lire le code avant même de commencer, voici une vue Plunkr ( vue d' intégration ou exécution ).

1) Définir IUser Interface

J'aime partager mes interfaces en deux parties:

  • Les propriétés que nous obtiendrons d'un serveur
  • Les propriétés que nous définissons uniquement pour l'interface utilisateur (si un bouton tourne par exemple)

Et voici l'interface IUser nous utiliserons:

user.interface.ts

export interface IUser {
  // from server
  username: string;
  email: string;
  
  // for UI
  isConnecting: boolean;
  isConnected: boolean;
};

2) Déclarez les actions pour manipuler l' User

Maintenant, nous devons réfléchir au type d'actions que nos réducteurs sont censés gérer.
Disons ici:

user.actions.ts

export const UserActions = {
  // when the user clicks on login button, before we launch the HTTP request
  // this will allow us to disable the login button during the request
  USR_IS_CONNECTING: 'USR_IS_CONNECTING',
  // this allows us to save the username and email of the user
  // we assume those data were fetched in the previous request
  USR_IS_CONNECTED: 'USR_IS_CONNECTED',

  // same pattern for disconnecting the user
  USR_IS_DISCONNECTING: 'USR_IS_DISCONNECTING',
  USR_IS_DISCONNECTED: 'USR_IS_DISCONNECTED'
};

Mais avant d'utiliser ces actions, laissez-moi vous expliquer pourquoi nous allons avoir besoin d'un service pour nous envoyer certaines de ces actions:

Disons que nous voulons connecter un utilisateur. Nous allons donc cliquer sur un bouton de connexion et voici ce qui va se passer:

  • Cliquez sur le bouton
  • Le composant intercepte l'événement et appelle userService.login
  • userService.login méthode userService.login dispatch un événement pour mettre à jour notre propriété store: user.isConnecting
  • Un appel HTTP est déclenché (nous utiliserons un setTimeout dans la démo pour simuler le comportement asynchrone )
  • Une fois l'appel HTTP terminé, nous enverrons une autre action pour avertir notre magasin qu'un utilisateur est connecté.

user.service.ts

@Injectable()
export class UserService {
  constructor(public store$: Store<AppState>) { }

  login(username: string) {
    // first, dispatch an action saying that the user's tyring to connect
    // so we can lock the button until the HTTP request finish
    this.store$.dispatch({ type: UserActions.USR_IS_CONNECTING });

    // simulate some delay like we would have with an HTTP request
    // by using a timeout
    setTimeout(() => {
      // some email (or data) that you'd have get as HTTP response
      let email = `${username}@email.com`;

      this.store$.dispatch({ type: UserActions.USR_IS_CONNECTED, payload: { username, email } });
    }, 2000);
  }

  logout() {
    // first, dispatch an action saying that the user's tyring to connect
    // so we can lock the button until the HTTP request finish
    this.store$.dispatch({ type: UserActions.USR_IS_DISCONNECTING });

    // simulate some delay like we would have with an HTTP request
    // by using a timeout
    setTimeout(() => {
      this.store$.dispatch({ type: UserActions.USR_IS_DISCONNECTED });
    }, 2000);
  }
}

3) Définir l’état initial du UserReducer

user.state.ts

export const UserFactory: IUser = () => {
  return {
    // from server
    username: null,
    email: null,

    // for UI
    isConnecting: false,
    isConnected: false,
    isDisconnecting: false
  };
};

4) Créez le réducteur UserReducer

Un réducteur prend 2 arguments:

  • L'état actuel
  • Une Action de type Action<{type: string, payload: any}>

Rappel: un réducteur doit être initialisé à un moment donné

Comme nous avons défini l'état par défaut de notre réducteur dans la partie 3), nous pourrons l'utiliser comme ça:

user.reducer.ts

export const UserReducer: ActionReducer<IUser> = (user: IUser, action: Action) => {
  if (user === null) {
    return userFactory();
  }
  
  // ...
}

Heureusement, il existe un moyen plus simple d’écrire cela en utilisant notre fonction factory pour renvoyer un objet et, dans le réducteur, utilisez une valeur de paramètres par défaut (ES6):

export const UserReducer: ActionReducer<IUser> = (user: IUser = UserFactory(), action: Action) => {
  // ...
}

Ensuite, nous devons gérer toutes les actions de notre réducteur: CONSEIL : utilisez la fonction ES6 Object.assign pour garder notre état immuable

export const UserReducer: ActionReducer<IUser> = (user: IUser = UserFactory(), action: Action) => {
  switch (action.type) {
    case UserActions.USR_IS_CONNECTING:
      return Object.assign({}, user, { isConnecting: true });

    case UserActions.USR_IS_CONNECTED:
      return Object.assign({}, user, { isConnecting: false, isConnected: true, username: action.payload.username });

    case UserActions.USR_IS_DISCONNECTING:
      return Object.assign({}, user, { isDisconnecting: true });

    case UserActions.USR_IS_DISCONNECTED:
      return Object.assign({}, user, { isDisconnecting: false, isConnected: false });

    default:
      return user;
  }
};

5) Importer notre UserReducer dans notre module principal pour construire le Store

app.module.ts

@NgModule({
    declarations: [
    AppComponent
    ],
    imports: [
    // angular modules
    // ...

    // declare your store by providing your reducers
    // (every reducer should return a default state)
    StoreModule.provideStore({
        user: UserReducer,
        // of course, you can put as many reducers here as you want
        // ...
    }),

    // other modules to import
    // ...
    ]
});

6) Utiliser les données de la Store pour afficher des informations à notre avis

Tout est maintenant prêt sur le côté logique et nous devons simplement afficher ce que nous voulons en deux composants:

  • UserComponent : [Composant Dumb] Nous allons simplement passer l'objet utilisateur du magasin en utilisant la propriété @Input et le tube async . De cette façon, le composant ne recevra l'utilisateur qu'une fois qu'il sera disponible (et l' user sera de type IUser et non de type Observable<IUser> !)
  • LoginComponent [Composant intelligent] Nous injecterons directement le Store dans ce composant et travaillerons uniquement sur l' user en tant Observable .

user.component.ts

@Component({
  selector: 'user',
  styles: [
    '.table { max-width: 250px; }',
    '.truthy { color: green; font-weight: bold; }',
    '.falsy { color: red; }'
  ],
  template: `
    <h2>User information :</h2>

    <table class="table">
      <tr>
        <th>Property</th>
        <th>Value</th>
      </tr>

      <tr>
        <td>username</td>
        <td [class.truthy]="user.username" [class.falsy]="!user.username">
          {{ user.username ? user.username : 'null' }}
        </td>
      </tr>

      <tr>
        <td>email</td>
        <td [class.truthy]="user.email" [class.falsy]="!user.email">
          {{ user.email ? user.email : 'null' }}
        </td>
      </tr>

      <tr>
        <td>isConnecting</td>
        <td [class.truthy]="user.isConnecting" [class.falsy]="!user.isConnecting">
          {{ user.isConnecting }}
        </td>
      </tr>

      <tr>
        <td>isConnected</td>
        <td [class.truthy]="user.isConnected" [class.falsy]="!user.isConnected">
          {{ user.isConnected }}
        </td>
      </tr>

      <tr>
        <td>isDisconnecting</td>
        <td [class.truthy]="user.isDisconnecting" [class.falsy]="!user.isDisconnecting">
          {{ user.isDisconnecting }}
        </td>
      </tr>
    </table>
  `
})
export class UserComponent {
  @Input() user;

  constructor() { }
}

login.component.ts

@Component({
  selector: 'login',
  template: `
    <form
      *ngIf="!(user | async).isConnected"
      #loginForm="ngForm"
      (ngSubmit)="login(loginForm.value.username)"
    >
      <input
        type="text"
        name="username"
        placeholder="Username"
        [disabled]="(user | async).isConnecting"
        ngModel
      >
 
      <button
        type="submit"
        [disabled]="(user | async).isConnecting || (user | async).isConnected"
      >Log me in</button>
    </form>
 
    <button
      *ngIf="(user | async).isConnected"
      (click)="logout()"
      [disabled]="(user | async).isDisconnecting"
    >Log me out</button>
  `
})
export class LoginComponent {
  public user: Observable<IUser>;
 
  constructor(public store$: Store<AppState>, private userService: UserService) {
      this.user = store$.select('user');
  }
 
  login(username: string) {
    this.userService.login(username);
  }
 
  logout() {
    this.userService.logout();
  }
}

Comme Ngrx est une fusion des concepts de Redux et de RxJs , il peut être assez difficile de comprendre les détails au début. Mais il s'agit d'un modèle puissant qui vous permet, comme nous l'avons vu dans cet exemple, de disposer d'une application réactive et de partager facilement vos données. N'oubliez pas qu'il y a un Plunkr disponible et que vous pouvez le préparer pour faire vos propres tests!

J'espère que c'était utile même si le sujet est assez long, bravo!



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow