Ngrx és Angular 2 Tutorial: épület egy reaktív alkalmazás
sokat beszélünk reaktív programozás Angular birodalmában. A reaktív programozás és az Angular 2 kéz a kézben járnak. Azonban, bárki számára, aki nem ismeri mindkét technológiát, elég ijesztő feladat lehet kitalálni, miről van szó.
ebben a cikkben egy reaktív Angular 2 alkalmazás felépítésével az Ngrx segítségével megtudhatja, mi a minta, ahol a minta hasznosnak bizonyulhat, és hogyan használható a minta jobb Angular 2 Alkalmazások felépítéséhez.
az Ngrx a reaktív kiterjesztések szögletes könyvtárainak csoportja. Ngrx / Store megvalósítja a Redux minta segítségével a jól ismert RxJS megfigyelhető Angular 2. Számos előnyt nyújt azáltal, hogy egyszerű objektumokra egyszerűsíti az alkalmazás állapotát, érvényesíti az egyirányú adatáramlást stb. Az Ngrx / Effects könyvtár lehetővé teszi az alkalmazás számára, hogy a mellékhatások kiváltásával kommunikáljon a külvilággal.
mi a reaktív programozás?
a reaktív programozás olyan kifejezés, amelyet manapság sokat hall, de mit jelent ez valójában?
a reaktív programozás egy módja annak, hogy az alkalmazások kezeljék az eseményeket és az adatáramlást az alkalmazásokban. A reaktív programozás során megtervezi a komponenseket és a szoftver egyéb részeit annak érdekében, hogy reagáljon ezekre a változásokra, ahelyett, hogy változtatásokat kérne. Ez nagy váltás lehet.
a reaktív programozás nagyszerű eszköze, amint azt Ön is tudja, az RxJS.
azáltal, hogy megfigyelhető és sok operátorok átalakítani a bejövő adatokat, ez a könyvtár segít kezelni események az alkalmazásban. Valójában a megfigyelhetőeknél az eseményt események áramlásaként tekintheti meg, nem pedig egyszeri eseményként. Ez lehetővé teszi, hogy összekapcsolja őket, például egy új esemény létrehozásához, amelyre hallgatni fog.
a reaktív programozás az alkalmazás különböző részei közötti kommunikáció elmozdulása. Ahelyett, hogy az adatokat közvetlenül a szükséges összetevőre vagy szolgáltatásra tolná, a reaktív programozásban az összetevő vagy szolgáltatás reagál az adatok változásaira.
egy szó az Ngrx-ről
annak érdekében, hogy megértsük az oktatóanyagon keresztül felépített alkalmazást, gyorsan be kell merülnie a Redux alapkoncepciókba.
Store
a boltban lehet tekinteni, mint a kliens oldali adatbázis, de ami még fontosabb, ez tükrözi az állam az alkalmazás. Úgy tekinthetsz rá, mint az igazság egyetlen forrására.
Ez az egyetlen dolog, amit megváltoztatsz, amikor követed a Redux mintát, és módosítod a műveletek elküldésével.
reduktor
a reduktorok azok a funkciók, amelyek tudják, mit kell tenni egy adott művelettel és az alkalmazás előző állapotával.
a reduktorok átveszik az előző állapotot a boltból, és tiszta funkciót alkalmaznak rá. A Pure azt jelenti, hogy a függvény mindig ugyanazt az értéket adja vissza ugyanarra a bemenetre, és nincs mellékhatása. Az eredmény, hogy a tiszta funkció, akkor egy új állapot, hogy kerül a boltban.
műveletek
műveletek az a hasznos adat, amely az áruház megváltoztatásához szükséges információkat tartalmazza. Alapvetően egy műveletnek van egy típusa és egy hasznos terhelése, amelyet a szűkítő funkció az állapot megváltoztatásához igényel.
diszpécser
a diszpécserek egyszerűen egy belépési pont az Ön számára a művelet elküldéséhez. Az Ngrx-ben van egy feladási módszer közvetlenül a boltban.
köztes szoftver
köztes szoftver néhány olyan funkció, amely elfogja az egyes műveleteket, amelyeket a mellékhatások létrehozása érdekében küldenek, annak ellenére, hogy nem fogja használni őket ebben a cikkben. Az Ngrx/Effect könyvtárban vannak megvalósítva, és nagy esély van arra, hogy szüksége lesz rájuk, miközben valós alkalmazásokat épít.
miért használja az Ngrx-et?
komplexitás
a tárolás és az egyirányú adatáramlás nagymértékben csökkenti az alkalmazás egyes részei közötti összekapcsolást. Ez a csökkentett kapcsolás csökkenti az alkalmazás összetettségét, mivel minden rész csak bizonyos állapotokkal foglalkozik.
szerszámozás
az alkalmazás teljes állapota egy helyen tárolódik, így könnyen áttekinthető az alkalmazás állapota, és segít a fejlesztés során. Ezenkívül A Redux segítségével sok szép dev eszköz található, amelyek kihasználják az áruház előnyeit, és segíthetnek például az alkalmazás bizonyos állapotának reprodukálásában vagy az időutazásban.
építészeti egyszerűség
az Ngrx számos előnye elérhető más megoldásokkal; végül is a Redux egy építészeti minta. De ha olyan alkalmazást kell létrehoznia, amely kiválóan illeszkedik a Redux mintához, például együttműködő szerkesztő eszközöket, akkor a minta követésével könnyen hozzáadhat funkciókat.
bár nem kell gondolkodnia azon, hogy mit csinál, néhány dolog, például az elemzés hozzáadása az összes alkalmazáshoz triviálissá válik, mivel nyomon követheti az összes elküldött műveletet.
kis tanulási görbe
mivel ez a minta olyan széles körben elfogadott és egyszerű, nagyon könnyű az új emberek a csapatban, hogy utolérjék gyorsan, amit tettél.
az Ngrx akkor ragyog a legjobban, ha sok külső szereplő van, akik módosíthatják az alkalmazást, például egy felügyeleti irányítópult. Ezekben az esetekben nehéz kezelni az összes beérkező adatot, amelyet az alkalmazásba tolnak, az Állami Menedzsment pedig nehézzé válik. Ezért szeretné egyszerűsíteni egy változatlan állapotban, és ez az egyik dolog, amit az Ngrx áruház biztosít számunkra.
épület egy alkalmazás Ngrx
a hatalom Ngrx ragyog a legjobban, ha van külső adatok, hogy tolják az alkalmazás valós időben. Ezt szem előtt tartva építsünk egy egyszerű szabadúszó rácsot, amely megmutatja az online szabadúszókat, és lehetővé teszi a szűrést rajtuk keresztül.
A Projekt beállítása
az Angular CLI egy fantasztikus eszköz, amely nagyban leegyszerűsíti a telepítési folyamatot. Lehet, hogy nem akarja használni, de ne feledje, hogy a cikk többi része használni fogja.
npm install -g @angular/cli
ezután új alkalmazást szeretne létrehozni és telepíteni az összes Ngrx könyvtárat:
ng new toptal-freelancersnpm install ngrx --save
Szabadúszók Reducer
A Redux architektúra alapvető elemei a Redux Reducerek, miért ne kezdhetnénk velük először az alkalmazás építése közben?
először hozzon létre egy “szabadúszók” szűkítőt, amely felelős az új állapot létrehozásáért minden alkalommal, amikor egy műveletet elküldünk a boltba.
szabadúszó-rács / szabadúszók.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; }}
tehát itt van a szabadúszók szűkítő.
Ez a funkció minden alkalommal meghívásra kerül, amikor egy műveletet az áruházon keresztül küldenek. Ha a művelet FREELANCERS_LOADED
, akkor új tömböt hoz létre a művelet hasznos adatából. Ha nem, akkor visszaadja a régi állapot hivatkozást, és semmi sem lesz csatolva.
itt fontos megjegyezni, hogy ha a régi állapothivatkozást visszaadják, az állapot változatlannak tekinthető. Ez azt jelenti, hogy ha hív egy state.push(something)
, az állapot nem tekinthető változottnak. Tartsa ezt szem előtt, miközben a szűkítő funkcióit végzi.
az állapotok változhatatlanok. Az új állapotot minden alkalommal vissza kell adni, amikor megváltozik.
Freelancer Grid Component
Hozzon létre egy grid komponenst, hogy megmutassa online szabadúszóinkat. Először csak azt tükrözi, ami a boltban van.
ng generate component freelancer-grid
tegye a következőket a freelancer-grid-be.alkatrész.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'); }}
és a következő freelancer-grid.alkatrész.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>
Szóval mit csináltál?
először létrehozott egy új összetevőt, amelynek nevefreelancer-grid
.
az összetevő egy freelancers
nevű tulajdonságot tartalmaz, amely az Ngrx áruházban található Alkalmazásállapot része. A select operátor használatával úgy dönt, hogy csak a teljes Alkalmazásállapot freelancers
tulajdonságával kap értesítést. Tehát most minden alkalommal, amikor az alkalmazás állapotának freelancers
tulajdonsága megváltozik, a megfigyelhető értesítést kap.
egy dolog, ami szép ezzel a megoldással, hogy az összetevőnek csak egy függősége van, és ez az áruház teszi az összetevőt sokkal kevésbé összetetté és könnyen újrafelhasználhatóvá.
a sablon részben nem tettél semmi túl bonyolultat. Figyelje meg az aszinkron cső használatát a *ngFor
alatt. Afreelancers
közvetlenül nem iterálható, de az Angular-nak köszönhetően megvan az eszközünk, hogy kicsomagoljuk és a dom értékét az aszinkron cső segítségével hozzákapcsoljuk. Ez sokkal könnyebbé teszi a megfigyelhető munkát.
a szabadúszók eltávolítása funkció hozzáadása
most, hogy van funkcionális alapja, adjunk hozzá néhány műveletet az alkalmazáshoz.
azt szeretné, hogy képes legyen eltávolítani a szabadúszó az állam. A Redux működésének megfelelően először meg kell határoznia ezt a műveletet minden olyan államban, amelyet érint.
ebben az esetben csak a freelancers
szűkítő:
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; }}
itt nagyon fontos, hogy új tömböt hozzunk létre a régiből, hogy új változhatatlan állapot legyen.
most hozzáadhat egy delete freelancers funkciót az összetevőhöz, amely elküldi ezt a műveletet a boltba:
delete(freelancer) { this.store.dispatch({ type: ACTIONS.DELETE_FREELANCER, payload: freelancer, }) }
nem tűnik egyszerűnek?
mostantól eltávolíthat egy adott szabadúszót az állapotból, és ez a változás az alkalmazáson keresztül terjed.
most mi van, ha hozzáad egy másik összetevőt az alkalmazáshoz, hogy megnézze, hogyan tudnak kölcsönhatásba lépni egymással az áruházon keresztül?
szűrő szűkítő
mint mindig, kezdjük a szűkítővel. Ehhez az összetevőhöz elég egyszerű. Azt akarja, hogy a reduktor mindig új állapotot adjon vissza, csak az általunk elküldött tulajdonsággal. Ennek így kell kinéznie:
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; }}
Szűrőösszetevő
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, }) }}
először egy egyszerű sablont készítettél, amely tartalmaz egy űrlapot két mezővel (név és e-mail), amely tükrözi állapotunkat.
ezeket a mezőket szinkronban tartja az állapottal egy kicsit másképp, mint amit afreelancers
állapotnál tett. Valójában, amint látta, feliratkozott a szűrőállapotra, és minden alkalommal, amikor kiváltja, az új értéket hozzárendeli a formControl
– hez.
az egyik dolog, ami szép az Angular 2-vel, az, hogy sok eszközt biztosít a megfigyelhető eszközökkel való interakcióhoz.
korábban már látta az aszinkron csövet, most pedig aformControl
osztályt, amely lehetővé teszi, hogy megfigyelhető legyen egy bemenet értéke. Ez olyan divatos dolgokat tesz lehetővé, mint amit a szűrőösszetevőben tett.
mint látható, a Rx.observable.merge
segítségével kombinálhatja a formControls
által megadott két megfigyelést, majd a filter
függvény aktiválása előtt lebontja ezt az új megfigyelést.
egyszerűbb szavakkal, várjon egy másodpercet, miután bármelyik név vagy e-mailformControl
megváltozott, majd hívja meg afilter
funkciót.
hát nem fantasztikus?
mindez néhány sornyi kódban történik. Ez az egyik oka annak, hogy szeretni fogja az RxJS-t. Ez lehetővé teszi, hogy sok olyan divatos dolgot könnyedén megtehessen, ami egyébként bonyolultabb lett volna.
most lépjünk erre a szűrőfunkcióra. Mire jó?
egyszerűen elküldi a UPDATE_FILTER
műveletet a név és az e-mail értékével, és a szűkítő gondoskodik az állapot megváltoztatásáról ezzel az információval.
térjünk át valami érdekesebbre.
hogyan lehet, hogy a szűrő kölcsönhatásba a korábban létrehozott szabadúszó rács?
egyszerű. Csak a bolt szűrő részét kell hallgatnia. Lássuk, hogy néz ki a kód.
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, }) }}
ennél nem bonyolultabb.
ismét az RxJS erejét használta a szűrő és a szabadúszók állapotának kombinálására.
valójában a combineLatest
akkor aktiválódik, ha a két megfigyelhető közül az egyik tüzet okoz, majd az egyes állapotokat a applyFilter
függvény segítségével kombinálja. Visszatér egy új megfigyelhető, hogy ezt. Nem kell megváltoztatnunk más kódsorokat.
figyelje meg, hogy az összetevő nem törődik azzal, hogy a szűrőt hogyan szerezzék be, módosítsák vagy tárolják; csak úgy hallgatja, mint bármely más állapot esetében. Most adtuk hozzá a szűrő funkciót, és nem adtunk hozzá új függőségeket.
ragyogása
ne feledje, hogy az Ngrx használata valóban ragyog, amikor valós idejű adatokkal kell foglalkoznunk? Adjuk hozzá ezt a részt az alkalmazásunkhoz, és nézzük meg, hogyan megy.
afreelancers-service
bemutatása.
ng generate service freelancer
a freelancer szolgáltatás valós idejű működést szimulál az adatokon, így kell kinéznie.
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'); } } }}
Ez a szolgáltatás nem tökéletes, de azt teszi, amit csinál, és demó célokra lehetővé teszi számunkra, hogy bemutassunk néhány dolgot.
először is, ez a szolgáltatás meglehetősen egyszerű. Lekérdezi a felhasználói API-t, és az eredményeket az áruházba tolja. Ez egy nem-agy, és nem kell gondolni, ahol az adatok megy. Ez megy a boltba, ami olyasmi, ami Redux annyira hasznos és veszélyes egyidejűleg—de visszatérünk erre később. Minden tíz másodperc után a szolgáltatás kiválaszt néhány szabadúszót, és küld egy műveletet, hogy törölje őket egy művelettel együtt néhány más szabadúszónak.
Ha azt akarjuk, hogy a szűkítőnk képes legyen kezelni, módosítanunk kell:
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; }}
most már képesek vagyunk kezelni az ilyen műveleteket.
az egyik dolog, amit ebben a szolgáltatásban bemutatunk, az, hogy az állapotváltozások szinkron folyamata közül ezt nagyon fontos észrevenni. Ha az állapot alkalmazása aszinkron volt, akkor a this.addFadeClassToNewElements();
hívás nem működne, mivel a Dom elem nem jön létre a függvény meghívásakor.
személy szerint ezt nagyon hasznosnak találom, mivel javítja a kiszámíthatóságot.
építési alkalmazások, a reaktív módon
Ezen oktatóanyagon keresztül reaktív alkalmazást épített az Ngrx, RxJS és Angular 2 használatával.
mint láttuk, ezek hatékony eszközök. Amit itt felépítettek, az egy Redux architektúra megvalósításának is tekinthető, és a Redux önmagában is erőteljes. Ennek azonban vannak bizonyos korlátai is. Míg az Ngrx-et használjuk, ezek a korlátozások elkerülhetetlenül tükröződnek az alkalmazásunk azon részében, amelyet használunk.
a fenti ábra az éppen elvégzett architektúra durva ábrázolása.
észreveheti, hogy még akkor is, ha egyes összetevők befolyásolják egymást, függetlenek egymástól. Ez az architektúra sajátossága: az összetevők közös függőséggel rendelkeznek, ami a bolt.
egy másik különleges dolog ebben az architektúrában az, hogy nem függvényeket hívunk, hanem műveleteket küldünk. Az Ngrx alternatívája lehet, ha csak egy olyan szolgáltatást hozunk létre, amely egy adott állapotot az alkalmazások megfigyelésével és a szolgáltatás hívási funkcióival kezel műveletek helyett. Ily módon az állam központosítását és reakcióképességét érhetjük el, miközben elszigeteljük a problémás államot. Ez a megközelítés segít csökkenteni a reduktor létrehozásának költségeit, és egyszerű objektumokként írja le a műveleteket.