Zoeken…


Invoering

Ngrx is een krachtige bibliotheek die u kunt gebruiken met Angular2 . Het idee achter is om twee concepten die goed samen speelt met een reactieve app met een voorspelbare toestand container samen te voegen: - [Redux] [1] - [RxJs] [2] De belangrijkste voordelen: - Het delen van gegevens in uw app tussen uw componenten gaat gemakkelijker - Het testen van de kernlogica van je app bestaat uit het testen van pure functies, zonder enige afhankelijkheid van Angular2 (heel gemakkelijk dus!) [1]: http://redux.js.org [2]: http: // reactctivex. io / rxjs

Volledig voorbeeld: een gebruiker aanmelden / afmelden

voorwaarden

Dit onderwerp gaat niet over Redux en / of Ngrx:

  • Je moet je op je gemak voelen bij Redux
  • Begrijp tenminste de basis van RxJ's en het waarneembare patroon

Laten we eerst een voorbeeld definiëren vanaf het begin en spelen met wat code:

Als ontwikkelaar wil ik:

  1. Heb een IUser interface die de eigenschappen van een User definieert
  2. Verklaar de acties die we later zullen gebruiken om de User in de Store te manipuleren
  3. Definieer de begintoestand van de UserReducer
  4. Maak de reducer UserReducer
  5. Importeer onze UserReducer in onze hoofdmodule om de Store te bouwen
  6. Gebruik gegevens uit de Store om informatie naar onze mening weer te geven

Spoiler-waarschuwing : als u de demo meteen wilt proberen of de code wilt lezen voordat we zelfs maar beginnen, hier is een Plunkr ( embed-weergave of run-weergave ).

1) Definieer IUser interface

Ik deel mijn interfaces graag in twee delen:

  • De eigenschappen die we van een server krijgen
  • De eigenschappen die we alleen voor de gebruikersinterface definiëren (als een knop bijvoorbeeld draait)

En hier is de interface- IUser we zullen gebruiken:

user.interface.ts

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

2) Verklaar de acties om de User te manipuleren

Nu moeten we nadenken over wat voor acties onze reductiemiddelen moeten uitvoeren.
Laten we hier zeggen:

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

Maar voordat we die acties gebruiken, wil ik uitleggen waarom we een service nodig hebben om sommige van die acties voor ons te verzenden:

Laten we zeggen dat we een gebruiker willen verbinden. Dus we klikken op een inlogknop en dit is wat er gaat gebeuren:

  • Klik op de knop
  • De component vangt het evenement op en roept userService.login
  • userService.login methode dispatch een gebeurtenis om onze user.isConnecting bij te werken: user.isConnecting
  • Een HTTP-oproep wordt geactiveerd (we gebruiken een setTimeout in de demo om het asynchrone gedrag te simuleren)
  • Zodra de HTTP oproep is beëindigd, verzenden we een andere actie om onze winkel te waarschuwen dat een gebruiker is aangemeld

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) Definieer de begintoestand van de UserReducer

user.state.ts

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

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

4) Maak het reduceer UserReducer

Een reductor heeft 2 argumenten:

  • De huidige staat
  • Een Action van het type Action<{type: string, payload: any}>

Herinnering: een verloopstuk moet op een bepaald moment worden geïnitialiseerd

Omdat we de standaardstatus van ons verloopstuk in deel 3 hebben gedefinieerd, kunnen we het zo gebruiken:

user.reducer.ts

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

Hopelijk is er een eenvoudigere manier om dat te schrijven door onze factory te gebruiken om een object te retourneren en binnen het verloop een standaardwaarde (ES6) te gebruiken :

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

Vervolgens moeten we alle acties in ons Object.assign uitvoeren: TIP : gebruik de functie ES6 Object.assign om onze status onveranderlijk te houden

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) Importeer onze UserReducer in onze hoofdmodule om de Store te bouwen

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) Gebruik gegevens uit de Store om informatie naar onze mening weer te geven

Alles is nu klaar aan de logische kant en we moeten gewoon weergeven wat we willen in twee componenten:

  • UserComponent : [Dumb component] We geven het gebruikersobject gewoon door vanuit de winkel met de eigenschap @Input en async pipe. Op deze manier ontvangt het onderdeel de gebruiker pas als het beschikbaar is (en de user is van het type IUser en niet van het type Observable<IUser> !)
  • LoginComponent [Smart component] We injecteren de Store rechtstreeks in dit component en werken alleen als user als 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();
  }
}

Als Ngrx is een fusie van Redux en RxJs concepten, kan het heel moeilijk zijn om de ins een outs te begrijpen in het begin. Maar dit is een krachtig patroon waarmee je, zoals we in dit voorbeeld hebben gezien, een reactieve app hebt en waar je eenvoudig je gegevens kunt delen. Vergeet niet dat er een Plunkr beschikbaar is en je kunt deze vork gebruiken om je eigen tests te maken!

Ik hoop dat het nuttig was, ook al is het onderwerp vrij lang, proost!



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow