Articles

NGR og Angular 2 Tutorial: opbygning af en reaktiv applikation

vi taler meget om reaktiv programmering i Vinkelområdet. Reaktiv programmering og Angular 2 ser ud til at gå hånd i hånd. Imidlertid, for alle, der ikke er bekendt med begge teknologier, det kan være en ganske skræmmende opgave at finde ud af, hvad det handler om.

i denne artikel lærer du ved at opbygge en reaktiv Vinkel 2-applikation ved hjælp af NGR, hvad mønsteret er, hvor mønsteret kan vise sig at være nyttigt, og hvordan mønsteret kan bruges til at opbygge bedre vinkel 2-applikationer.en gruppe af Vinkelbiblioteker til reaktive udvidelser. Vi bruger de velkendte rksobservabler af Vinkel 2. Det giver flere fordele ved at forenkle din applikationstilstand til almindelige objekter, håndhæve ensrettet datastrøm og meget mere. Biblioteket giver applikationen mulighed for at kommunikere med omverdenen ved at udløse bivirkninger.

Hvad er reaktiv programmering?

reaktiv programmering er et udtryk, som du hører meget i disse dage, men hvad betyder det virkelig?

reaktiv programmering er en måde, hvorpå applikationer håndterer begivenheder og datastrøm i dine applikationer. I reaktiv programmering designer du dine komponenter og andre dele af dit program for at reagere på disse ændringer i stedet for at bede om ændringer. Dette kan være et stort skift.

et godt værktøj til reaktiv programmering, som du måske ved, er Rksj ‘ er.

ved at levere observerbare og mange operatører til at transformere indgående data, hjælper dette bibliotek dig med at håndtere begivenheder i din applikation. Faktisk kan du med observerbare se begivenhed som en strøm af begivenheder og ikke en engangshændelse. Dette giver dig mulighed for at kombinere dem, for eksempel for at oprette en ny begivenhed, som du vil lytte til.

reaktiv programmering er et skift i den måde, du kommunikerer mellem forskellige dele af et program. I stedet for at skubbe data direkte til den komponent eller tjeneste, der havde brug for det, er det i reaktiv programmering den komponent eller tjeneste, der reagerer på dataændringer.

et ord om NGR

for at forstå det program, du vil bygge gennem denne tutorial, skal du gøre en hurtig dykke ned i kernen reduce begreber.

Store

butikken kan ses som din klientsidedatabase, men vigtigere, det afspejler tilstanden af din ansøgning. Du kan se det som den eneste kilde til sandhed.

det er det eneste, du ændrer, når du følger mønsteret, og du ændrer ved at sende handlinger til det.

Reducer

reducer er de funktioner, der ved, hvad de skal gøre med en given handling og den tidligere tilstand af din app.

reduktionsanordningerne tager den forrige tilstand fra din butik og anvender en ren funktion på den. Pure betyder, at funktionen altid returnerer den samme værdi for det samme input, og at den ikke har nogen bivirkninger. Fra resultatet af den rene funktion får du en ny tilstand, der vil blive sat i din butik.

handlinger

handlinger er den nyttelast, der indeholder nødvendige oplysninger til at ændre din butik. Dybest set har en handling en type og en nyttelast, som din reduktionsfunktion vil tage for at ændre tilstanden.

Dispatcher

dispatchere er simpelthen et indgangspunkt for dig at sende din handling. Der er en forsendelsesmetode direkte i butikken.

mellemvare

mellemvare er nogle funktioner, der opfanger hver handling, der sendes for at skabe bivirkninger, selvom du ikke vil bruge dem i denne artikel. De implementeres i biblioteket, og der er en stor chance for, at du får brug for dem, mens du bygger applikationer i den virkelige verden.

Hvorfor bruge ?

kompleksitet

butikken og ensrettet datastrøm reducerer i høj grad koblingen mellem dele af din applikation. Denne reducerede kobling reducerer kompleksiteten af din ansøgning, da hver del kun bekymrer sig om specifikke tilstande.

værktøj

hele din applikations tilstand er gemt et sted, så det er let at få et globalt overblik over din applikationstilstand og hjælper under udviklingen. Der er også mange gode dev-værktøjer, der drager fordel af butikken og kan hjælpe med at gengive en bestemt tilstand af applikationen eller f.eks.

arkitektonisk enkelhed

mange af fordelene ved NGR er opnåelige med andre løsninger. Men når du er nødt til at opbygge et program, der passer perfekt til det nye mønster, såsom samarbejdsredigeringsværktøjer, kan du nemt tilføje funktioner ved at følge mønsteret.

selvom du ikke behøver at tænke over, hvad du laver, bliver det trivielt at tilføje nogle ting som analytics gennem alle dine applikationer, da du kan spore alle de handlinger, der sendes.

lille indlæringskurve

