Angular 2
ngrx
サーチ…
前書き
完全な例:ユーザのログイン/ログアウト
前提条件
このトピックは、Reduxおよび/またはNgrxについてのトピックではありません 。
- あなたはReduxに慣れている必要があります
- 少なくとも、RxJsとObservableパターンの基本を理解する
最初に、最初からサンプルを定義し、いくつかのコードで試してみましょう:
開発者として、私はしたい:
-
User
プロパティを定義するIUser
インターフェイスを持つ - 後で
Store
User
を操作するために使用するアクションを宣言する -
UserReducer
初期状態を定義する - レデューサー
UserReducer
作成する -
UserReducer
をメインモジュールにインポートしてStore
を構築する -
Store
からのデータを使用してビューに情報を表示する
Spoiler alert :すぐにデモを試したり、コードを読んだりする前に、Plunkr( 埋め込みビューまたは実行ビュー ) をご覧ください 。
1) IUser
インターフェースを定義する
私は2つの部分で私のインターフェイスを分割するのが好きです:
- サーバーから取得するプロパティ
- UIに対してのみ定義するプロパティ(ボタンが回転している場合など)
そしてここで私たちが使用するインターフェースIUser
があります:
user.interface.ts
export interface IUser {
// from server
username: string;
email: string;
// for UI
isConnecting: boolean;
isConnected: boolean;
};
2) User
を操作するアクションを宣言する
今、私たちは、 減速機がどのような行動を取るべきかについて考える必要があります。
ここで言う:
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'
};
しかし、私たちがこれらの行動を使用する前に、私たちになぜそれらの行動のうちのいくつかを派遣するサービスが必要となる理由を説明しましょう。
ユーザーと接続したいとしましょう。ログインボタンをクリックすると、次のようなことが起こります:
- ボタンをクリック
- コンポーネントはイベントをキャッチし、
userService.login
を呼び出しますuserService.login
-
userService.login
メソッドdispatch
、イベントをdispatch
してstoreプロパティを更新しますuser.isConnecting
- HTTPコールが起動されます(デモの
setTimeout
を使用して非同期動作をシミュレートします ) -
HTTP
コールが終了すると、ユーザーがログに記録されていることを店舗に警告する別のアクションをHTTP
ます
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) UserReducer
初期状態を定義する
user.state.ts
export const UserFactory: IUser = () => {
return {
// from server
username: null,
email: null,
// for UI
isConnecting: false,
isConnected: false,
isDisconnecting: false
};
};
4)減速機を作成するUserReducer
減速器には2つの引数があります。
- 現在の状態
-
Action
タイプAction<{type: string, payload: any}>
注意:減速機はある時点で初期化する必要があります
パート3の減速機のデフォルト状態を定義したので、次のように使用することができます:
user.reducer.ts
export const UserReducer: ActionReducer<IUser> = (user: IUser, action: Action) => {
if (user === null) {
return userFactory();
}
// ...
}
うまくいけば、私たちのfactory
関数を使ってオブジェクトを返すことで、それを書く簡単な方法があり、減速機内には(ES6) デフォルトのパラメータ値を使う:
export const UserReducer: ActionReducer<IUser> = (user: IUser = UserFactory(), action: Action) => {
// ...
}
それでは、減速機のすべてのアクションを処理する必要があります。 ヒント : ES6 Object.assign
関数を使用して状態を不変に保ちます
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) UserReducer
をメインモジュールにインポートして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) Store
からのデータを使用して、ビューに情報を表示する
すべてのものが論理的な面で準備されており、私たちが望むものを2つのコンポーネントに表示するだけです。
-
UserComponent
: [Dumb component]@Input
プロパティとasync
パイプを使用して、ストアからユーザーオブジェクトを渡すだけです。このようにして、コンポーネントは使用可能になるとユーザーを受け取ります(user
はタイプObservable<IUser>
ではなくタイプIUser
なります)。 -
LoginComponent
[Smartコンポーネント]このコンポーネントにStore
を直接注入し、Observable
としてuser
のみで作業しuser
。
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();
}
}
Ngrx
はRedux
とRxJs
コンセプトのマージであるため、最初はインアウトを理解するのはかなり難しいでしょう。しかし、これは強力なパターンです。この例では、 反応的なアプリを持っていて、データを簡単に共有できるようになっています。利用可能なPlunkrがあることを忘れないでください。あなた自身のテストを行うためにそれをフォークすることができます!
私はそれもトピックがかなり長い歓迎であっても助けてくれることを願っています!