Articles

Ngrx en Angular 2 Tutorial: bouwen van een reactieve toepassing

we praten veel over reactief programmeren in het Hoekrijk. Reactief programmeren en Hoek 2 lijken hand in hand te gaan. Echter, voor iedereen die niet bekend is met beide technologieën, kan het nogal een ontmoedigende taak om erachter te komen wat het allemaal over.

in dit artikel, door het bouwen van een reactieve Angular 2 applicatie met gebruik van Ngrx, leert u wat het patroon is, waar het patroon nuttig kan blijken te zijn, en hoe het patroon kan worden gebruikt om betere Angular 2 applicaties te bouwen.

Ngrx is een groep van Hoekbibliotheken voor reactieve extensies. Ngrx / Store implementeert het Redux patroon met behulp van de bekende RxJS observables van Angular 2. Het biedt verschillende voordelen door het vereenvoudigen van uw applicatie staat om gewone objecten, afdwingen unidirectionele gegevensstroom, en meer. De ngrx / effecten bibliotheek kan de toepassing om te communiceren met de buitenwereld door het activeren van bijwerkingen.

Wat Is reactief programmeren?

reactief programmeren is een term die je tegenwoordig veel hoort, maar wat betekent het eigenlijk?

reactief programmeren is een manier waarop applicaties gebeurtenissen en gegevensstromen in uw applicaties verwerken. Bij reactief programmeren ontwerpt u uw componenten en andere onderdelen van uw software om op die veranderingen te reageren in plaats van om veranderingen te vragen. Dit kan een geweldige verschuiving zijn.

een geweldig hulpmiddel voor reactief programmeren, zoals je misschien wel weet, is RxJS.

door het leveren van observables en veel operators om inkomende gegevens te transformeren, zal deze bibliotheek u helpen gebeurtenissen in uw toepassing af te handelen. In feite, met observables, kunt u Gebeurtenis zien als een stroom van gebeurtenissen en niet een eenmalige gebeurtenis. Dit stelt u in staat om ze te combineren, bijvoorbeeld, om een nieuwe gebeurtenis te creëren waarnaar u zult luisteren.

reactief programmeren is een verschuiving in de manier waarop u communiceert tussen verschillende delen van een toepassing. In plaats van data direct naar de component of dienst te sturen die het nodig had, is het de component of dienst die reageert op datawijzigingen.

een woord over Ngrx

om de toepassing te begrijpen die u in deze tutorial gaat bouwen, moet u snel een duik nemen in de kern Redux Concepten.

Store

de store kan worden gezien als uw client side database, maar, nog belangrijker, het geeft de status van uw toepassing weer. Je kunt het zien als de enige bron van waarheid.

Het is het enige wat je verandert als je het Redux patroon volgt en je wijzigt door acties ernaar te sturen.

Reducer

Reducers zijn de functies die weten wat te doen met een bepaalde actie en de vorige status van uw app.

De reductoren nemen de vorige status van uw winkel en passen er een pure functie op toe. Pure betekent dat de functie altijd dezelfde waarde retourneert voor dezelfde input en dat het geen bijwerkingen heeft. Uit het resultaat van die pure functie, zult u een nieuwe staat die in uw winkel zal worden gezet.

acties

acties zijn de lading die de benodigde informatie bevat om uw winkel te wijzigen. Kortom, een actie heeft een type en een lading die uw reducer functie zal nemen om de toestand te veranderen.

Dispatcher

Dispatchers zijn gewoon een toegangspunt voor u om uw actie te verzenden. In Ngrx is er een verzendmethode direct op de winkel.

Middleware

Middleware zijn enkele functies die elke actie die wordt verzonden onderscheppen om bijwerkingen te creëren, ook al zult u ze niet gebruiken in dit artikel. Ze zijn geà mplementeerd in de ngrx/Effect bibliotheek, en er is een grote kans dat je ze nodig hebt tijdens het bouwen van real-world applicaties.

waarom Ngrx gebruiken?

complexiteit