da dette mønster er så bredt vedtaget og enkelt, er det virkelig nemt for nye mennesker i dit team at indhente hurtigt hvad du gjorde.når du har mange eksterne aktører, der kan ændre din applikation, f.eks. I disse tilfælde er det svært at styre alle de indgående data, der skubbes til din ansøgning, og Statsforvaltningen bliver hård. Derfor vil du forenkle det med en uforanderlig tilstand, og det er en ting, som butikken giver os.

opbygning af en applikation med NGR

styrken ved NGR lyser mest, når du har eksterne data, der skubbes til vores applikation i realtid. Med det i tankerne, lad os opbygge et simpelt freelancer-gitter, der viser online freelancere og giver dig mulighed for at filtrere gennem dem.

opsætning af projektet

Angular CLI er et fantastisk værktøj, der i høj grad forenkler installationsprocessen. Du vil måske ikke bruge det, men husk på, at resten af denne artikel vil bruge det.

npm install -g @angular/cli

Dernæst vil du oprette en ny applikation og installere alle biblioteker:

ng new toptal-freelancersnpm install ngrx --save

Freelancers Reducer

Reducers er et kernestykke i den nye arkitektur, så hvorfor ikke starte med dem først, mens du bygger applikationen?

Opret først en “freelancere” reducer, der er ansvarlig for at oprette vores nye tilstand, hver gang en handling sendes til butikken.

freelancer-gitter / freelancere.neddeler.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; }}

så her er vores freelancere reducer.

denne funktion kaldes hver gang en handling sendes gennem butikken. Hvis handlingen er FREELANCERS_LOADED, opretter den et nyt array fra handlingens nyttelast. Hvis det ikke er tilfældet, vil det returnere den gamle statsreference, og intet vil blive tilføjet.

det er vigtigt at bemærke her, at hvis den gamle statsreference returneres, vil staten blive betragtet som uændret. Dette betyder, at hvis du kalder en state.push(something), vil staten ikke blive betragtet som ændret. Husk det, mens du udfører dine reduktionsfunktioner.

stater er uforanderlige. En ny stat skal returneres hver gang den ændres.

Freelancer Grid Component

Opret en gitterkomponent for at vise vores online freelancere. I første omgang vil det kun afspejle, hvad der er i butikken.

ng generate component freelancer-grid

sæt følgende i freelancer-grid.komponent.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'); }}

og følgende i freelancer-grid.komponent.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>

Så hvad gjorde du bare gøre?

først har du oprettet en ny komponent kaldet freelancer-grid.

komponenten indeholder en egenskab med navnetfreelancers, der er en del af applikationstilstanden i butikken. Ved at bruge select-operatøren vælger du kun at blive underrettet af freelancers egenskaben for den samlede applikationstilstand. Så nu hver gang freelancers egenskaben af applikationstilstanden ændres, vil din observerbare blive underrettet.

en ting, der er smuk med denne løsning, er, at din komponent kun har en afhængighed, og det er butikken, der gør din komponent meget mindre kompleks og let genanvendelig.

på skabelondelen gjorde du intet for komplekst. Bemærk brugen af async-rør i *ngForfreelancers observerbar er ikke direkte iterabel, men takket være Angular har vi værktøjerne til at pakke den ud og binde dom til dens værdi ved hjælp af async-røret. Dette gør arbejdet med det observerbare så meget lettere.

tilføjelse af funktionen Fjern freelancere

nu hvor du har en funktionel base, lad os tilføje nogle handlinger til applikationen.

du vil være i stand til at fjerne en freelancer fra staten. I henhold til hvordan Reduks fungerer, skal du først definere den handling i hver stat, der er berørt af den.

i dette tilfælde er det kun 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; }}

det er virkelig vigtigt her at oprette et nyt array fra den gamle for at få en ny uforanderlig tilstand.

nu Kan du tilføje en slet freelancerfunktion til din komponent, der sender denne handling til butikken:

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

ser det ikke simpelt ud?

Du kan nu fjerne en bestemt freelancer fra staten, og denne ændring vil udbrede sig gennem din ansøgning.

hvad nu hvis du tilføjer en anden komponent til applikationen for at se, hvordan de kan interagere mellem hinanden gennem butikken?

Filter Reducer

som altid, lad os starte med reduceren. For den komponent er det ret simpelt. Du vil have reduceren til altid at returnere en ny stat med kun den ejendom, vi sendte. Det skal se sådan ud:

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

Filterkomponent

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

først har du lavet en simpel skabelon, der indeholder en formular med to felter (navn og e-mail), der afspejler vores tilstand.

du holder disse felter synkroniseret med tilstand ganske lidt anderledes end hvad du gjorde medfreelancers tilstand. Faktisk, som du har set, abonnerer du på filtertilstanden, og hver gang udløser det, at du tildeler den nye værdi til formControl.

