Angular 2
NGRX
Zoeken…
Invoering
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:
- Heb een
IUser
interface die de eigenschappen van eenUser
definieert - Verklaar de acties die we later zullen gebruiken om de
User
in deStore
te manipuleren - Definieer de begintoestand van de
UserReducer
- Maak de reducer
UserReducer
- Importeer onze
UserReducer
in onze hoofdmodule om deStore
te bouwen - 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
methodedispatch
een gebeurtenis om onzeuser.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 typeAction<{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
enasync
pipe. Op deze manier ontvangt het onderdeel de gebruiker pas als het beschikbaar is (en deuser
is van het typeIUser
en niet van het typeObservable<IUser>
!) -
LoginComponent
[Smart component] We injecteren deStore
rechtstreeks in dit component en werken alleen alsuser
alsObservable
.
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!