Angular 2
ngrx
Buscar..
Introducción
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:
- Tener una
IUser
interfaz que define las propiedades de unUser
- Declare las acciones que usaremos más adelante para manipular al
User
en laStore
- Definir el estado inicial del
UserReducer
- Crear el reductor
UserReducer
- Importe nuestro
UserReducer
en nuestro módulo principal para construir laStore
- 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étodouserService.login
dispatch
un evento para actualizar nuestra propiedad de la tienda:user.isConnecting
- Se
setTimeout
una llamada HTTP (usaremos unsetTimeout
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 tipoAction<{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 conductoasync
. De esta manera, el componente recibirá al usuario solo una vez que esté disponible (¡y eluser
será de tipoIUser
y no de tipoObservable<IUser>
!) -
LoginComponent
[componente inteligente] Inyectaremos directamente laStore
en este componente y trabajaremos solo en eluser
comoObservable
.
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!