Ngrx a Angular 2 Tutorial: budování reaktivní aplikace
mluvíme hodně o reaktivním programování v Úhlové sféře. Zdá se, že reaktivní programování a Angular 2 jdou ruku v ruce. Pro každého, kdo není obeznámen s oběma technologiemi, však může být docela skličující úkol zjistit, o co jde.
v tomto článku se pomocí vytváření reaktivní aplikace Angular 2 pomocí Ngrx dozvíte, co je vzor, kde se vzor může ukázat jako užitečný a jak lze vzor použít k vytvoření lepších aplikací Angular 2.
Ngrx je skupina úhlových knihoven pro reaktivní rozšíření. Ngrx / Store implementuje vzor Redux pomocí známých RxJS pozorovatelných úhlových 2. Poskytuje několik výhod tím, že zjednodušuje stav aplikace na obyčejné objekty, prosazuje jednosměrný tok dat a další. Knihovna Ngrx / Effects umožňuje aplikaci komunikovat s okolním světem spuštěním vedlejších účinků.
co je reaktivní programování?
reaktivní programování je termín, který v dnešní době hodně slyšíte, ale co to vlastně znamená?
reaktivní programování je způsob, jakým aplikace zpracovávají události a tok dat ve vašich aplikacích. V reaktivním programování navrhujete své komponenty a další části softwaru, abyste reagovali na tyto změny místo toho, abyste žádali o změny. To může být velký posun.
skvělý nástroj pro reaktivní programování, jak možná víte, je RxJS.
poskytováním pozorovatelných a mnoha operátorů pro transformaci příchozích dat vám tato knihovna pomůže zvládnout události ve vaší aplikaci. Ve skutečnosti, s pozorovatelnými, můžete vidět událost jako proud událostí a ne jednorázovou událost. To vám umožní kombinovat je například k vytvoření nové události, na kterou budete poslouchat.
reaktivní programování je posun ve způsobu komunikace mezi různými částmi aplikace. Namísto tlačení dat přímo na komponentu nebo službu, která je potřebovala, v reaktivním programování reaguje na změny dat komponenta nebo služba.
Slovo o Ngrx
abychom pochopili aplikaci, kterou bude stavět prostřednictvím tohoto kurzu, musíte se rychle ponořit do jádra Redux pojmy.
Store
obchod lze považovat za databázi na straně klienta, ale co je důležitější, odráží stav vaší aplikace. Můžete to vidět jako jediný zdroj pravdy.
je to jediná věc, kterou změníte, když budete postupovat podle vzoru Redux a upravíte jej odesláním akcí.
reduktor
reduktory jsou funkce, které vědí, co dělat s danou akcí a předchozím stavem aplikace.
reduktory vezmou předchozí stav z vašeho obchodu a použijí na něj čistou funkci. Pure znamená, že funkce vždy vrací stejnou hodnotu pro stejný vstup a že nemá žádné vedlejší účinky. Z výsledku této čisté funkce budete mít nový stav, který bude vložen do vašeho obchodu.
akce
akce jsou užitečné zatížení, které obsahuje potřebné informace pro změnu vašeho obchodu. V zásadě má akce typ a užitečné zatížení, které vaše funkce reduktoru změní stav.
Dispečer
dispečeři jsou jednoduše vstupní bod pro odeslání vaší akce. V Ngrx existuje způsob odeslání přímo v obchodě.
Middleware
Middleware jsou některé funkce, které zachytí každou odeslanou akci za účelem vytvoření vedlejších účinků, i když je v tomto článku nebudete používat. Jsou implementovány v knihovně Ngrx/Effect a existuje velká šance, že je budete potřebovat při vytváření aplikací v reálném světě.
Proč používat Ngrx?
složitost
úložiště a jednosměrný tok dat výrazně snižují spojení mezi částmi vaší aplikace. Tato snížená vazba snižuje složitost vaší aplikace, protože každá část se stará pouze o konkrétní stavy.
Nástroje
celý stav aplikace je uložen v jednom místě, takže je snadné mít globální pohled na vaši žádost státu a pomáhá při vývoji. S Reduxem také přichází spousta pěkných Dev nástrojů, které využívají úložiště a mohou pomoci reprodukovat určitý stav aplikace nebo například cestovat v čase.
architektonická jednoduchost
mnoho výhod ngrx je dosažitelných s jinými řešeními; koneckonců, Redux je architektonický vzor. Ale když musíte vytvořit aplikaci, která se skvěle hodí pro vzor Redux, jako jsou nástroje pro spolupráci, můžete snadno přidat funkce podle vzoru.
i když nemusíte přemýšlet o tom, co děláte, přidání některých věcí, jako je analytika ve všech aplikacích, se stává triviální, protože můžete sledovat všechny odeslané akce.
malá křivka učení
vzhledem k tomu, že tento vzor je tak široce přijímaný a jednoduchý, je pro nové lidi ve vašem týmu opravdu snadné rychle dohnat to, co jste udělali.
Ngrx svítí nejvíce, když máte mnoho externích aktérů, kteří mohou upravit vaši aplikaci, například monitorovací panel. V těchto případech je obtížné spravovat všechna příchozí data, která jsou tlačena do vaší aplikace, a správa státu se stává obtížným. To je důvod, proč to chcete zjednodušit neměnným stavem,a to je jedna věc, kterou nám poskytuje obchod Ngrx.
vytváření aplikace s Ngrx
síla Ngrx svítí nejvíce, když máte vnější data, která jsou tlačena do naší aplikace v reálném čase. S ohledem na to vybudujme jednoduchou síť freelancerů, která zobrazuje online freelancery a umožňuje vám je filtrovat.
Nastavení projektu
Úhlové CLI je úžasný nástroj, který výrazně zjednodušuje proces nastavení. Možná budete chtít nepoužívat, ale mějte na paměti, že zbytek tohoto článku bude používat.
npm install -g @angular/cli
Další, chcete-li vytvořit novou aplikaci a nainstalovat všechny Ngrx knihoven:
ng new toptal-freelancersnpm install ngrx --save
na volné noze Redukce
Přechodky jsou základní kus Redux architektury, tak proč ne začít nejdřív s nimi, zatímco budování aplikace?
nejprve vytvořte reduktor „freelancers“, který bude zodpovědný za vytvoření našeho nového stavu při každém odeslání akce do obchodu.
freelancer-grid / freelancers.reduktor.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; }}
takže tady je náš reduktor na volné noze.
Tato funkce bude volána pokaždé, když je akce odeslána přes obchod. Pokud je akce FREELANCERS_LOADED
, vytvoří nové pole z užitečného zatížení akce. Pokud tomu tak není, vrátí starý státní odkaz a nic nebude připojeno.
je důležité si uvědomit, že pokud je vrácen Starý odkaz na stav, bude stav považován za nezměněný. To znamená, že pokud zavoláte state.push(something)
, stav nebude považován za změněný. Mějte to na paměti při provádění funkcí reduktoru.
stavy jsou neměnné. Nový stav musí být vrácen pokaždé, když se změní.
Freelancer Grid Component
Vytvořte komponentu mřížky pro zobrazení našich online freelancerů. Zpočátku to bude odrážet pouze to, co je v obchodě.
ng generate component freelancer-grid
vložte následující do freelancer-grid.součást.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'); }}
a následující v freelancer-grid.součást.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>
tak co jste právě udělali?
nejprve jste vytvořili novou komponentu s názvem freelancer-grid
.
komponenta obsahuje vlastnost s názvem freelancers
, která je součástí stavu aplikace obsaženého v úložišti Ngrx. Pomocí operátoru select se rozhodnete, že budete upozorněni pouze vlastností freelancers
celkového stavu aplikace. Takže teď pokaždé, když se změní freelancers
vlastnost stavu aplikace, bude váš pozorovatelný upozorněn.
Jedna věc, která je krásná s tímto řešením je, že komponenta má jen jednu závislost, a to je obchod, který dělá vaše složka mnohem méně komplexní a snadno opakovaně použitelný.
v části šablony jste neudělali nic příliš složitého. Všimněte si použití asynchronního potrubí v *ngFor
freelancers
observable není přímo iterable, ale díky Angular máme nástroje pro rozbalení a vázání dom na jeho hodnotu pomocí asynchronní trubky. Díky tomu je práce s pozorovatelným mnohem snazší.
přidání funkce Remove Freelancers
Nyní, když máte funkční základnu, přidejte do aplikace některé akce.
chcete mít možnost odebrat nezávislého pracovníka ze státu. Podle toho, jak Redux funguje, musíte nejprve definovat tuto akci v každém stavu, který je ovlivněn.
V tomto případě, je to jen freelancers
redukce:
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; }}
To je opravdu důležité vytvořit nové pole z starých jeden aby měl nový neměnný stav.
Nyní, můžete přidat a odstranit volné noze funkci komponentu, který bude odeslání této akce na prodejně:
delete(freelancer) { this.store.dispatch({ type: ACTIONS.DELETE_FREELANCER, payload: freelancer, }) }
Nevypadá to jednoduché?
nyní můžete odebrat konkrétního nezávislého pracovníka ze státu a tato změna se bude šířit prostřednictvím vaší aplikace.
Co když do aplikace přidáte další komponentu, abyste zjistili, jak mohou vzájemně komunikovat prostřednictvím obchodu?
reduktor filtru
jako vždy začněme s redukcí. Pro tuto komponentu je to docela jednoduché. Chcete, aby reduktor vždy vrátil nový stát pouze s majetkem, který jsme odeslali. To by vypadá takto:
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; }}
Filtr Složka
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, }) }}
za Prvé, že jste udělali jednoduché šablony, která obsahuje formulář s dvěma poli (jméno a e-mail), který odráží náš stát.
udržujete tato pole v synchronizaci se stavem trochu jinak, než co jste udělali se stavem freelancers
. Ve skutečnosti, jak jste viděli, jste se přihlásili k odběru stavu filtru a pokaždé, když se spustí, přiřadíte novou hodnotu formControl
.
jedna věc, která je pěkná s Angular 2 je, že vám poskytuje spoustu nástrojů pro interakci s pozorovatelnými.
viděli jste asynchronní potrubí dříve a nyní vidíte třídu formControl
, která vám umožní mít pozorovatelnou hodnotu vstupu. To umožňuje efektní věci, jako to, co jste udělali v komponentě filtru.
Jak můžete vidět, můžete použít Rx.observable.merge
spojit dvě rozpoznatelnosti dána tím formControls
, a pak debounce, že nové pozorovatelné před spuštěním filter
funkce.
V jednodušších slov, počkejte jednu sekundu po buď jméno nebo e-mail formControl
změnily, a pak volat filter
funkce.
není to úžasné?
to vše se provádí v několika řádcích kódu. To je jeden z důvodů, proč budete milovat RxJS. To vám umožní snadno dělat spoustu těch fantazijních věcí, které by jinak byly komplikovanější.
nyní přejděme k této funkci filtru. Co to dělá?
jednoduše odešle akci UPDATE_FILTER
s hodnotou jména a e-mailu a reduktor se postará o změnu stavu těmito informacemi.
pojďme k něčemu zajímavějšímu.
Jak si udělat, aby tento filtr komunikovat s vaší dříve vytvořené freelancer sítě?
jednoduché. Stačí poslouchat filtrační část obchodu. Uvidíme, jak ten kód vypadá.
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, }) }}
není to složitější.
opět jste použili sílu RxJS ke spojení stavu filtru a freelancerů.
ve skutečnosti, combineLatest
bude střílet, pokud jeden ze dvou pozorovatelných oheň a pak kombinovat každý stav pomocí funkce applyFilter
. Vrací nový pozorovatelný, který tak činí. Nemusíme měnit žádné další řádky kódu.
Všimněte si, že komponenta se nestará o to, jak je filtr získán, upraven nebo uložen; poslouchá jej pouze jako v jakémkoli jiném stavu. Právě jsme přidali funkci filtru a nepřidali jsme žádné nové závislosti.
díky tomu svítí
nezapomeňte, že použití Ngrx opravdu svítí, když se musíme vypořádat s daty v reálném čase? Přidejme tuto část do naší aplikace a uvidíme, jak to půjde.
Představujeme freelancers-service
.
ng generate service freelancer
služba freelancer bude simulovat provoz v reálném čase na datech a měla by vypadat takto.
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'); } } }}
tato služba není dokonalá, ale dělá to, co dělá, a pro demo účely nám umožňuje demonstrovat několik věcí.
nejprve je tato služba poměrně jednoduchá. Dotazuje uživatelské rozhraní API a tlačí výsledky do obchodu. Je to ne-nasnadě, a nemusíte přemýšlet o tom, kam data jdou. Jde to do obchodu, což je něco, co dělá Redux tak užitečným a nebezpečným zároveň—ale k tomu se vrátíme později. Po každých deseti sekundách, služba vybere málo na volné noze a posílá operace je odstranit spolu s operací na několik dalších nezávislých.
Pokud chceme, aby naše reduktoru být schopni to zvládnout, musíme ho upravit:
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; }}
Nyní jsme schopni zvládnout tyto operace.
Jedna věc, která je prokázána v této službě je, že celý proces změn stavu provádí synchronně, to je docela důležité upozornění. Pokud by aplikace stavu byla asynchronní, volání na this.addFadeClassToNewElements();
by nefungovalo, protože prvek DOM by nebyl vytvořen při volání této funkce.
osobně to považuji za docela užitečné, protože to zlepšuje předvídatelnost.
stavební aplikace, reaktivní způsob
prostřednictvím tohoto tutoriálu jste vytvořili reaktivní aplikaci pomocí Ngrx, RxJS a Angular 2.
jak jste viděli, jedná se o výkonné nástroje. To, co jste zde vytvořili, lze také považovat za implementaci architektury Redux a Redux je sám o sobě silný. Má však také určitá omezení. Zatímco používáme Ngrx, tato omezení se nevyhnutelně odrážejí v části naší aplikace, kterou používáme.
výše uvedený diagram je hrubý architektury, které jste právě udělal.
můžete si všimnout, že i když se některé komponenty navzájem ovlivňují, jsou na sobě nezávislé. To je zvláštnost této architektury: komponenty sdílejí společnou závislost, což je úložiště.
Další konkrétní věcí na této architektuře je, že nevoláme funkce, ale odesíláme akce. Alternativou k Ngrx může být pouze služba, která spravuje konkrétní stát s rozpoznatelnosti své aplikace a funkce volání na servisní místo akce. Tímto způsobem byste mohli získat centralizaci a reaktivitu státu při izolaci problematického stavu. Tento přístup vám může pomoci snížit režii vytváření reduktoru a popsat akce jako prosté objekty.