Angular 2
ngrx
Suche…
Einführung
Vollständiges Beispiel: Anmelden / Abmelden eines Benutzers
Voraussetzungen
Dieses Thema behandelt nicht Redux und / oder Ngrx:
- Sie müssen mit Redux vertraut sein
- Verstehe zumindest die Grundlagen von RxJs und Observable Pattern
Zuerst definieren wir ein Beispiel von Anfang an und spielen mit etwas Code:
Als Entwickler möchte ich:
-
IUser
eineIUser
Schnittstelle, die die Eigenschaften einesUser
- Deklarieren Sie die Aktionen, die wir später verwenden werden, um den
User
imStore
- Definieren Sie den Anfangszustand des
UserReducer
- Erstellen Sie den Reduzierer
UserReducer
- Importieren Sie unseren
UserReducer
in unser Hauptmodul, um denStore
zu erstellen - Verwenden Sie Daten aus dem
Store
, um Informationen in unserer Ansicht anzuzeigen
Spoiler-Alarm : Wenn Sie die Demo sofort ausprobieren oder den Code lesen möchten, bevor wir überhaupt anfangen, ist hier ein Plunkr (Embed- View oder Run-View ).
1) Definieren Sie die IUser
Schnittstelle
Ich mag es, meine Schnittstellen in zwei Teile aufzuteilen:
- Die Eigenschaften erhalten wir von einem Server
- Die Eigenschaften, die wir nur für die Benutzeroberfläche definieren (sollte sich beispielsweise eine Schaltfläche drehen)
Und hier ist das Interface, IUser
ich verwenden werde:
user.interface.ts
export interface IUser {
// from server
username: string;
email: string;
// for UI
isConnecting: boolean;
isConnected: boolean;
};
2) Deklarieren Sie die Aktionen zur Manipulation des User
Jetzt müssen wir darüber nachdenken, mit welchen Aktionen unsere Reduzierer umgehen sollen.
Sagen wir hier:
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'
};
Bevor wir diese Aktionen verwenden, lassen Sie mich erklären, warum wir einen Dienst benötigen, um einige dieser Aktionen für uns auszuführen:
Angenommen, wir möchten einen Benutzer verbinden. Wir werden also auf einen Login-Button klicken und Folgendes wird passieren:
- Klicken Sie auf die Schaltfläche
- Die Komponente fängt das Ereignis ab und ruft
userService.login
-
userService.login
Methodedispatch
ein Ereignis unseren Speicher Eigenschaft zu aktualisieren:user.isConnecting
- Ein HTTP-Aufruf wird ausgelöst (wir verwenden ein
setTimeout
in der Demo, um das async-Verhalten zu simulieren) - Sobald der
HTTP
Aufruf abgeschlossen ist, werden wir eine weitere Aktion auslösen, um unseren Shop zu warnen, dass ein Benutzer angemeldet ist
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) Definieren Sie den Anfangszustand des UserReducer
user.state.ts
export const UserFactory: IUser = () => {
return {
// from server
username: null,
email: null,
// for UI
isConnecting: false,
isConnected: false,
isDisconnecting: false
};
};
4) Erstellen Sie den Reduzierer UserReducer
Ein Reduzierer benötigt 2 Argumente:
- Der aktuelle Zustand
- Eine
Action
vom TypAction<{type: string, payload: any}>
Zur Erinnerung: Ein Reduzierer muss irgendwann initialisiert werden
Da wir in Teil 3 den Standardzustand unseres Reduzierers definiert haben, können wir ihn so verwenden:
user.reducer.ts
export const UserReducer: ActionReducer<IUser> = (user: IUser, action: Action) => {
if (user === null) {
return userFactory();
}
// ...
}
Hoffentlich gibt es eine einfachere Möglichkeit, dies zu schreiben, indem Sie unsere factory
Funktion verwenden, um ein Objekt zurückzugeben, und innerhalb des Reduzierers einen (ES6) Standardparameterwert verwenden :
export const UserReducer: ActionReducer<IUser> = (user: IUser = UserFactory(), action: Action) => {
// ...
}
Dann müssen wir alle Aktionen in unserem Minderer zu handhaben : TIP: Verwenden Sie ES6 Object.assign
Funktion unseres Staates unveränderlich zu halten
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) Importieren Sie unseren UserReducer
in unser Hauptmodul, um den Store
zu erstellen
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) Verwenden Sie Daten aus dem Store
, um Informationen in unserer Ansicht anzuzeigen
Alles ist jetzt auf der Logikseite fertig und wir müssen nur das zeigen, was wir wollen, in zwei Komponenten:
-
UserComponent
: [Dumme Komponente] Wir übergeben das Benutzerobjekt einfach mit der@Input
Eigenschaft und derasync
Pipe aus dem Speicher. Auf diese Weise erhält die Komponente den Benutzer erst, wenn er verfügbar ist (und deruser
ist vom TypIUser
und nicht vom TypObservable<IUser>
!). -
LoginComponent
[Smart-Komponente] Wir injizieren denStore
direkt in diese Komponente und arbeiten nur für denuser
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();
}
}
Wie Ngrx
eine Zusammenführung ist Redux
und RxJs
Konzepte, kann es sehr schwierig sein , die In eine outs am Anfang zu verstehen. Dies ist jedoch ein leistungsfähiges Muster, mit dem Sie, wie wir in diesem Beispiel gesehen haben, eine reaktive App haben können und Ihre Daten problemlos freigeben können. Vergiss nicht, dass es einen Plunkr gibt und du kannst ihn für deine eigenen Tests zusammenfassen!
Ich hoffe, es war hilfreich, obwohl das Thema ziemlich lang ist, Prost!