De opslag en unidirectionele gegevensstroom verminderen de koppeling tussen delen van uw toepassing aanzienlijk. Deze verminderde koppeling vermindert de complexiteit van uw toepassing, omdat elk onderdeel alleen geeft om specifieke toestanden.

Tooling

de volledige status van uw applicatie wordt op één plaats opgeslagen, dus het is gemakkelijk om een globaal beeld van uw applicatie status te hebben en helpt tijdens de ontwikkeling. Ook, met Redux komt een heleboel leuke dev tools die gebruik maken van de winkel en kan helpen om een bepaalde staat van de applicatie te reproduceren of tijdreizen, bijvoorbeeld.

architecturale eenvoud

veel van de voordelen van Ngrx zijn haalbaar met andere oplossingen; Redux is immers een architecturaal patroon. Maar als je een applicatie die is een geweldige pasvorm voor de Redux patroon te bouwen, zoals collaborative editing tools, kunt u eenvoudig functies toevoegen door het volgen van het patroon.

hoewel u niet hoeft na te denken over wat u doet, wordt het toevoegen van een aantal dingen zoals analytics door al uw toepassingen triviaal omdat u alle acties kunt bijhouden die worden verzonden.

kleine leercurve

omdat dit patroon zo algemeen en eenvoudig is, is het heel gemakkelijk voor nieuwe mensen in uw team om snel in te halen wat u deed.

Ngrx schittert het meest wanneer u veel externe actoren hebt die uw toepassing kunnen wijzigen, zoals een monitoring dashboard. In die gevallen is het moeilijk om alle inkomende gegevens te beheren die naar uw toepassing worden gepusht, en wordt het beheer van de staat moeilijk. Dat is de reden waarom je het wilt vereenvoudigen met een onveranderlijke staat, en dit is een ding dat de ngrx store ons biedt.

een toepassing bouwen met Ngrx

De kracht van Ngrx schijnt het meest wanneer u Externe gegevens hebt die in realtime naar onze toepassing worden gepusht. Met dat in het achterhoofd, laten we bouwen van een eenvoudige freelancer raster dat online freelancers toont en kunt u filteren via hen.

het opzetten van het Project

hoekige CLI is een geweldig hulpmiddel dat het installatieproces aanzienlijk vereenvoudigt. U wilt het misschien niet gebruiken, maar houd er rekening mee dat de rest van dit artikel het zal gebruiken.

npm install -g @angular/cli

vervolgens wilt u een nieuwe toepassing maken en alle ngrx-bibliotheken installeren:

ng new toptal-freelancersnpm install ngrx --save

Freelancers Reducer

Reducers zijn een kernstuk van De Redux-architectuur, dus waarom zou u er niet eerst mee beginnen tijdens het bouwen van de toepassing?

maak eerst een” freelancers ” reducer aan die verantwoordelijk zal zijn voor het maken van onze nieuwe status elke keer dat een actie naar de winkel wordt verzonden.

freelancer-grid / freelancers.drukregelaar.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; }}

dus hier is onze freelancers reducer.

Deze functie wordt aangeroepen elke keer dat een actie wordt verzonden via het archief. Als de actie FREELANCERS_LOADED is, zal er een nieuwe array van de actie payload worden aangemaakt. Als dat niet het geval is, zal het de oude referentie van de staat teruggeven en zal er niets worden toegevoegd.

Het is belangrijk hier op te merken dat, als de oude statusreferentie wordt geretourneerd, de status ongewijzigd zal worden beschouwd. Dit betekent dat als u een state.push(something) aanroept, de status niet als gewijzigd wordt beschouwd. Houd dat in gedachten, terwijl het doen van uw reducer functies.

toestanden zijn onveranderlijk. Een nieuwe staat moet worden teruggegeven elke keer dat het verandert.

Freelancer Grid Component

Maak een grid component om onze online freelancers te tonen. In eerste instantie zal het alleen weerspiegelen wat er in de winkel.

ng generate component freelancer-grid

plaats het volgende in freelancer-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: ,})export class FreelancerGridComponent implements OnInit { public freelancers: Rx.Observable<Array<IFreelancer>>; constructor(private store: Store<AppState>) { this.freelancers = store.select('freelancers'); }}

en het volgende in freelancer-grid.component.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>

dus wat heb je net gedaan?

eerst hebt u een nieuw component aangemaakt met de naam freelancer-grid.

Het component bevat een eigenschap met de naam freelancers die deel uitmaakt van de toepassingsstatus in het ngrx-archief. Door de Select operator te gebruiken, kiest u ervoor om alleen te worden gewaarschuwd door de eigenschap freelancers van de totale toepassingsstatus. Dus nu elke keer dat defreelancers eigenschap van de applicatie status verandert, zal uw waarneembare worden aangemeld.

een ding dat mooi is met deze oplossing is dat uw component slechts één afhankelijkheid heeft, en het is de winkel die uw component veel minder complex en gemakkelijk herbruikbaar maakt.

op het sjabloongedeelte hebt u niets te complex gedaan. Let op het gebruik van async-pijp in de *ngFor. Defreelancers waarneembaar is niet direct iterbaar, maar dankzij Angular hebben we de tools om het uit te pakken en de dom aan zijn waarde te binden door de Async-pijp te gebruiken. Dit maakt het werken met het waarneembare zo veel gemakkelijker.

het toevoegen van de Remove Freelancers functionaliteit

Nu u een functionele basis hebt, laten we enkele acties toevoegen aan de toepassing.

u wilt een freelancer uit de status kunnen verwijderen. Afhankelijk van hoe Redux werkt, moet je eerst die actie definiëren in elke staat die erdoor wordt beïnvloed.

In dit geval is het alleen de 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; }}

Het is echt belangrijk hier om een nieuwe array te maken van de Oude om een nieuwe onveranderlijke status te hebben.

nu kunt u een delete freelancers-functie toevoegen aan uw component die deze actie naar de winkel zal verzenden:

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

ziet dat er niet eenvoudig uit?

u kunt nu een specifieke freelancer uit de status verwijderen, en die verandering zal zich verspreiden via uw toepassing.

wat als u een ander onderdeel toevoegt aan de applicatie om te zien hoe ze tussen elkaar kunnen interageren via de winkel?

Filter Reducer

Zoals altijd, Laten we beginnen met het reducer. Voor dat onderdeel is het heel eenvoudig. Je wilt dat het reductiemiddel altijd een nieuwe staat teruggeeft met alleen het eigendom dat we verzonden hebben. Het ziet er als volgt uit:

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

Filter Component

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

eerst hebt u een eenvoudig sjabloon gemaakt dat een formulier bevat met twee velden (naam en e-mail) dat onze status weergeeft.

u houdt deze velden synchroon met status een beetje anders dan wat u deed met de freelancers status. In feite, zoals je hebt gezien, heb je je geabonneerd op de filterstatus, en elke keer wordt de nieuwe waarde toegewezen aan de formControl.

een ding dat leuk is met Angular 2 is dat het je voorziet van een heleboel tools om te communiceren met observables.

u hebt de Async-pijp eerder gezien, en nu ziet u deformControl klasse die u toelaat om een waarneembare waarde van een input te hebben. Dit maakt fancy dingen zoals wat je deed in de filter component.

zoals u kunt zien, gebruikt u Rx.observable.merge om de twee observables te combineren die door uw formControls worden gegeven, en dan ontleedt u die nieuwe observable voordat u de functie filter activeert.

in eenvoudigere woorden wacht u één seconde nadat een van de naam of e-mail formControl is gewijzigd en roept u vervolgens de functie filter aan.

is dat niet geweldig?

dit alles wordt gedaan in een paar regels code. Dit is een van de redenen waarom u zult houden van RxJS. Het stelt je in staat om veel van die mooie dingen gemakkelijk te doen die anders ingewikkelder zouden zijn geweest.

laten we nu naar die filterfunctie gaan. Wat doet het?

Het verzendt eenvoudig de actie UPDATE_FILTER met de waarde van de naam en de e-mail, en het reducer zorgt voor het wijzigen van de status met die informatie.

laten we verder gaan met iets interessants.

hoe laat u dat filter interageren met uw eerder gemaakte freelancer grid?

eenvoudig. Je hoeft alleen maar naar het filtergedeelte van de winkel te luisteren. Eens kijken hoe de code eruit ziet.

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

Het is niet ingewikkelder dan dat.

opnieuw gebruikte U de kracht van RxJS om het filter en de status van freelancers te combineren.

in feite zal combineLatest vuren als een van de twee observables afvuurt en vervolgens elke toestand combineren met behulp van de functie applyFilter. Het geeft een nieuwe waarneembare die dat doen. We hoeven geen andere regels code te veranderen.

merk op dat het component er niet om geeft hoe het filter wordt verkregen, gewijzigd of opgeslagen; het luistert er alleen naar zoals het zou doen voor elke andere toestand. We hebben zojuist de filterfunctionaliteit toegevoegd en we hebben geen nieuwe afhankelijkheden toegevoegd.

waardoor het glanst

onthoud dat het gebruik van Ngrx echt schittert wanneer we te maken hebben met real-time gegevens? Laten we dat deel toevoegen aan onze applicatie en zien hoe het gaat.

introduceert de freelancers-service.

ng generate service freelancer

de freelancer-service zal realtime bewerkingen op data simuleren en moet er zo uitzien.

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

Deze service is niet perfect, maar het doet wat het doet en, voor demo doeleinden, stelt het ons in staat om een paar dingen te demonstreren.

ten eerste is deze service vrij eenvoudig. Het vraagt een gebruiker API en duwt de resultaten naar de winkel. Het is een no-brainer, en je hoeft niet na te denken over waar de gegevens gaan. Het gaat naar de winkel, dat is iets dat Redux zo nuttig en gevaarlijk maakt op hetzelfde moment—maar we zullen hier later op terugkomen. Na elke tien seconden, de dienst kiest een paar freelancers en stuurt een operatie om ze te verwijderen samen met een operatie aan een paar andere freelancers.

als we willen dat ons reductiemiddel het kan verwerken, moeten we het wijzigen:

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 zijn we in staat om dergelijke operaties af te handelen.

een ding dat wordt aangetoond in die service is dat, van al het proces van statuswijzigingen die synchroon worden uitgevoerd, het heel belangrijk is om dat op te merken. Als de toepassing van de status async was, zou de aanroep op this.addFadeClassToNewElements(); niet werken omdat het DOM-element niet zou worden aangemaakt wanneer deze functie wordt aangeroepen.

persoonlijk vind ik dat heel nuttig, omdat het de voorspelbaarheid verbetert.

toepassingen bouwen, de reactieve manier

in deze handleiding hebt u een reactieve toepassing gebouwd met behulp van Ngrx, RxJS en Angular 2.

zoals je hebt gezien, zijn dit krachtige hulpmiddelen. Wat je hier hebt gebouwd kan ook worden gezien als de implementatie van een Redux architectuur, en Redux is krachtig op zich. Het heeft echter ook een aantal beperkingen. Hoewel we Ngrx gebruiken, weerspiegelen deze beperkingen onvermijdelijk in het deel van onze applicatie dat we gebruiken.

reactief paradigma

het diagram hierboven is een ruwe weergave van de architectuur die u zojuist hebt gemaakt.

u kunt merken dat zelfs als sommige componenten elkaar beïnvloeden, ze onafhankelijk van elkaar zijn. Dit is een bijzonderheid van deze architectuur: componenten delen een gemeenschappelijke afhankelijkheid, dat is de winkel.

een ander specifiek ding over deze architectuur is dat we geen functies aanroepen maar acties verzenden. Een alternatief voor Ngrx zou kunnen zijn om alleen een service te maken die een bepaalde toestand beheert met observables van je applicaties en callfuncties op die service in plaats van acties. Op deze manier kun je centralisatie en reactiviteit van de staat krijgen terwijl je de problematische staat isoleert. Deze aanpak kan u helpen om de overhead van het maken van een reductiemiddel te verminderen en acties te beschrijven als gewone objecten.