Ricerca…


introduzione

Ngrx è una potente libreria che puoi usare con Angular2 . L'idea alla base è quella di unire due concetti che giocano bene insieme per avere un'app reattiva con un contenitore di stato prevedibile: - [Redux] [1] - [RxJs] [2] I principali vantaggi: - Condivisione dei dati nella tua app tra i tuoi componenti sta andando più facile - Testare la tua logica di base dell'app consiste nel testare pure funzioni, senza alcuna dipendenza da Angular2 (molto semplice!) [1]: http://redux.js.org [2]: http: // reactivex. io / rxjs

Esempio completo: Login / logout di un utente

Prerequisiti

Questo argomento non riguarda Redux e / o Ngrx:

  • Devi stare tranquillo con Redux
  • Almeno capire le basi di RxJs e modello osservabile

Per prima cosa, definiamo un esempio fin dall'inizio e giochiamo con un codice:

Come sviluppatore, voglio:

  1. Avere un'interfaccia IUser che definisce le proprietà di un User
  2. Dichiarare le azioni che useremo in seguito per manipolare l' User nello Store
  3. Definire lo stato iniziale di UserReducer
  4. Crea il riduttore UserReducer
  5. Importa il nostro UserReducer nel nostro modulo principale per costruire lo Store
  6. Utilizza i dati dallo Store per visualizzare le informazioni nella nostra vista

Avviso spoiler : se vuoi provare subito la demo o leggere il codice prima ancora di iniziare, ecco un Plunkr ( visualizzazione incorporata o visualizzazione di esecuzione ).

1) Definire IUser interfaccia IUser

Mi piace dividere le mie interfacce in due parti:

  • Le proprietà che otterremo da un server
  • Le proprietà che definiamo solo per l'interfaccia utente (se un pulsante deve ruotare ad esempio)

Ed ecco l'interfaccia IUser che IUser :

user.interface.ts

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

2) Dichiarare le azioni per manipolare l' User

Ora dobbiamo pensare a quale tipo di azioni devono essere gestite dai nostri riduttori .
Diciamo qui:

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'
};

Ma prima di utilizzare tali azioni, lasciami spiegare perché avremo bisogno di un servizio per inviare alcune di queste azioni per noi:

Diciamo che vogliamo connettere un utente. Quindi faremo clic su un pulsante di accesso ed ecco cosa succederà:

  • Clicca sul pulsante
  • Il componente intercetta l'evento e chiama userService.login
  • userService.login metodo userService.login dispatch un evento per aggiornare la nostra proprietà del negozio: user.isConnecting
  • Viene lanciata una chiamata HTTP (useremo un setTimeout nella demo per simulare il comportamento asincrono )
  • Una volta terminata la chiamata HTTP , invieremo un'altra azione per avvisare il nostro archivio che un utente è registrato

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) Definire lo stato iniziale di UserReducer

user.state.ts

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

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

4) Crea il riduttore UserReducer

Un riduttore accetta 2 argomenti:

  • Lo stato attuale
  • Action di tipo Action<{type: string, payload: any}>

Promemoria: un riduttore deve essere inizializzato ad un certo punto

Come abbiamo definito lo stato predefinito del nostro riduttore nella parte 3, saremo in grado di usarlo in questo modo:

user.reducer.ts

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

Speriamo che ci sia un modo più semplice per scrivere che usando la nostra funzione di factory per restituire un oggetto e all'interno del riduttore usare un valore di parametri di default (ES6):

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

Quindi, dobbiamo gestire tutte le azioni del nostro riduttore: SUGGERIMENTO : utilizzare la funzione Object.assign ES6 per mantenere il nostro stato immutabile

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) Importa il nostro UserReducer nel nostro modulo principale per costruire lo 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) Utilizzare i dati dallo Store per visualizzare le informazioni nella nostra vista

Tutto è ora pronto per il lato logico e dobbiamo solo mostrare ciò che vogliamo in due componenti:

  • UserComponent : [Componente stupido] UserComponent semplicemente l'oggetto utente dallo store usando la proprietà @Input e la pipe async . In questo modo, il componente riceverà l'utente solo una volta disponibile (e l' user sarà di tipo IUser e non di tipo Observable<IUser> !)
  • LoginComponent [Componente intelligente] LoginComponent direttamente lo Store in questo componente e lavoreremo solo user come 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();
  }
}

Dato che Ngrx è un'unione dei concetti di Redux e RxJs , può essere piuttosto difficile capire all'inizio un outs. Ma questo è uno schema potente che ti consente, come abbiamo visto in questo esempio, di avere un'app reattiva ed è possibile condividere facilmente i tuoi dati. Non dimenticare che c'è un Plunkr disponibile e puoi forarlo per fare i tuoi test!

Spero sia stato utile anche se l'argomento è abbastanza lungo, evviva!



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow