Angular 2
ngrx
Sök…
Introduktion
Komplett exempel: Logga in / logga ut en användare
förutsättningar
Detta ämne handlar inte om Redux och / eller Ngrx:
- Du måste vara bekväm med Redux
- Åtminstone förstå grunderna i RxJs och observerbara mönster
Låt oss först definiera ett exempel från början och spela med någon kod:
Som utvecklare vill jag:
- Har du ett
IUser
gränssnitt som definierar egenskaperna hos enUser
- Deklarera de åtgärder som vi kommer att använda senare för att manipulera
User
iStore
- Definiera det initiala läget för
UserReducer
- Skapa reduceraren
UserReducer
- Importera vår
UserReducer
till vår huvudmodul för att byggaStore
- Använd data från
Store
att visa information i vår vy
Spoiler-varning : Om du vill prova demon direkt eller läsa koden innan vi ens börjar, här är en Plunkr ( bädda in vy eller körvyn ).
1) Definiera IUser
gränssnitt
Jag vill dela mina gränssnitt i två delar:
- Egenskaperna får vi från en server
- Egenskaperna som vi definierar endast för användargränssnittet (om en knapp roterar till exempel)
Och här är det gränssnitt IUser
vi kommer att använda:
user.interface.ts
export interface IUser {
// from server
username: string;
email: string;
// for UI
isConnecting: boolean;
isConnected: boolean;
};
2) Förklara åtgärderna för att manipulera User
Nu måste vi tänka på vilken typ av åtgärder våra reducerare är tänkta att hantera.
Låt oss säga här:
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'
};
Men innan vi använder dessa åtgärder, låt mig förklara varför vi kommer att behöva en tjänst för att skicka några av dessa åtgärder för oss:
Låt oss säga att vi vill ansluta en användare. Så vi kommer att klicka på en inloggningsknapp och här är vad som kommer att hända:
- Klicka på knappen
- Komponenten fångar händelsen och ringer
userService.login
-
userService.login
metodendispatch
en händelse för att uppdatera våruser.isConnecting
:user.isConnecting
- Ett HTTP-samtal avfyras (vi använder en
setTimeout
i demonstrationen för att simulera async-beteendet ) - När
HTTP
samtalet är klart skickar vi ytterligare en åtgärd för att varna vår butik om att en användare är inloggad
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) Definiera det initiala tillståndet för UserReducer
user.state.ts
export const UserFactory: IUser = () => {
return {
// from server
username: null,
email: null,
// for UI
isConnecting: false,
isConnected: false,
isDisconnecting: false
};
};
4) Skapa reduceraren UserReducer
En reducerare tar två argument:
- Det aktuella tillståndet
- En
Action
av typenAction<{type: string, payload: any}>
Påminnelse: En reducerare måste initialiseras vid någon tidpunkt
När vi definierade standardtillståndet för vår reducerare i del 3) kommer vi att kunna använda det så:
user.reducer.ts
export const UserReducer: ActionReducer<IUser> = (user: IUser, action: Action) => {
if (user === null) {
return userFactory();
}
// ...
}
Förhoppningsvis finns det ett enklare sätt att skriva det genom att använda vår factory
att returnera ett objekt och inom reduceraren använda ett (ES6) standardparametervärde :
export const UserReducer: ActionReducer<IUser> = (user: IUser = UserFactory(), action: Action) => {
// ...
}
Sedan måste vi hantera alla åtgärder i vår reducerare: TIPS : Använd funktionen ES6 Object.assign
att hålla vårt tillstånd oföränderligt
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) Importera vår UserReducer
till vår huvudmodul för att bygga 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) Använd data från Store
att visa information i vår vy
Allt är nu klart på logisk sida och vi måste bara visa vad vi vill ha i två komponenter:
-
UserComponent
: [Dumb component] Vi passerar bara användarobjektet från butiken med@Input
egenskap ochasync
rör. På det här sättet kommer komponenten att ta emot användaren endast när den är tillgänglig (ochuser
kommer att vara av typenIUser
och inte av typenObservable<IUser>
!) -
LoginComponent
[Smart component] Vi injicerarStore
direkt i denna komponent och fungerar endast påuser
somObservable
.
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();
}
}
Eftersom Ngrx
är en sammanslagning av Redux
och RxJs
koncept, kan det vara ganska svårt att förstå de ins och outs i början. Men detta är ett kraftfullt mönster som låter dig som vi har sett i det här exemplet ha en reaktiv app och om du enkelt kan dela dina data. Glöm inte att det finns en Plunkr tillgänglig och du kan gaffla den för att göra dina egna tester!
Jag hoppas att det var till hjälp även om ämnet är ganska långt, jubel!