en ting, der er rart med Angular 2, er, at det giver dig en masse værktøjer til at interagere med observerbare.

du har set async-røret tidligere, og nu ser duformControl klasse, der giver dig mulighed for at have en observerbar på værdien af et input. Dette tillader smarte ting som hvad du gjorde i filterkomponenten.

som du kan se, bruger duRx.observable.mergetil at kombinere de to observerbare givet af dinformControls, og så debounce den nye observerbare, før du udløserfilter funktion.

i enklere ord venter du et sekund efter et af navnet eller e-mailen formControl er ændret og kalder derefter filter funktion.

er det ikke fantastisk?

alt dette sker i et par linjer kode. Dette er en af grundene til, at du vil elske RTS. Det giver dig mulighed for nemt at gøre mange af de smarte ting, der ellers ville have været mere kompliceret.

lad os nu gå til den filterfunktion. Hvad gør den?

det sender simpelthen UPDATE_FILTER handling med værdien af navnet og e-mailen, og reduceren tager sig af at ændre tilstanden med disse oplysninger.

lad os gå videre til noget mere interessant.

hvordan får du det filter til at interagere med dit tidligere oprettede freelancer-gitter?

enkel. Du skal kun lytte til filterdelen af butikken. Lad os se, hvordan koden ser ud.

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

det er ikke mere kompliceret end det.

endnu en gang brugte du kraften i RJS til at kombinere filteret og freelancers tilstand.

faktisk combineLatestvil brand, hvis en af de to observerbare Brand og derefter kombinere hver tilstand ved hjælp af applyFilter funktion. Det returnerer en ny observerbar, der gør det. Vi behøver ikke ændre andre kodelinjer.

bemærk, hvordan komponenten ikke er ligeglad med, hvordan filteret opnås, ændres eller gemmes; det lytter kun til det, som det ville gøre for enhver anden tilstand. Vi har lige tilføjet filterfunktionaliteten, og vi tilføjede ikke nye afhængigheder.

Making it Shine

Husk, at brugen af NGR virkelig skinner, når vi skal håndtere data i realtid? Lad os tilføje den del til vores ansøgning og se, hvordan det går.

introduktion af freelancers-service.

ng generate service freelancer

freelancer-tjenesten simulerer realtidsdrift på data og skal se sådan ud.

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

denne service er ikke perfekt, men den gør, hvad den gør, og til demo-formål giver den os mulighed for at demonstrere et par ting.

for det første er denne service ret simpel. Det forespørger en bruger API og skubber resultaterne til butikken. Det er en no-brainer, og du behøver ikke at tænke over, hvor dataene går. Det går til butikken, hvilket er noget, der gør Reduks så nyttigt og farligt på samme tid—men vi vender tilbage til dette senere. Efter hvert tiende sekund vælger tjenesten et par freelancere og sender en operation for at slette dem sammen med en operation til et par andre freelancere.

Hvis vi ønsker, at vores reducer skal kunne håndtere det, skal vi ændre det:

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

nu er vi i stand til at håndtere sådanne operationer.

en ting, der demonstreres i denne tjeneste, er, at af al processen med tilstandsændringer, der udføres synkront, er det ret vigtigt at bemærke det. Hvis statens anvendelse var async, ville opkaldet på this.addFadeClassToNewElements(); ikke fungere, da DOM-elementet ikke ville blive oprettet, når denne funktion kaldes.

personligt finder jeg det ret nyttigt, da det forbedrer forudsigeligheden.

Building Applications, den reaktive måde

gennem denne vejledning har du bygget en reaktiv applikation ved hjælp af NGR, Rksog Angular 2.

som du har set, er disse kraftfulde værktøjer. Det, du har bygget her, kan også ses som implementeringen af en ny arkitektur, og ny er stærk i sig selv. Det har dog også nogle begrænsninger. Mens vi bruger , afspejler disse begrænsninger uundgåeligt i den del af vores applikation, vi bruger.

reaktivt paradigme

diagrammet ovenfor er et groft af den Arkitektur, du lige gjorde.

Du kan bemærke, at selvom nogle komponenter påvirker hinanden, er de uafhængige af hinanden. Dette er en særegenhed ved denne arkitektur: komponenter deler en fælles afhængighed, som er butikken.

en anden særlig ting ved denne arkitektur er, at vi ikke kalder funktioner, men afsendelseshandlinger. Et alternativ til kan være at kun lave en tjeneste, der administrerer en bestemt tilstand med observerbare af dine applikationer og opkaldsfunktioner på den pågældende tjeneste i stedet for handlinger. På denne måde kan du få centralisering og reaktivitet i staten, mens du isolerer den problematiske tilstand. Denne tilgang kan hjælpe dig med at reducere omkostningerne ved at oprette en reducer og beskrive handlinger som almindelige objekter.