Angular 2
NGRX
Ricerca…
introduzione
Esempio completo: Login / logout di un utente
Prerequisiti
Questo argomento non riguarda Redux e / o Ngrx:
- Devi stare tranquillo con Redux
- Almeno capire le basi di RxJs e modello osservabile
Per prima cosa, definiamo un esempio fin dall'inizio e giochiamo con un codice:
Come sviluppatore, voglio:
- Avere un'interfaccia
IUser
che definisce le proprietà di unUser
- Dichiarare le azioni che useremo in seguito per manipolare l'
User
nelloStore
- Definire lo stato iniziale di
UserReducer
- Crea il riduttore
UserReducer
- Importa il nostro
UserReducer
nel nostro modulo principale per costruire loStore
- Utilizza i dati dallo
Store
per visualizzare le informazioni nella nostra vista
Avviso spoiler : se vuoi provare subito la demo o leggere il codice prima ancora di iniziare, ecco un Plunkr ( visualizzazione incorporata o visualizzazione di esecuzione ).
1) Definire IUser
interfaccia IUser
Mi piace dividere le mie interfacce in due parti:
- Le proprietà che otterremo da un server
- Le proprietà che definiamo solo per l'interfaccia utente (se un pulsante deve ruotare ad esempio)
Ed ecco l'interfaccia IUser
che IUser
:
user.interface.ts
export interface IUser {
// from server
username: string;
email: string;
// for UI
isConnecting: boolean;
isConnected: boolean;
};
2) Dichiarare le azioni per manipolare l' User
Ora dobbiamo pensare a quale tipo di azioni devono essere gestite dai nostri riduttori .
Diciamo qui:
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'
};
Ma prima di utilizzare tali azioni, lasciami spiegare perché avremo bisogno di un servizio per inviare alcune di queste azioni per noi:
Diciamo che vogliamo connettere un utente. Quindi faremo clic su un pulsante di accesso ed ecco cosa succederà:
- Clicca sul pulsante
- Il componente intercetta l'evento e chiama
userService.login
-
userService.login
metodouserService.login
dispatch
un evento per aggiornare la nostra proprietà del negozio:user.isConnecting
- Viene lanciata una chiamata HTTP (useremo un
setTimeout
nella demo per simulare il comportamento asincrono ) - Una volta terminata la chiamata
HTTP
, invieremo un'altra azione per avvisare il nostro archivio che un utente è registrato
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) Definire lo stato iniziale di UserReducer
user.state.ts
export const UserFactory: IUser = () => {
return {
// from server
username: null,
email: null,
// for UI
isConnecting: false,
isConnected: false,
isDisconnecting: false
};
};
4) Crea il riduttore UserReducer
Un riduttore accetta 2 argomenti:
- Lo stato attuale
-
Action
di tipoAction<{type: string, payload: any}>
Promemoria: un riduttore deve essere inizializzato ad un certo punto
Come abbiamo definito lo stato predefinito del nostro riduttore nella parte 3, saremo in grado di usarlo in questo modo:
user.reducer.ts
export const UserReducer: ActionReducer<IUser> = (user: IUser, action: Action) => {
if (user === null) {
return userFactory();
}
// ...
}
Speriamo che ci sia un modo più semplice per scrivere che usando la nostra funzione di factory
per restituire un oggetto e all'interno del riduttore usare un valore di parametri di default (ES6):
export const UserReducer: ActionReducer<IUser> = (user: IUser = UserFactory(), action: Action) => {
// ...
}
Quindi, dobbiamo gestire tutte le azioni del nostro riduttore: SUGGERIMENTO : utilizzare la funzione Object.assign
ES6 per mantenere il nostro stato immutabile
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) Importa il nostro UserReducer
nel nostro modulo principale per costruire lo 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) Utilizzare i dati dallo Store
per visualizzare le informazioni nella nostra vista
Tutto è ora pronto per il lato logico e dobbiamo solo mostrare ciò che vogliamo in due componenti:
-
UserComponent
: [Componente stupido]UserComponent
semplicemente l'oggetto utente dallo store usando la proprietà@Input
e la pipeasync
. In questo modo, il componente riceverà l'utente solo una volta disponibile (e l'user
sarà di tipoIUser
e non di tipoObservable<IUser>
!) -
LoginComponent
[Componente intelligente]LoginComponent
direttamente loStore
in questo componente e lavoreremo solouser
comeObservable
.
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();
}
}
Dato che Ngrx
è un'unione dei concetti di Redux
e RxJs
, può essere piuttosto difficile capire all'inizio un outs. Ma questo è uno schema potente che ti consente, come abbiamo visto in questo esempio, di avere un'app reattiva ed è possibile condividere facilmente i tuoi dati. Non dimenticare che c'è un Plunkr disponibile e puoi forarlo per fare i tuoi test!
Spero sia stato utile anche se l'argomento è abbastanza lungo, evviva!