Szukaj…


Wprowadzenie

Ngrx to potężna biblioteka, której można używać z Angular2 . Ideą jest połączenie dwóch koncepcji, które dobrze ze sobą współgrają, aby mieć reaktywną aplikację z przewidywalnym pojemnikiem stanu : - [Redux] [1] - [RxJs] [2] Główne zalety: - Udostępnianie danych w aplikacji między komponentami będzie łatwiejsze - Testowanie podstawowej logiki aplikacji polega na testowaniu czystych funkcji, bez żadnej zależności od Angular2 (bardzo łatwe!) [1]: http://redux.js.org [2]: http: // reactivex. io / rxjs

Kompletny przykład: Zaloguj się / wyloguj użytkownika

Wymagania wstępne

Ten temat nie dotyczy Redux i / lub Ngrx:

  • Musisz być wygodny z Redux
  • Zrozum przynajmniej podstawy RxJ i obserwowalnego wzorca

Najpierw zdefiniujmy przykład od samego początku i grajmy z pewnym kodem:

Jako programista chcę:

  1. IUser interfejs IUser , który definiuje właściwości User
  2. Zadeklaruj działania, których będziemy używać później do manipulowania User w Store
  3. Zdefiniuj stan początkowy UserReducer
  4. Utwórz reduktor UserReducer
  5. Zaimportuj nasz UserReducer do naszego głównego modułu, aby zbudować Store
  6. Użyj danych ze Store aby wyświetlić informacje w naszym widoku

Spoiler alert: Jeśli chcesz wypróbować demo od razu lub odczytać kodu zanim nawet zacząć, oto Plunkr ( osadzić widoku lub uruchomić widok ).

1) Zdefiniuj interfejs IUser

Lubię dzielić interfejsy na dwie części:

  • Właściwości otrzymamy z serwera
  • Właściwości definiujemy tylko dla interfejsu użytkownika (na przykład powinien się obracać przycisk)

A oto interfejs IUser którego będziemy używać:

user.interface.ts

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

2) Zadeklaruj działania mające na celu manipulowanie User

Teraz musimy zastanowić się, jakie działania powinny obsługiwać nasze reduktory .
Powiedzmy tutaj:

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

Ale zanim skorzystamy z tych akcji, pozwól mi wyjaśnić, dlaczego potrzebujemy usługi, aby wysłać nam niektóre z tych akcji:

Powiedzmy, że chcemy połączyć użytkownika. Klikamy przycisk logowania i oto, co się stanie:

  • Kliknij przycisk
  • Składnik przechwytuje zdarzenie i wywołuje userService.login
  • userService.login metoda dispatch zdarzenie zaktualizować naszą własność sklepu: user.isConnecting
  • Wywoływane jest połączenie HTTP (użyjemy setTimeout w wersji demo, aby zasymulować zachowanie asynchroniczne )
  • Po zakończeniu połączenia HTTP wykonamy kolejne działanie, aby ostrzec nasz sklep, że użytkownik jest zalogowany

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) Zdefiniuj stan początkowy UserReducer

user.state.ts

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

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

4) Utwórz reduktor UserReducer

Reduktor przyjmuje 2 argumenty:

  • Aktualny stan
  • Action typu Action<{type: string, payload: any}>

Przypomnienie: w pewnym momencie należy zainicjować reduktor

Ponieważ zdefiniowaliśmy domyślny stan naszego reduktora w części 3), będziemy mogli go użyć w następujący sposób:

user.reducer.ts

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

Mamy nadzieję, że istnieje łatwiejszy sposób, aby to napisać, używając naszej funkcji factory do zwrócenia obiektu, aw ramach reduktora użyj wartości domyślnych parametrów (ES6):

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

Następnie musimy obsłużyć wszystkie działania naszego reduktora: WSKAZÓWKA : Użyj funkcji ES6 Object.assign aby utrzymać nasz stan na niezmiennym poziomie

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) Zaimportuj nasz UserReducer do naszego głównego modułu, aby zbudować 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) Użyj danych ze Store aby wyświetlić informacje w naszym widoku

Wszystko jest teraz gotowe po stronie logicznej i musimy tylko wyświetlić to, czego chcemy w dwóch komponentach:

  • UserComponent : [Dumb component] Po prostu przekażemy obiekt użytkownika ze sklepu za pomocą właściwości @Input i potoku async . W ten sposób komponent otrzyma użytkownika tylko wtedy, gdy będzie dostępny (a user będzie typu IUser a nie typu Observable<IUser> !)
  • LoginComponent [Inteligentny komponent] Będziemy bezpośrednio wstrzykiwać Store do tego komponentu i działać tylko na user jako 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();
  }
}

Ponieważ Ngrx jest połączeniem koncepcji Redux i RxJs , na początku może być dość trudno zrozumieć tajniki. Ale jest to potężny wzorzec, który pozwala, jak widzieliśmy w tym przykładzie, mieć reaktywną aplikację i umożliwić łatwe udostępnianie danych. Nie zapominaj, że jest dostępny Plunkr i możesz go rozwidlić, aby wykonać własne testy!

Mam nadzieję, że to pomogło, nawet jeśli temat jest dość długi, na zdrowie!



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow