Angular 2
ngrx
Szukaj…
Wprowadzenie
Kompletny przykład: Zaloguj się / wyloguj użytkownika
Wymagania wstępne
Ten temat nie dotyczy Redux i / lub Ngrx:
- Musisz być wygodny z Redux
- Zrozum przynajmniej podstawy RxJ i obserwowalnego wzorca
Najpierw zdefiniujmy przykład od samego początku i grajmy z pewnym kodem:
Jako programista chcę:
-
IUser
interfejsIUser
, który definiuje właściwościUser
- Zadeklaruj działania, których będziemy używać później do manipulowania
User
wStore
- Zdefiniuj stan początkowy
UserReducer
- Utwórz reduktor
UserReducer
- Zaimportuj nasz
UserReducer
do naszego głównego modułu, aby zbudowaćStore
- Użyj danych ze
Store
aby wyświetlić informacje w naszym widoku
Spoiler alert: Jeśli chcesz wypróbować demo od razu lub odczytać kodu zanim nawet zacząć, oto Plunkr ( osadzić widoku lub uruchomić widok ).
1) Zdefiniuj interfejs IUser
Lubię dzielić interfejsy na dwie części:
- Właściwości otrzymamy z serwera
- Właściwości definiujemy tylko dla interfejsu użytkownika (na przykład powinien się obracać przycisk)
A oto interfejs IUser
którego będziemy używać:
user.interface.ts
export interface IUser {
// from server
username: string;
email: string;
// for UI
isConnecting: boolean;
isConnected: boolean;
};
2) Zadeklaruj działania mające na celu manipulowanie User
Teraz musimy zastanowić się, jakie działania powinny obsługiwać nasze reduktory .
Powiedzmy tutaj:
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'
};
Ale zanim skorzystamy z tych akcji, pozwól mi wyjaśnić, dlaczego potrzebujemy usługi, aby wysłać nam niektóre z tych akcji:
Powiedzmy, że chcemy połączyć użytkownika. Klikamy przycisk logowania i oto, co się stanie:
- Kliknij przycisk
- Składnik przechwytuje zdarzenie i wywołuje
userService.login
-
userService.login
metodadispatch
zdarzenie zaktualizować naszą własność sklepu:user.isConnecting
- Wywoływane jest połączenie HTTP (użyjemy
setTimeout
w wersji demo, aby zasymulować zachowanie asynchroniczne ) - Po zakończeniu połączenia
HTTP
wykonamy kolejne działanie, aby ostrzec nasz sklep, że użytkownik jest zalogowany
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) Zdefiniuj stan początkowy UserReducer
user.state.ts
export const UserFactory: IUser = () => {
return {
// from server
username: null,
email: null,
// for UI
isConnecting: false,
isConnected: false,
isDisconnecting: false
};
};
4) Utwórz reduktor UserReducer
Reduktor przyjmuje 2 argumenty:
- Aktualny stan
-
Action
typuAction<{type: string, payload: any}>
Przypomnienie: w pewnym momencie należy zainicjować reduktor
Ponieważ zdefiniowaliśmy domyślny stan naszego reduktora w części 3), będziemy mogli go użyć w następujący sposób:
user.reducer.ts
export const UserReducer: ActionReducer<IUser> = (user: IUser, action: Action) => {
if (user === null) {
return userFactory();
}
// ...
}
Mamy nadzieję, że istnieje łatwiejszy sposób, aby to napisać, używając naszej funkcji factory
do zwrócenia obiektu, aw ramach reduktora użyj wartości domyślnych parametrów (ES6):
export const UserReducer: ActionReducer<IUser> = (user: IUser = UserFactory(), action: Action) => {
// ...
}
Następnie musimy obsłużyć wszystkie działania naszego reduktora: WSKAZÓWKA : Użyj funkcji ES6 Object.assign
aby utrzymać nasz stan na niezmiennym poziomie
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) Zaimportuj nasz UserReducer
do naszego głównego modułu, aby zbudować 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) Użyj danych ze Store
aby wyświetlić informacje w naszym widoku
Wszystko jest teraz gotowe po stronie logicznej i musimy tylko wyświetlić to, czego chcemy w dwóch komponentach:
-
UserComponent
: [Dumb component] Po prostu przekażemy obiekt użytkownika ze sklepu za pomocą właściwości@Input
i potokuasync
. W ten sposób komponent otrzyma użytkownika tylko wtedy, gdy będzie dostępny (auser
będzie typuIUser
a nie typuObservable<IUser>
!) -
LoginComponent
[Inteligentny komponent] Będziemy bezpośrednio wstrzykiwaćStore
do tego komponentu i działać tylko nauser
jakoObservable
.
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();
}
}
Ponieważ Ngrx
jest połączeniem koncepcji Redux
i RxJs
, na początku może być dość trudno zrozumieć tajniki. Ale jest to potężny wzorzec, który pozwala, jak widzieliśmy w tym przykładzie, mieć reaktywną aplikację i umożliwić łatwe udostępnianie danych. Nie zapominaj, że jest dostępny Plunkr i możesz go rozwidlić, aby wykonać własne testy!
Mam nadzieję, że to pomogło, nawet jeśli temat jest dość długi, na zdrowie!