Buscar..


Introducción

Ngrx es una biblioteca poderosa que puedes usar con Angular2 . La idea subyacente es combinar dos conceptos que funcionen bien juntos para tener una aplicación reactiva con un contenedor de estado predecible: - [Redux] [1] - [RxJs] [2] Las principales ventajas: - Compartir datos en su aplicación entre sus componentes va a ser más fácil: probar la lógica central de su aplicación consiste en probar funciones puras, sin ninguna dependencia de Angular2 (¡muy fácil!) [1]: http://redux.js.org [2]: http: // reactivex. io / rxjs

Ejemplo completo: iniciar sesión / cerrar sesión de un usuario

Prerrequisitos

Este tema no es sobre Redux y / o Ngrx:

  • Necesitas estar cómodo con Redux
  • Al menos entender los conceptos básicos de RxJs y el patrón observable

Primero, definamos un ejemplo desde el principio y juguemos con algún código:

Como desarrollador, quiero:

  1. Tener una IUser interfaz que define las propiedades de un User
  2. Declare las acciones que usaremos más adelante para manipular al User en la Store
  3. Definir el estado inicial del UserReducer
  4. Crear el reductor UserReducer
  5. Importe nuestro UserReducer en nuestro módulo principal para construir la Store
  6. Utilice los datos de la Store para mostrar información en nuestra vista

Alerta de spoiler : si desea probar la demostración de inmediato o leer el código antes de comenzar, aquí hay un Plunkr ( vista de incrustación o vista de ejecución ).

1) Definir la interfaz IUser

Me gusta dividir mis interfaces en dos partes:

  • Las propiedades que obtendremos de un servidor.
  • Las propiedades que definimos solo para la interfaz de usuario (por ejemplo, un botón debe estar girando)

Y aquí está la interfaz que IUser :

user.interface.ts

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

2) Declarar las acciones para manipular al User

Ahora tenemos que pensar qué tipo de acciones se supone que deben manejar nuestros reductores .
Vamos a decir aquí:

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

Pero antes de usar esas acciones, permítame explicar por qué vamos a necesitar un servicio para enviar algunas de esas acciones para nosotros:

Digamos que queremos conectar a un usuario. Entonces, haremos clic en el botón de inicio de sesión y esto es lo que sucederá:

  • Haga clic en el botón
  • El componente captura el evento y llama a userService.login
  • userService.login método userService.login dispatch un evento para actualizar nuestra propiedad de la tienda: user.isConnecting
  • Se setTimeout una llamada HTTP (usaremos un setTimeout en la demostración para simular el comportamiento asíncrono )
  • Una vez que finalice la llamada HTTP , enviaremos otra acción para advertir a nuestra tienda que un usuario está registrado

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) Definir el estado inicial del UserReducer

user.state.ts

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

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

4) Crear el reductor UserReducer

Un reductor toma 2 argumentos:

  • El estado actual
  • Una Action de tipo Action<{type: string, payload: any}>

Recordatorio: un reductor debe inicializarse en algún momento

Como definimos el estado predeterminado de nuestro reductor en la parte 3), podremos usarlo así:

user.reducer.ts

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

Con suerte, hay una forma más fácil de escribir eso al usar nuestra función de factory para devolver un objeto y dentro del reductor usar un valor de parámetros predeterminado (ES6):

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

Luego, debemos manejar cada acción en nuestro reductor: SUGERENCIA : use la función ES6 Object.assign para mantener nuestro estado inmutable

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) Importe nuestro UserReducer en nuestro módulo principal para construir la 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) Utilice los datos de la Store para mostrar información en nuestra vista

Ahora todo está listo en el lado lógico y solo tenemos que mostrar lo que queremos en dos componentes:

  • UserComponent : [Dumb component] Solo pasaremos el objeto de usuario de la tienda usando la propiedad @Input y el conducto async . De esta manera, el componente recibirá al usuario solo una vez que esté disponible (¡y el user será de tipo IUser y no de tipo Observable<IUser> !)
  • LoginComponent [componente inteligente] Inyectaremos directamente la Store en este componente y trabajaremos solo en el user como 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();
  }
}

Como Ngrx es una combinación de los conceptos de Redux y RxJs , puede ser bastante difícil de entender al principio. Pero este es un patrón poderoso que le permite, como hemos visto en este ejemplo, tener una aplicación reactiva en la que puede compartir fácilmente sus datos. ¡No olvide que hay un Plunkr disponible y puede hacerlo para hacer sus propias pruebas!

Espero que haya sido útil, aunque el tema es bastante largo, ¡salud!



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow