Articles

Ngrx: n ja Angular 2 Tutorialin kanssa: reaktiivisen sovelluksen rakentaminen

puhumme paljon reaktiivisesta ohjelmoinnista Angular-valtakunnassa. Reaktiivinen ohjelmointi ja Angular 2 tuntuvat kulkevan käsi kädessä. Kuitenkin kaikille, jotka eivät tunne molempia tekniikoita, se voi olla melko pelottava tehtävä selvittää, mitä se on kyse.

tässä artikkelissa, rakentamalla reaktiivinen Kulmikas 2-sovellus ngrx: n avulla, opit mikä kuvio on, missä kuvio voi osoittautua hyödylliseksi, ja miten kuviolla voidaan rakentaa parempia kulmikkaita 2-sovelluksia.

ngrx on ryhmä kulmakirjastoja reaktiivisille laajennuksille. Ngrx / Store toteuttaa Redux-kuvion käyttäen Angular 2: n tunnettua rxjs observablesia. Se tarjoaa useita etuja yksinkertaistamalla sovellustilaasi tavallisiin esineisiin, valvomalla yksisuuntaista tiedonkulkua ja paljon muuta. Ngrx / Effects-kirjaston avulla sovellus voi kommunikoida ulkomaailman kanssa käynnistämällä sivuvaikutuksia.

mitä on reaktiivinen ohjelmointi?

reaktiivinen ohjelmointi on termi, jota kuulee nykyään paljon, mutta mitä se oikeastaan tarkoittaa?

reaktiivinen ohjelmointi on sovellusten tapa käsitellä sovellusten tapahtumia ja tiedonkulkua. Reaktiivisessa ohjelmoinnissa suunnittelet ohjelmistosi komponentit ja muut osat reagoidaksesi muutoksiin sen sijaan, että pyytäisit muutoksia. Tämä voi olla suuri muutos.

loistava työkalu reaktiiviseen ohjelmointiin, kuten ehkä tiedätte, on RxJS.

tarjoamalla havainnoitavia ja paljon operaattoreita muuttamaan saapuvaa dataa, tämä kirjasto auttaa sinua käsittelemään sovelluksen tapahtumia. Itse asiassa havaintojen avulla tapahtuman voi nähdä tapahtumien virtana eikä kertaluonteisena tapahtumana. Näin voit yhdistellä niitä, esimerkiksi luoda uuden tapahtuman, jota kuuntelet.

reaktiivinen ohjelmointi on muutos tavassa, jolla kommunikoit sovelluksen eri osien välillä. Sen sijaan, että data työnnettäisiin suoraan sitä tarvitsevalle komponentille tai palvelulle, reaktiivisessa ohjelmoinnissa kyse on datamuutoksiin reagoivasta komponentista tai palvelusta.

sana Ngrx: stä

jotta ymmärtäisit sovelluksen, jonka rakennat tämän opetusohjelman kautta, sinun täytyy tehdä nopea sukellus ydin Redux-käsitteisiin.

Store

kauppa voidaan nähdä asiakaspuolen tietokantana, mutta mikä tärkeintä, se kuvastaa sovelluksesi tilaa. Voit nähdä sen ainoana totuuden lähteenä.

se on ainoa asia, jota muutat, kun noudatat Redux-kaavaa ja muutat siirtämällä siihen toimintoja.

Reducer

Reducerit ovat funktioita, jotka tietävät mitä tehdä tietylle toiminnolle ja sovelluksen aiemmalle tilalle.

pelkistimet ottavat liikkeestäsi edellisen tilan ja soveltavat siihen puhtaan funktion. Puhdas tarkoittaa, että funktio palauttaa aina saman arvon samalle syötölle ja että sillä ei ole sivuvaikutuksia. Tämän puhtaan toiminnon tuloksena sinulla on uusi tila, joka laitetaan myymälääsi.

toiminnot

toiminnot ovat hyötykuorma, joka sisältää tarpeellisia tietoja varastosi muuttamiseksi. Periaatteessa toiminnolla on tyyppi ja hyötykuorma, jonka vähennystoimintosi ottaa muuttaakseen tilaa.

Lähettäjä

lähettäjät ovat yksinkertaisesti maahantulopiste, johon voit lähettää toimesi. Ngrx: ssä on lähetysmenetelmä suoraan myymälässä.

Middleware

Middleware on joitakin toimintoja, jotka pysäyttävät jokaisen lähetettävän toiminnon sivuvaikutusten luomiseksi, vaikka et käytä niitä tässä artikkelissa. Ne toteutetaan Ngrx / Effect-kirjastossa, ja on suuri mahdollisuus, että tarvitset niitä rakentaessasi reaalimaailman sovelluksia.

miksi Ngrx: ää käytetään?

monimutkaisuus

säilö ja yksisuuntainen tiedonkulku vähentävät huomattavasti sovelluksen osien välisiä kytkentöjä. Tämä vähennetty kytkentä vähentää monimutkaisuutta sovelluksen, koska jokainen osa välittää vain tiettyjä tiloja.

Työkalutus

sovelluksen koko tila on tallennettu yhteen paikkaan, joten on helppo saada yleiskuva hakemustilasta ja auttaa kehityksen aikana. Reduxin mukana tulee myös paljon mukavia dev-työkaluja, jotka hyödyntävät myymälää ja voivat auttaa toistamaan sovelluksen tietyn tilan tai tekemään esimerkiksi aikamatkustusta.

arkkitehtoninen yksinkertaisuus

monet ngrx: n eduista ovat saavutettavissa muilla ratkaisuilla; onhan Redux arkkitehtoninen kuvio. Mutta kun sinun täytyy rakentaa sovellus, joka sopii erinomaisesti Redux malli, kuten yhteistyössä muokkaustyökaluja, voit helposti lisätä ominaisuuksia seuraamalla kuvio.

vaikka sinun ei tarvitse miettiä, mitä olet tekemässä, joidenkin asioiden, kuten analytiikan lisääminen kaikkiin sovelluksiisi käy triviaaliksi, koska voit seurata kaikkia lähetettäviä toimintoja.

pieni oppimiskäyrä

koska tämä kuvio on niin laajasti omaksuttu ja yksinkertainen, tiimisi uusien ihmisten on todella helppo saada nopeasti kiinni tekemisistäsi.

Ngrx loistaa eniten, kun on paljon ulkopuolisia toimijoita, jotka voivat muokata sovellusta, kuten valvontapaneeli. Näissä tapauksissa, se on vaikea hallita kaikkia saapuvia tietoja, jotka työnnetään sovellukseen, ja valtionhallinnon tulee vaikeaa. Siksi haluat yksinkertaistaa sitä muuttumattomassa tilassa, ja tämä on yksi asia, että Ngrx store tarjoaa meille.

sovelluksen rakentaminen ngrx: llä

ngrx: n teho loistaa eniten, kun on ulkopuolista dataa, jota työnnetään sovellukseemme reaaliajassa. Tämä mielessä, let ’ s rakentaa yksinkertainen freelancer grid, joka näyttää online freelancers ja voit suodattaa niitä.

projektin perustaminen

Angular CLI on mahtava työkalu, joka yksinkertaistaa asetusprosessia huomattavasti. Et ehkä halua käyttää sitä, mutta muista, että loput tämän artikkelin käyttää sitä.

npm install -g @angular/cli

seuraavaksi haluat luoda uuden sovelluksen ja asentaa kaikki Ngrx-kirjastot:

ng new toptal-freelancersnpm install ngrx --save

freelancerit Reducer

Reducerit ovat Redux-arkkitehtuurin ydinkappale, joten miksi et aloittaisi niistä ensin sovellusta rakentaessasi?

luo ensin ”freelancers” – pelkistin, joka vastaa uuden valtiomme luomisesta aina, kun kauppaan lähetetään toiminto.

freelancer-grid / freelancerit.vähennin.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; }}

joten tässä on meidän freelancereiden reducer.

tätä toimintoa kutsutaan aina, kun toiminto lähetetään kaupan kautta. Jos toiminto on FREELANCERS_LOADED, se luo uuden joukon toiminnan hyötykuormasta. Jos se ei ole, se palauttaa vanhan valtion viite ja mitään ei ole liitetty.

tässä on tärkeää huomata, että jos vanha valtioviite palautetaan, tila katsotaan muuttumattomaksi. Tämä tarkoittaa sitä, että jos kutsutaan state.push(something), valtion ei katsota muuttuneen. Pidä tämä mielessä, kun teet vähennystoimintoja.

valtiot ovat muuttumattomia. Uusi tila on palautettava aina, kun se muuttuu.

Freelancer Grid Component

Create a grid component to show our online freelancers. Aluksi se vain kuvastaa sitä, mitä myymälässä on.

ng generate component freelancer-grid

laita freelancer-ruudukkoon seuraavakomponentti.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'); }}

ja seuraavat freelancer-ruudussa.komponentti.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">&times;</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>

joten mitä sinä juuri teit?

ensin on luotu uusi komponentti nimeltä freelancer-grid.

komponentti sisältää ominaisuuden nimeltä freelancers, joka on osa ngrx-kaupan sisältämää sovellustilaa. Valitsemalla operaattorin valitset vain freelancers ominaisuus koko sovellustilassa. Joten nyt joka kerta, kun freelancers sovellustilan ominaisuus muuttuu, havainnoitavalle ilmoitetaan.

yksi asia, joka on kaunis tämän ratkaisun on, että komponentti on vain yksi riippuvuus, ja se on myymälä, joka tekee komponentti paljon vähemmän monimutkainen ja helposti uudelleenkäytettäviä.

sapluunaosuudella et tehnyt mitään liian monimutkaista. Huomaa async-putken käyttö *ngForfreelancers havainnoitava ei ole suoraan iteroitavissa, mutta kulmikkuuden ansiosta meillä on Työkalut avata se ja sitoa dom sen arvoon async-putken avulla. Tämä tekee havainnoitavan kanssa työskentelystä paljon helpompaa.

poistamalla freelancerit-toiminnallisuuden lisääminen

nyt kun sinulla on toimiva pohja, lisätään sovellukseen joitakin toimintoja.

haluat poistaa freelancerin valtiosta. Sen mukaan, miten Redux toimii, sinun täytyy ensin määritellä, että toiminta kussakin tilassa, johon se vaikuttaa.

tässä tapauksessa on vain 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; }}

tässä on todella tärkeää luoda uusi array vanhasta, jotta saadaan Uusi muuttumaton tila.

nyt voit lisätä komponenttiisi delete freelancers-funktion, joka lähettää tämän toiminnon kauppaan:

delete(freelancer) { this.store.dispatch({ type: ACTIONS.DELETE_FREELANCER, payload: freelancer, }) }

eikö tuo näytä yksinkertaiselta?

voit nyt poistaa tietyn freelancerin valtiosta, ja tämä muutos etenee hakemuksesi kautta.

nyt mitä jos lisäät toisen komponentin sovellukseen nähdäksesi, miten ne voivat olla vuorovaikutuksessa keskenään kaupan kautta?

suodattimen pelkistin

kuten aina, aloitetaan pelkistimestä. Että komponentti, se on melko yksinkertainen. Haluat pelkistäjän palauttavan aina uuden valtion, jossa on vain lähettämämme omaisuus. Sen pitäisi näyttää tältä:

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; }}

Suodatinkomponentti

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, }) }}

ensin on tehty yksinkertainen malli, joka sisältää lomakkeen, jossa on kaksi kenttää (nimi ja sähköposti), jotka kuvastavat tilaamme.

pidät noita kenttiä synkronoituina tilan kanssa melko vähän eri tavalla kuin mitä teit freelancers valtion kanssa. Itse asiassa, kuten olet nähnyt, tilasit suodatintilan, ja joka kerta se laukaisee uuden arvon formControl.

yksi asia, joka on kiva Angular 2 on, että se tarjoaa sinulle paljon työkaluja vuorovaikutuksessa observables.

olet nähnyt asyncin putken aiemmin, ja nyt näet formControl luokan, jonka avulla voit olla havaittava tulon arvosta. Tämä mahdollistaa hienoja asioita, kuten mitä teit suodatinkomponentissa.

kuten näet, käytät Rx.observable.merge yhdistääksesi kaksi formControls antamaa havainnoitavaa, ja tämän jälkeen poistat uuden havaitun ennen kuin käynnistät filter funktion.

yksinkertaisemmilla sanoilla odotellaan sekunti sen jälkeen, kun jompikumpi nimi tai sähköposti formControl on muuttunut, ja sitten kutsutaan filter funktio.

eikö olekin mahtavaa?

kaikki tämä tehdään muutamalla koodirivillä. Tämä on yksi syy, miksi rakastat RxJS. Sen avulla voit tehdä paljon niitä hienoja asioita helposti, jotka olisivat olleet monimutkaisempia muuten.

nyt astutaan siihen suodatusfunktioon. Mitä se tekee?

se vain lähettää

UPDATE_FILTER

toiminnan nimen ja sähköpostin arvolla, ja pelkistäjä huolehtii valtion muuttamisesta näillä tiedoilla.

siirrytään johonkin mielenkiintoisempaan.

miten saat kyseisen suodattimen toimimaan aiemmin luodun freelancer-ruudukon kanssa?

yksinkertainen. Sinun tarvitsee vain kuunnella suodatinosaa kaupassa. Katsotaan, miltä koodi näyttää.

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, }) }}

se ei ole sen monimutkaisempaa.

käytit jälleen kerran rxjs: n voimaa yhdistääksesi suodattimen ja freelancereiden valtion.

itse asiassa combineLatest ampuu, jos jompikumpi havaituista ampuu ja yhdistää sitten jokaisen tilan käyttäen applyFilter funktiota. Se palauttaa uuden havaittava, jotka tekevät niin. Meidän ei tarvitse muuttaa muita koodirivejä.

huomaa, miten komponentti ei välitä siitä, miten suodatin on saatu, muutettu tai tallennettu; se vain kuuntelee sitä, kuten se tekisi missä tahansa muussa tilassa. Lisäsimme juuri suodatintoiminnon emmekä lisänneet uusia riippuvuuksia.

Making It Shine

Muistatko, että ngrx: n käyttö todella loistaa, kun joudumme käsittelemään reaaliaikaista dataa? Lisätään se osa hakemukseemme ja katsotaan, miten se menee.

käyttöön freelancers-service.

ng generate service freelancer

freelancer-palvelu simuloi reaaliaikaista toimintaa dataan ja sen pitäisi näyttää tältä.

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'); } } }}

Tämä palvelu ei ole täydellinen, mutta se tekee mitä tekee ja demotarkoituksessa se mahdollistaa muutaman asian esittelyn.

ensinnäkin tämä palvelu on melko yksinkertainen. Se kysyy käyttäjän API ja työntää tulokset tallentaa. Ei tarvitse miettiä, mihin data menee. Se menee kauppaan, mikä tekee Reduxista yhtä aikaa niin hyödyllisen ja vaarallisen—mutta palaamme tähän myöhemmin. Kymmenen sekunnin välein palvelu valitsee muutaman freelancerin ja lähettää toiminnon, jolla heidät poistetaan yhdessä operaation kanssa muutamalle muulle freelancerille.

Jos haluamme, että pelkistimemme pystyy käsittelemään sitä, meidän on muokattava sitä:

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; }}

nyt pystymme käsittelemään tällaisia operaatioita.

yksi asia, joka tuossa palvelussa näkyy on se, että kaikista valtiomuutosprosesseista, jotka tehdään synkronisesti, on aika tärkeää huomata se. Jos Valtion sovellus olisi async, kutsu this.addFadeClassToNewElements(); ei toimisi, koska Dom-elementtiä ei luotaisi, kun tätä funktiota kutsutaan.

henkilökohtaisesti pidän sitä varsin hyödyllisenä, koska se parantaa ennustettavuutta.

Rakennussovellukset, reaktiivinen tapa

tämän opetusohjelman kautta, olet rakentanut reaktiivisen sovelluksen käyttäen Ngrx: ää, RxJS: ää ja Angular 2: ta.

kuten olet nähnyt, nämä ovat tehokkaita työkaluja. Se, mitä olet rakentanut tänne, voidaan nähdä myös Redux-arkkitehtuurin toteutuksena, ja Redux on itsessään voimakas. Sillä on kuitenkin myös joitakin rajoitteita. Vaikka käytämme Ngrx: ää, nämä rajoitteet näkyvät väistämättä sovelluksemme osassa, jota käytämme.

reaktiivinen paradigma

yllä oleva kaavio on karkea juuri tekemästäsi arkkitehtuurista.

Saatat huomata, että vaikka jotkin osat vaikuttavat toisiinsa, ne ovat toisistaan riippumattomia. Tämä on tämän arkkitehtuurin erikoisuus: komponenteilla on yhteinen riippuvuus, joka on myymälä.

toinen erityinen asia tässä arkkitehtuurissa on se, että emme kutsu funktioita vaan lähetämme toimintoja. Vaihtoehto Ngrx: lle voisi olla vain tehdä palvelu, joka hallitsee tiettyä tilaa, jossa on havaittavissa sovelluksia ja puhelutoimintoja kyseisessä Palvelussa toimintojen sijaan. Näin voisi saada valtion keskittämistä ja taantumuksellisuutta ja samalla eristää ongelmallisen valtion. Tämä lähestymistapa voi auttaa vähentämään overhead luoda reducer ja kuvata toimia tavallinen esineitä.