Sök…


Introduktion

Ngrx är ett kraftfullt bibliotek som du kan använda med Angular2 . Tanken bakom är att slå samman två koncept som spelar bra tillsammans för att ha en reaktiv app med en förutsägbar tillståndsbehållare : - [Redux] [1] - [RxJs] [2] De viktigaste fördelarna: - Dela data i din app mellan dina komponenter kommer att bli enklare - Testa din app-kärnlogik består av att testa rena funktioner, utan beroende av Angular2 (väldigt enkelt så!) [1]: http://redux.js.org [2]: http: // reactivex. io / rxjs

Komplett exempel: Logga in / logga ut en användare

förutsättningar

Detta ämne handlar inte om Redux och / eller Ngrx:

  • Du måste vara bekväm med Redux
  • Åtminstone förstå grunderna i RxJs och observerbara mönster

Låt oss först definiera ett exempel från början och spela med någon kod:

Som utvecklare vill jag:

  1. Har du ett IUser gränssnitt som definierar egenskaperna hos en User
  2. Deklarera de åtgärder som vi kommer att använda senare för att manipulera User i Store
  3. Definiera det initiala läget för UserReducer
  4. Skapa reduceraren UserReducer
  5. Importera vår UserReducer till vår huvudmodul för att bygga Store
  6. Använd data från Store att visa information i vår vy

Spoiler-varning : Om du vill prova demon direkt eller läsa koden innan vi ens börjar, här är en Plunkr ( bädda in vy eller körvyn ).

1) Definiera IUser gränssnitt

Jag vill dela mina gränssnitt i två delar:

  • Egenskaperna får vi från en server
  • Egenskaperna som vi definierar endast för användargränssnittet (om en knapp roterar till exempel)

Och här är det gränssnitt IUser vi kommer att använda:

user.interface.ts

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

2) Förklara åtgärderna för att manipulera User

Nu måste vi tänka på vilken typ av åtgärder våra reducerare är tänkta att hantera.
Låt oss säga här:

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

Men innan vi använder dessa åtgärder, låt mig förklara varför vi kommer att behöva en tjänst för att skicka några av dessa åtgärder för oss:

Låt oss säga att vi vill ansluta en användare. Så vi kommer att klicka på en inloggningsknapp och här är vad som kommer att hända:

  • Klicka på knappen
  • Komponenten fångar händelsen och ringer userService.login
  • userService.login metoden dispatch en händelse för att uppdatera vår user.isConnecting : user.isConnecting
  • Ett HTTP-samtal avfyras (vi använder en setTimeout i demonstrationen för att simulera async-beteendet )
  • När HTTP samtalet är klart skickar vi ytterligare en åtgärd för att varna vår butik om att en användare är inloggad

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) Definiera det initiala tillståndet för UserReducer

user.state.ts

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

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

4) Skapa reduceraren UserReducer

En reducerare tar två argument:

  • Det aktuella tillståndet
  • En Action av typen Action<{type: string, payload: any}>

Påminnelse: En reducerare måste initialiseras vid någon tidpunkt

När vi definierade standardtillståndet för vår reducerare i del 3) kommer vi att kunna använda det så:

user.reducer.ts

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

Förhoppningsvis finns det ett enklare sätt att skriva det genom att använda vår factory att returnera ett objekt och inom reduceraren använda ett (ES6) standardparametervärde :

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

Sedan måste vi hantera alla åtgärder i vår reducerare: TIPS : Använd funktionen ES6 Object.assign att hålla vårt tillstånd oföränderligt

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) Importera vår UserReducer till vår huvudmodul för att bygga 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) Använd data från Store att visa information i vår vy

Allt är nu klart på logisk sida och vi måste bara visa vad vi vill ha i två komponenter:

  • UserComponent : [Dumb component] Vi passerar bara användarobjektet från butiken med @Input egenskap och async rör. På det här sättet kommer komponenten att ta emot användaren endast när den är tillgänglig (och user kommer att vara av typen IUser och inte av typen Observable<IUser> !)
  • LoginComponent [Smart component] Vi injicerar Store direkt i denna komponent och fungerar endast på user som 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();
  }
}

Eftersom Ngrx är en sammanslagning av Redux och RxJs koncept, kan det vara ganska svårt att förstå de ins och outs i början. Men detta är ett kraftfullt mönster som låter dig som vi har sett i det här exemplet ha en reaktiv app och om du enkelt kan dela dina data. Glöm inte att det finns en Plunkr tillgänglig och du kan gaffla den för att göra dina egna tester!

Jag hoppas att det var till hjälp även om ämnet är ganska långt, jubel!



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow