Angular領域でのリアクティブプログラミングについて多くのことを話します。リアクティブプログラミングとAngular2は密接に関連しているようです。ただし、両方のテクノロジーに精通していない人にとっては、それが何であるかを理解することは非常に困難な作業になる可能性があります。
この記事では、 リアクティブAngular2アプリケーション Ngrxを使用して、パターンとは何か、パターンが有用であることが証明できる場所、およびパターンを使用してより優れたAngular2アプリケーションを構築する方法を学習します。
Ngrxは、リアクティブ拡張用のAngularライブラリのグループです。 Ngrx / Storeは、Angular 2のよく知られたRxJSオブザーバブルを使用してReduxパターンを実装します。これは、アプリケーションの状態をプレーンオブジェクトに単純化し、単方向のデータフローを適用するなど、いくつかの利点を提供します。 Ngrx / Effectsライブラリを使用すると、アプリケーションは副作用をトリガーして外界と通信できます。
リアクティブプログラミングは最近よく耳にする用語ですが、実際にはどういう意味ですか?
リアクティブプログラミングは、アプリケーションがアプリケーション内のイベントとデータフローを処理する方法です。リアクティブプログラミングでは、変更を要求するのではなく、それらの変更に対応するために、コンポーネントやソフトウェアの他の部分を設計します。これは大きな変化になる可能性があります。
ご存知かもしれませんが、リアクティブプログラミングに最適なツールはRxJSです。
このライブラリは、受信データを変換するためのオブザーバブルと多くの演算子を提供することにより、アプリケーションでイベントを処理するのに役立ちます。実際、オブザーバブルを使用すると、イベントを1回限りのイベントではなく、イベントのストリームとして表示できます。これにより、たとえば、それらを組み合わせて、リッスンする新しいイベントを作成できます。
リアクティブプログラミングは、アプリケーションのさまざまな部分の間で通信する方法の変化です。リアクティブプログラミングでは、データを必要とするコンポーネントまたはサービスに直接データをプッシュするのではなく、データの変更に反応するのはコンポーネントまたはサービスです。
このチュートリアルで作成するアプリケーションを理解するには、Reduxのコアコンセプトを簡単に理解する必要があります。
お店
ストアはクライアント側のデータベースと見なすことができますが、さらに重要なことは、アプリケーションの状態を反映していることです。あなたはそれを唯一の正しい情報源として見ることができます。
Reduxパターンに従い、アクションをディスパッチして変更する場合は、これだけを変更します。
レデューサー
レデューサーは、特定のアクションとアプリの以前の状態をどう処理するかを知っている関数です。
レデューサーはストアから以前の状態を取得し、それに純粋関数を適用します。純粋とは、関数が同じ入力に対して常に同じ値を返し、副作用がないことを意味します。その純粋関数の結果から、ストアに配置される新しい状態が得られます。
行動
アクションは、ストアを変更するために必要な情報を含むペイロードです。基本的に、アクションには、リデューサー関数が状態を変更するために実行するタイプとペイロードがあります。
発車係
ディスパッチャは、アクションをディスパッチするための単なるエントリポイントです。 Ngrxには、ストアに直接ディスパッチする方法があります。
Javaメモリリーク検出ツール
ミドルウェア
ミドルウェアは、この記事では使用しませんが、副作用を作成するためにディスパッチされている各アクションをインターセプトするいくつかの関数です。これらはNgrx / Effectライブラリに実装されており、実際のアプリケーションを構築する際に必要になる可能性が高くなります。
複雑
ストアと単方向のデータフローにより、アプリケーションのパーツ間の結合が大幅に減少します。各部分は特定の状態のみを考慮しているため、この結合の減少により、アプリケーションの複雑さが軽減されます。
ツーリング
アプリケーションの状態全体が1つの場所に保存されるため、アプリケーションの状態をグローバルに表示するのは簡単で、開発中に役立ちます。また、Reduxには、ストアを利用して、アプリケーションの特定の状態を再現したり、タイムトラベルを作成したりするのに役立つ多くの優れた開発ツールが付属しています。
アーキテクチャのシンプルさ
Ngrxの利点の多くは、他のソリューションで実現できます。結局のところ、Reduxはアーキテクチャパターンです。しかし、次のようなアプリケーションを構築する必要がある場合 Reduxパターンに最適 共同編集ツールなど、パターンに従うことで簡単に機能を追加できます。
何をしているのかを考える必要はありませんが、ディスパッチされたすべてのアクションを追跡できるため、すべてのアプリケーションに分析などを追加するのは簡単です。
小さな学習曲線
このパターンは非常に広く採用されており、シンプルであるため、チームの新しい人々があなたのしたことにすばやく追いつくのは本当に簡単です。
Ngrxは、監視ダッシュボードなど、アプリケーションを変更できる外部アクターが多数ある場合に最も効果的です。このような場合、アプリケーションにプッシュされるすべての受信データを管理することは困難であり、状態管理は困難になります。そのため、不変の状態で単純化する必要があります。これは、Ngrxストアが提供するものの1つです。
Ngrxのパワーは、アプリケーションにリアルタイムでプッシュされている外部データがある場合に最も効果的です。それを念頭に置いて、オンラインのフリーランサーを表示し、それらをフィルタリングできる簡単なフリーランサーグリッドを作成しましょう。
Angular CLIは、セットアッププロセスを大幅に簡素化する素晴らしいツールです。使用したくない場合もありますが、この記事の残りの部分では使用することに注意してください。
npm install -g @angular/cli
次に、新しいアプリケーションを作成し、すべてのNgrxライブラリをインストールします。
ng new toptal-freelancers npm install ngrx --save
レデューサーはReduxアーキテクチャのコア部分なので、アプリケーションを構築する際に最初にレデューサーから始めてみませんか?
まず、アクションがストアにディスパッチされるたびに新しい状態を作成する責任がある「フリーランサー」レデューサーを作成します。
freelancer-grid / freelancers.reducer.ts
国境調整税とは
import { Action } from '@ngrx/store'; export interface AppState { freelancers : Array } export interface IFreelancer { name: string, email: string, thumbnail: string } export const ACTIONS = { FREELANCERS_LOADED: 'FREELANCERS_LOADED', } export function freelancersReducer( state: Array = [], action: Action): Array { 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)
を呼び出した場合、状態が変更されたとは見なされないことを意味します。レデューサー機能を実行するときは、このことを覚えておいてください。
状態は不変です。変更するたびに新しい状態を返す必要があります。
オンラインフリーランサーを表示するグリッドコンポーネントを作成します。最初は、ストアにあるものだけが反映されます。
ng generate component freelancer-grid
以下を入れて フリーランサー-grid.component.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: ['./freelancer-grid.component.scss'], }) export class FreelancerGridComponent implements OnInit { public freelancers: Rx.Observable ; constructor(private store: Store) { this.freelancers = store.select('freelancers'); } }
そして次の freelancer-grid.component.html :
Number of freelancers online: {(freelancers }
Name: {{freelancer.name}} Email: {{freelancer.email}} Hire {{freelancer.name}}
それで、あなたはただ何をしましたか?
まず、freelancer-grid
という新しいコンポーネントを作成しました。
コンポーネントには、freelancers
という名前のプロパティが含まれていますこれは、Ngrxストアに含まれるアプリケーション状態の一部です。 select演算子を使用すると、freelancers
によってのみ通知されるように選択できます。アプリケーション全体の状態のプロパティ。だから今度はfreelancers
アプリケーション状態のプロパティが変更されると、監視対象に通知されます。
このソリューションの優れている点の1つは、コンポーネントの依存関係が1つしかないことです。これにより、コンポーネントの複雑さが大幅に軽減され、簡単に再利用できるようになります。
テンプレートの部分では、複雑すぎることは何もしていません。 *ngFor
で非同期パイプが使用されていることに注意してください。 freelancers
observableは直接反復可能ではありませんが、Angularのおかげで、非同期パイプを使用して、それをアンラップし、domをその値にバインドするツールがあります。これにより、オブザーバブルの操作が非常に簡単になります。
機能ベースができたので、アプリケーションにいくつかのアクションを追加しましょう。
フリーランサーを州から削除できるようにしたい。 Reduxの動作に応じて、最初に、影響を受ける各状態でそのアクションを定義する必要があります。
この場合、それはfreelancers
だけです。レデューサー:
export const ACTIONS = { FREELANCERS_LOADED: 'FREELANCERS_LOADED', DELETE_FREELANCER: 'DELETE_FREELANCER', } export function freelancersReducer( state: Array = [], action: Action): Array { 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(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: ''+ 'Name'+ ''+ 'Email'+ ''+ ' Clear Filter '+ '', styleUrls: ['./filter.component.scss'], }) export class FilterComponent implements OnInit { public name = new FormControl(); public email = new FormControl(); constructor(private store: Store) { 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, }) } }
まず、状態を反映する2つのフィールド(名前と電子メール)を持つフォームを含む単純なテンプレートを作成しました。
freelancers
で行ったのとはかなり異なる方法で、これらのフィールドを状態と同期させます。状態。実際、これまで見てきたように、フィルター状態をサブスクライブし、そのたびに、formControl
に新しい値を割り当てるようにトリガーします。
Angular 2の優れた点の1つは、オブザーバブルと対話するための多くのツールを提供することです。
以前に非同期パイプを見たことがありますが、今はformControl
が表示されています。入力の値を監視できるようにするクラス。これにより、フィルターコンポーネントで行ったような凝ったことが可能になります。
ご覧のとおり、Rx.observable.merge
を使用しますformControls
によって指定された2つのオブザーバブルを結合し、filter
をトリガーする前にその新しいオブザーバブルをデバウンスします。関数。
簡単に言うと、名前またはメールアドレスのいずれかが表示されてから1秒待ちますformControl
変更してからfilter
を呼び出します関数。
すごいじゃないですか?
クラウドベースのサービスにサブスクライブして、データを同期しました
これらはすべて、数行のコードで実行されます。これが、RxJSを気に入る理由の1つです。それはあなたがそうでなければもっと複雑だったであろうそれらの素晴らしいことの多くを簡単に行うことを可能にします。
それでは、そのフィルター機能に移りましょう。それは何をするためのものか?
UPDATE_FILTER
をディスパッチするだけです名前と電子メールの値を使用してアクションを実行すると、レデューサーがその情報を使用して状態を変更します。
もっと面白いことに移りましょう。
そのフィルターを以前に作成したフリーランサーグリッドとどのように相互作用させますか?
シンプル。あなたは店のフィルター部分を聞く必要があるだけです。コードがどのように見えるか見てみましょう。
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: ['./freelancer-grid.component.scss'], }) export class FreelancerGridComponent implements OnInit { public freelancers: Rx.Observable ; public filter: Rx.Observable; constructor(private store: Store) { this.freelancers = Rx.Observable.combineLatest(store.select('freelancers'), store.select('filter'), this.applyFilter); } applyFilter(freelancers: Array, filter: IFilter): Array return freelancers .filter(x => !filter.name ngOnInit() { } delete(freelancer) { this.store.dispatch({ type: ACTIONS.DELETE_FREELANCER, payload: freelancer, }) } }
それ以上に複雑ではありません。
そこに、一人で、私は
ここでも、RxJSの機能を使用して、フィルターとフリーランサーの状態を組み合わせました。
実際、combineLatest
2つのオブザーバブルのいずれかが起動すると起動し、applyFilter
を使用して各状態を結合します関数。そうする新しいオブザーバブルを返します。他のコード行を変更する必要はありません。
コンポーネントが、フィルターの取得、変更、または保存の方法を気にしないことに注意してください。他の状態の場合と同じように、それをリッスンするだけです。フィルタ機能を追加しただけで、新しい依存関係は追加しませんでした。
リアルタイムデータを処理する必要がある場合、Ngrxの使用が本当に優れていることを覚えていますか?その部分をアプリケーションに追加して、どのように機能するかを見てみましょう。
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, 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) => { 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 このサービスは完璧ではありませんが、それが行うことを実行し、デモの目的で、いくつかのことを示すことができます。
まず、このサービスは非常に簡単です。ユーザーAPIにクエリを実行し、結果をストアにプッシュします。簡単で、データの行き先を考える必要はありません。それは店に行きます、それはReduxをとても便利で危険なものにするものです-しかし私たちは後でこれに戻ります。 10秒ごとに、サービスは数人のフリーランサーを選び、それらを削除する操作を他の数人のフリーランサーに送信します。
レデューサーで処理できるようにする場合は、次のように変更する必要があります。
import { Action } from '@ngrx/store'; export interface AppState { freelancers : Array } 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 = [], action: Action): Array { 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; } }
これで、このような操作を処理できるようになりました。
そのサービスで示されていることの1つは、状態変化のすべてのプロセスが同期的に実行される中で、それに注意することが非常に重要であるということです。状態の適用が非同期の場合、this.addFadeClassToNewElements();
の呼び出しこの関数が呼び出されたときにDOM要素が作成されないため、は機能しません。
個人的には、予測可能性が向上するため、非常に便利だと思います。
アプリケーションの構築、反応的な方法
このチュートリアルを通じて、 リアクティブアプリケーション Ngrx、RxJS、およびAngular2を使用します。
ご覧のとおり、これらは強力なツールです。ここで構築したものは、Reduxアーキテクチャの実装と見なすこともでき、Redux自体が強力です。ただし、いくつかの制約もあります。 Ngrxを使用している間、これらの制約は必然的に、使用するアプリケーションの一部に反映されます。

