NgrxとAngular2チュートリアル:リアクティブアプリケーションの構築
Angularレルムにおけるリアクティブプログラミングについて多くのことを話しています。 リアクティブプログラミングとAngular2は手をつないで行くようです。 しかし、両方の技術に精通していない人にとっては、それが何であるかを理解するのは非常に困難な作業になる可能性があります。この記事では、Ngrxを使用してreactive Angular2アプリケーションを構築することで、パターンが何であるか、パターンが有用であることが証明できる場所、およびパターNgrxは、リアクティブ拡張のためのAngularライブラリのグループです。 Ngrx/Storeは、Angular2のよく知られているrxjsオブザーバブルを使用してReduxパターンを実装します。 アプリケーションの状態を単純なオブジェクトに単純化したり、単方向のデータフローを適用したりすることで、いくつかの利点があります。 Ngrx/Effectsライブラリは、アプリケーションが副作用をトリガーすることにより、外界と通信することができます。
リアクティブプログラミングとは何ですか?リアクティブプログラミングは、最近よく耳にする言葉ですが、実際にはどういう意味ですか?
リアクティブプログラミングは、アプリケーションでイベントとデータフローを処理する方法です。 リアクティブプログラミングでは、変更を要求するのではなく、それらの変更に対応するために、コンポーネントやソフトウェアの他の部分を設計 これは大きな変化になる可能性があります。
リアクティブプログラミングのための素晴らしいツールは、あなたが知っているかもしれないように、RxJSです。
受信データを変換するための観測値と多くの演算子を提供することで、このライブラリはアプリケーションのイベントを処理するのに役立ちます。 実際、observablesを使用すると、イベントをイベントのストリームとして見ることができ、1回限りのイベントではありません。 これにより、たとえば、それらを組み合わせて、リッスンする新しいイベントを作成することができます。
リアクティブプログラミングは、アプリケーションのさまざまな部分間で通信する方法のシフトです。 リアクティブプログラミングでは、必要なコンポーネントまたはサービスにデータを直接プッシュするのではなく、データの変更に反応するコンポーネ
Ngrxについての言葉
このチュートリアルでビルドするアプリケーションを理解するには、reduxのコア概念に素早く飛び込む必要があります。ストアはクライアント側のデータベースと見なすことができますが、さらに重要なことに、アプリケーションの状態を反映しています。 あなたはそれを真実の単一の源として見ることができます。これは、Reduxパターンに従い、アクションをディスパッチして変更するときに変更する唯一のものです。
Reducer
Reducerは、特定のアクションとアプリの以前の状態をどうするかを知る関数です。reducersはストアから以前の状態を取得し、それに純粋な関数を適用します。
reducersはあなたのストアから以前の状態を取得し、それに純粋な関数を適用します。 Pureは、関数が同じ入力に対して常に同じ値を返し、副作用がないことを意味します。 その純粋な関数の結果から、あなたはあなたの店に置かれる新しい状態を持つでしょう。
アクション
アクションは、ストアを変更するために必要な情報を含むペイロードです。 基本的に、actionには、reducer関数が状態を変更するために取るタイプとペイロードがあります。
Dispatcher
Dispatcherは、アクションをディスパッチするためのエントリポイントです。 Ngrxでは、店舗に直接発送方法があります。
ミドルウェア
ミドルウェアは、この記事では使用しませんが、副作用を作成するためにディスパッチされる各アクションを傍受する関数です。 これらはNgrx/Effectライブラリに実装されており、実際のアプリケーションを構築する際にそれらが必要になる可能性が非常に高くなります。
なぜNgrxを使用するのですか?
複雑さ
ストアと単方向のデータフローは、アプリケーションの部分間の結合を大幅に削減します。 この結合の減少により、各部分は特定の状態のみを気にするため、アプリケーションの複雑さが軽減されます。
Tooling
アプリケーションの状態全体が一箇所に格納されるため、アプリケーションの状態をグローバルに把握するのは簡単で、開発中に役立ちます。 また、Reduxでは、ストアを利用し、アプリケーションの特定の状態を再現したり、タイムトラベルを行うのに役立つ多くの素敵な開発ツールが付属しています。
アーキテクチャのシンプルさ
Ngrxの利点の多くは、他のソリューションで達成可能です。 しかし、共同編集ツールなど、Reduxパターンに適したアプリケーションを構築する必要がある場合は、パターンに従うことで簡単に機能を追加できます。
何をしているかを考える必要はありませんが、ディスパッチされたすべてのアクションを追跡できるため、すべてのアプリケーションを介してanalyticsのようないくつかのものを追加するのは簡単になります。
小さな学習曲線
このパターンは非常に広く採用されており、シンプルなので、あなたのチームの新しい人があなたがしたことにすぐに追いつく
監視ダッシュボードなど、アプリケーションを変更できる外部アクタがたくさんある場合、Ngrxが最も輝いています。 そのような場合、アプリケーションにプッシュされるすべての受信データを管理することは困難であり、状態管理は困難になります。 それがあなたが不変の状態でそれを単純化したい理由であり、これはNgrxストアが私たちに提供する一つのことです。
Ngrxを使用したアプリケーションの構築
ngrxのパワーは、リアルタイムでアプリケーションにプッシュされている外部データがあるときに最も輝いて それを念頭に置いて、オンラインフリーランサーを示し、それらをフィルタリングすることができます単純なフリーランサーグリッドを構築してみましょう。
プロジェクトのセットアップ
Angular CLIは、セットアッププロセスを大幅に簡素化する素晴らしいツールです。 あなたはそれを使用しないが、この記事の残りの部分はそれを使用することに注意してくださいすることができます。
npm install -g @angular/cli
次に、新しいアプリケーションを作成し、すべてのNgrxライブラリをインストールしたいと思います。
ng new toptal-freelancersnpm install ngrx --save
Freelancers Reducer
ReducersはReduxアーキテクチャのコアピースであるため、アプリケーションをビルドする際に最初にそれらから始めてみませんか?
まず、アクションがストアにディスパッチされるたびに新しい状態を作成する責任を持つ”freelancers”レデューサーを作成します。
freelancer-グリッド/フリーランサー。リデューサーts
import { Action } from '@ngrx/store';export interface AppState { freelancers : Array<IFreelancer>}export interface IFreelancer { name: string, email: string, thumbnail: string}export const ACTIONS = { FREELANCERS_LOADED: 'FREELANCERS_LOADED',}export function freelancersReducer( state: Array<IFreelancer> = , action: Action): Array<IFreelancer> { switch (action.type) { case ACTIONS.FREELANCERS_LOADED: // Return the new state with the payload as freelancers list return Array.prototype.concat(action.payload); default: return state; }}
だからここに私たちのフリーランサーの減速機です。
この関数は、ストアを介してアクションがディスパッチされるたびに呼び出されます。 アクションがFREELANCERS_LOADED
の場合、アクションペイロードから新しい配列が作成されます。 そうでない場合は、古い状態参照を返し、何も追加されません。
ここでは、古い状態参照が返された場合、状態は変更されていないと見なされることに注意することが重要です。 これは、state.push(something)
を呼び出した場合、状態は変更されたとはみなされないことを意味します。 あなたの減速機機能をしている間それを心に留めておいてください。
状態は不変です。 新しい状態は、変更するたびに返される必要があります。
Freelancer Grid Component
オンラインフリーランサーを表示するグリッドコンポーネントを作成します。 最初は、店内にあるものだけが反映されます。p>
ng generate component freelancer-grid
freelancer-gridに以下を入れます。コンポーネント。ts
import { Component, OnInit } from '@angular/core';import { Store } from '@ngrx/store';import { AppState, IFreelancer, ACTIONS } from './freelancer-reducer';import * as Rx from 'RxJS';@Component({ selector: 'app-freelancer-grid', templateUrl: './freelancer-grid.component.html', styleUrls: ,})export class FreelancerGridComponent implements OnInit { public freelancers: Rx.Observable<Array<IFreelancer>>; constructor(private store: Store<AppState>) { this.freelancers = store.select('freelancers'); }}
そしてfreelancer-gridでは次のようになります。コンポーネント。html:
<span class="count">Number of freelancers online: {{(freelancers | async).length}}</span><div class="freelancer fade thumbail" *ngFor="let freelancer of freelancers | async"> <button type="button" class="close" aria-label="Close" (click)="delete(freelancer)"><span aria-hidden="true">×</span></button><br> <img class="img-circle center-block" src="{{freelancer.thumbnail}}" /><br> <div class="info"><span><strong>Name: </strong>{{freelancer.name}}</span> <span><strong>Email: </strong>{{freelancer.email}}</span></div> <a class="btn btn-default">Hire {{freelancer.name}}</a></div>
だから、あなたはちょうど何をしましたか?まず、freelancer-grid
という新しいコンポーネントを作成しました。
コンポーネントには、Ngrxストアに含まれるアプリケーション状態の一部であるfreelancers
という名前のプロパティが含まれています。 Select演算子を使用すると、アプリケーション全体の状態のfreelancers
freelancers
プロパティが変更されるたびに、observableに通知されます。
このソリューションで美しいことの一つは、コンポーネントには依存関係が一つしかなく、コンポーネントをはるかに複雑で簡単に再利用できるようにするストアであるということです。
テンプレート部分では、あまりにも複雑なことは何もしませんでした。 *ngFor
freelancers
observableは直接反復可能ではありませんが、Angularのおかげで、非同期パイプを使用してdomをラップ解除し、domをその値にバインドす これにより、observableでの作業がはるかに簡単になります。
フリーランサーの削除機能の追加
機能ベースができたので、アプリケーションにいくつかのアクションを追加しましょう。あなたは状態からフリーランサーを削除できるようにしたい。
あなたは状態からフリーランサーを削除できるようにしたい。
Reduxの動作に応じて、最初にそのアクションの影響を受ける各状態でそのアクションを定義する必要があります。この場合、freelancers
reducer:
export const ACTIONS = { FREELANCERS_LOADED: 'FREELANCERS_LOADED', DELETE_FREELANCER: 'DELETE_FREELANCER',}export function freelancersReducer( state: Array<IFreelancer> = , action: Action): Array<IFreelancer> { switch (action.type) { case ACTIONS.FREELANCERS_LOADED: // Return the new state with the payload as freelancers list return Array.prototype.concat(action.payload); case ACTIONS.DELETE_FREELANCER: // Remove the element from the array state.splice(state.indexOf(action.payload), 1); // We need to create another reference return Array.prototype.concat(state); default: return state; }}
新しい不変状態を持つために、古い配列から新しい配列を作成することは本当に重要です。これで、このアクションをストアにディスパッチするdelete freelancers関数をコンポーネントに追加できます。
delete(freelancer) { this.store.dispatch({ type: ACTIONS.DELETE_FREELANCER, payload: freelancer, }) }
それは簡単に見えませんか?
特定のフリーランサーを状態から削除できるようになり、その変更がアプリケーションに反映されます。ここで、別のコンポーネントをアプリケーションに追加して、ストアを介して相互にどのように対話できるかを確認した場合はどうなりますか?
フィルター減速機
いつものように、減速機から始めましょう。 そのコンポーネントのために、それは非常に簡単です。 リデューサーが、ディスパッチしたプロパティのみを使用して常に新しい状態を返すようにします。 これは次のようになります。
import { Action } from '@ngrx/store';export interface IFilter { name: string, email: string,}export const ACTIONS = { UPDATE_FITLER: 'UPDATE_FITLER', CLEAR_FITLER: 'CLEAR_FITLER',}const initialState = { name: '', email: '' };export function filterReducer( state: IFilter = initialState, action: Action): IFilter { switch (action.type) { case ACTIONS.UPDATE_FITLER: // Create a new state from payload return Object.assign({}, action.payload); case ACTIONS.CLEAR_FITLER: // Create a new state from initial state return Object.assign({}, initialState); default: return state; }}
フィルタコンポーネント
import { Component, OnInit } from '@angular/core';import { IFilter, ACTIONS as FilterACTIONS } from './filter-reducer';import { Store } from '@ngrx/store';import { FormGroup, FormControl } from '@angular/forms';import * as Rx from 'RxJS';@Component({ selector: 'app-filter', template: '<form class="filter">'+ '<label>Name</label>'+ '<input type="text" ="name" name="name"/>'+ '<label>Email</label>'+ '<input type="text" ="email" name="email"/>'+ '<a (click)="clearFilter()" class="btn btn-default">Clear Filter</a>'+ '</form>', styleUrls: ,})export class FilterComponent implements OnInit { public name = new FormControl(); public email = new FormControl(); constructor(private store: Store<any>) { store.select('filter').subscribe((filter: IFilter) => { this.name.setValue(filter.name); this.email.setValue(filter.email); }) Rx.Observable.merge(this.name.valueChanges, this.email.valueChanges).debounceTime(1000).subscribe(() => this.filter()); } ngOnInit() { } filter() { this.store.dispatch({ type: FilterACTIONS.UPDATE_FITLER, payload: { name: this.name.value, email: this.email.value, } }); } clearFilter() { this.store.dispatch({ type: FilterACTIONS.CLEAR_FITLER, }) }}
まず、状態を反映する二つのフィールド(名前と電子メール)を持つフォームを含む簡単なテンプレートを作成しました。これらのフィールドを状態と同期させておくのは、freelancers
formControl
割り当てます。
Angular2で良いことの1つは、observablesと対話するための多くのツールを提供することです。あなたは以前に非同期パイプを見てきましたが、今ではformControl
入力の値にobservableを持つことを可能にするクラスが表示されます。 これにより、filterコンポーネントで行ったことのような派手なことが可能になります。ご覧のとおり、Rx.observable.merge
formControls
filter
filter
filter
関数を呼び出します。
それは素晴らしいではありませんか?
すべてのコードは数行で実行されます。 これはあなたがRxJSを愛する理由の一つです。 それはあなたがそうでなければもっと複雑になっていたであろうそれらの派手なことの多くを簡単にすることを可能にします。
次に、そのフィルタ関数にステップしてみましょう。 それは何をしますか?
名前と電子メールの値を持つUPDATE_FILTER
アクションを単にディスパッチし、レデューサーはその情報で状態を変更します。より興味深いものに移りましょう。
そのフィルタを以前に作成したfreelancerグリッドとどのように相互作用させるのですか?
シンプルです。 あなただけの店のフィルタ部分に耳を傾ける必要があります。 コードがどのように見えるか見てみましょう。p>
import { Component, OnInit } from '@angular/core';import { Store } from '@ngrx/store';import { AppState, IFreelancer, ACTIONS } from './freelancer-reducer';import { IFilter, ACTIONS as FilterACTIONS } from './../filter/filter-reducer';import * as Rx from 'RxJS';@Component({ selector: 'app-freelancer-grid', templateUrl: './freelancer-grid.component', styleUrls: ,})export class FreelancerGridComponent implements OnInit { public freelancers: Rx.Observable<Array<IFreelancer>>; public filter: Rx.Observable<IFilter>; constructor(private store: Store<AppState>) { this.freelancers = Rx.Observable.combineLatest(store.select('freelancers'), store.select('filter'), this.applyFilter); } applyFilter(freelancers: Array<IFreelancer>, filter: IFilter): Array<IFreelancer> { return freelancers .filter(x => !filter.name || x.name.toLowerCase().indexOf(filter.name.toLowerCase()) !== -1) .filter(x => !filter.email || x.email.toLowerCase().indexOf(filter.email.toLowerCase()) !== -1) } ngOnInit() { } delete(freelancer) { this.store.dispatch({ type: ACTIONS.DELETE_FREELANCER, payload: freelancer, }) }}
それよりも複雑ではありません。
もう一度、rxjsのパワーを使用してフィルタとフリーランサーの状態を組み合わせました。実際には、combineLatest
applyFilter
関数を使用して各状態を結合する場合に起動します。 そうする新しいobservableを返します。 他のコード行を変更する必要はありません。
コンポーネントは、フィルターの取得、変更、または保存の方法を気にせず、他の状態と同様にリッスンするだけです。 フィルタ機能を追加したばかりで、新しい依存関係は追加しませんでした。
それを輝かせる
リアルタイムデータを処理する必要があるとき、Ngrxの使用は本当に輝いていることを覚えていますか? その部分をアプリケーションに追加して、それがどのようになるかを見てみましょう。p>
freelancers-service
を導入します。
ng generate service freelancer
フリーランサーサービスは、データ上のリアルタイム操作をシミュレートし、このようになります。
import { Injectable } from '@angular/core';import { Store } from '@ngrx/store';import { AppState, IFreelancer, ACTIONS } from './freelancer-grid/freelancer-reducer';import { Http, Response } from '@angular/http';@Injectable()export class RealtimeFreelancersService { private USER_API_URL = 'https://randomuser.me/api/?results=' constructor(private store: Store<AppState>, private http: Http) { } private toFreelancer(value: any) { return { name: value.name.first + ' ' + value.name.last, email: value.email, thumbail: value.picture.large, } } private random(y) { return Math.floor(Math.random() * y); } public run() { this.http.get(`${this.USER_API_URL}51`).subscribe((response) => { this.store.dispatch({ type: ACTIONS.FREELANCERS_LOADED, payload: response.json().results.map(this.toFreelancer) }) }) setInterval(() => { this.store.select('freelancers').first().subscribe((freelancers: Array<IFreelancer>) => { let getDeletedIndex = () => { return this.random(freelancers.length - 1) } this.http.get(`${this.USER_API_URL}${this.random(10)}`).subscribe((response) => { this.store.dispatch({ type: ACTIONS.INCOMMING_DATA, payload: { ADD: response.json().results.map(this.toFreelancer), DELETE: new Array(this.random(6)).fill(0).map(() => getDeletedIndex()), } }); this.addFadeClassToNewElements(); }); }); }, 10000); } private addFadeClassToNewElements() { let elements = window.document.getElementsByClassName('freelancer'); for (let i = 0; i < elements.length; i++) { if (elements.item(i).className.indexOf('fade') === -1) { elements.item(i).classList.add('fade'); } } }}
このサービスは完璧ではありませんが、それはそれが何をするかを行い、デモ目的のために、それは私たちがいくつかのことを実証す
まず、このサービスは非常に簡単です。 これは、ユーザー APIを照会し、ストアに結果をプッシュします。 それは非常に簡単ではなく、データがどこに行くのかを考える必要はありません。 それは同時にReduxがとても便利で危険なものである店に行く—しかし、我々は後でこれに戻ってくるでしょう。 十秒ごとに、サービスはいくつかのフリーランサーを選択し、いくつかの他のフリーランサーに操作と一緒にそれらを削除する操作を送信します。
減速機がそれを処理できるようにしたい場合は、それを変更する必要があります。
import { Action } from '@ngrx/store';export interface AppState { freelancers : Array<IFreelancer>}export interface IFreelancer { name: string, email: string,}export const ACTIONS = { LOAD_FREELANCERS: 'LOAD_FREELANCERS', INCOMMING_DATA: 'INCOMMING_DATA', DELETE_FREELANCER: 'DELETE_FREELANCER',}export function freelancersReducer( state: Array<IFreelancer> = , action: Action): Array<IFreelancer> { switch (action.type) { case ACTIONS.INCOMMING_DATA: action.payload.DELETE.forEach((index) => { state.splice(state.indexOf(action.payload), 1); }) return Array.prototype.concat(action.payload.ADD, state); case ACTIONS.FREELANCERS_LOADED: // Return the new state with the payload as freelancers list return Array.prototype.concat(action.payload); case ACTIONS.DELETE_FREELANCER: // Remove the element from the array state.splice(state.indexOf(action.payload), 1); // We need to create another reference return Array.prototype.concat(state); default: return state; }}
今、私たちはそのような操作を処理できます。
そのサービスで実証されていることの一つは、状態変更のすべてのプロセスが同期的に行われていることですが、それに気づくことは非常に重要 状態のアプリケーションが非同期であった場合、この関数が呼び出されたときにDOM要素が作成されないため、this.addFadeClassToNewElements();
の呼び出しは機能個人的には、予測可能性を向上させるため、非常に便利です。
個人的には、予測可能性を向上させるため、非常に便利です。
アプリケーションの構築、リアクティブな方法
このチュートリアルでは、Ngrx、RxJS、およびAngular2を使用してリアクティブアプリケーションを構築しました。あなたが見てきたように、これらは強力なツールです。
あなたが見たように、これらは強力なツールです。 ここで構築したものは、Reduxアーキテクチャの実装と見ることもでき、Redux自体は強力です。 しかし、それにはいくつかの制約もあります。 Ngrxを使用している間、これらの制約は必然的に使用するアプリケーションの一部に反映されます。
上の図は、あなたがやったアいくつかのコンポーネントが相互に影響を与えていても、それらは互いに独立していることに気付くかもしれません。
コンポーネントは共通の依存関係、つまりストアを共有しています。
このアーキテクチャに関するもう一つの特別なことは、関数を呼び出すのではなく、アクションをディスパッチすることです。 Ngrxの代わりに、アプリケーションのオブザーバブルで特定の状態を管理するサービスを作成し、アクションの代わりにそのサービスの関数を呼び出すこと このようにして、問題のある状態を隔離しながら、状態の集中化と反応性を得ることができます。 このアプローチは、リデューサーを作成するオーバーヘッドを削減し、アクションをプレーンなオブジェクトとして記述するのに役立ちます。