上の図は、先ほど行ったアーキテクチャの大まかなものです。
一部のコンポーネントが互いに影響し合っている場合でも、それらは互いに独立していることに気付くかもしれません。これは、このアーキテクチャの特徴です。コンポーネントは、ストアという共通の依存関係を共有します。
このアーキテクチャのもう1つの特別な点は、関数を呼び出すのではなく、アクションをディスパッチすることです。 Ngrxの代わりに、アプリケーションのオブザーバブルを使用して特定の状態を管理するサービスのみを作成し、アクションではなくそのサービスの関数を呼び出すこともできます。このようにして、問題のある状態を分離しながら、状態の集中化と反応性を得ることができます。このアプローチは、レデューサーを作成するオーバーヘッドを削減し、アクションをプレーンオブジェクトとして記述するのに役立ちます。
アプリケーションの状態がさまざまなソースから更新されているように感じ、それが混乱し始めた場合、Ngrxが必要です。
関連: すべての特典、手間なし:Angular9チュートリアル 基本を理解する
リアクティブプログラミングとは何ですか?
リアクティブプログラミングは、アプリケーションのさまざまな部分が相互に通信する方法の変化です。リアクティブプログラミングでは、データを必要とするコンポーネントまたはサービスに直接データをプッシュするのではなく、データの変更に反応するのはコンポーネントまたはサービスです。
Ngrxとは何ですか?
Ngrxは、リアクティブ拡張用のAngularライブラリのセットです。 2つの人気のあるNgrxライブラリは、Angular2のよく知られたRxJSオブザーバブルを使用したReduxパターンの実装であるNgrx / Storeと、アプリケーションが副作用をトリガーすることで外界と通信できるようにするライブラリであるNgrx / Effectsです。