Hoe Angular-sorteerfilters te upgraden

Inleiding

AngularJS, Een van de nuttigste functies van het eerste aanbod van AngularJS was de mogelijkheid om gegevens op de pagina te filteren en te sorteren met behulp van alleen sjabloonvariabelen en -filters. Twee-weg data binding won veel bekeerlingen tot AngularJS.

Vandaag de dag, echter, veel front-end ontwikkelaars de voorkeur aan een-weg data binding, en die orderBy en filter filters zijn zonondergang met de komst van Angular. (Opmerking: in dit artikel zal ik “AngularJS” gebruiken om te verwijzen naar 1.x en gewoon “Angular” om te verwijzen naar 2+.)

Maar hoe worden we verondersteld om datzelfde effect te bereiken? Het antwoord ligt in onze componenten, dus laten we eens kijken naar een ngUpgrade project en leren hoe dit te doen!

Stap 1 – Code van Git trekken

We gaan stap voor stap het sjabloon van een vers herschreven component bijwerken. Daarna voegen we sorteren en filteren toe om alle functionaliteit te herstellen die het had in AngularJS. Dit is een belangrijke vaardigheid om te ontwikkelen voor het ngUpgrade proces.

Om te beginnen, neem even de tijd om het voorbeeld project dat we zullen gebruiken te clonen (vergeet niet om npm install in zowel de public en server mappen uit te voeren). Bekijk deze commit voor ons startpunt:

git checkout 9daf9ab1e21dc5b20d15330e202f158b4c065bc3

Dit voorbeeld project is een ngUpgrade hybride project dat zowel AngularJS 1.6 als Angular 4 gebruikt. Het heeft een werkende Express API en een Webpack builds voor zowel ontwikkeling als productie. Voel je vrij om het te verkennen, fork het, en gebruik de patronen in je eigen projecten. Als je een versie van dit project wilt bekijken die Angular 5 gebruikt, bekijk dan deze repo. Voor het doel van deze tutorial zijn de verschillen tussen de twee versies niet van belang (ik zal wijzen op alles wat klein is).

Step 2 – Vervang de AngularJS Syntax

In dit stadium van onze applicatie, is onze orders component herschreven in Angular, met al zijn afhankelijkheden geïnjecteerd en opgelost. Als we echter zouden proberen onze applicatie uit te voeren, zouden we fouten in de console zien die duiden op problemen met onze template. Dat is wat we eerst moeten oplossen. We gaan de AngularJS syntax in het ordersjabloon (orders/orders.html) vervangen, zodat we de route kunnen laden en de orders op de pagina kunnen tonen. Het filteren en sorteren doen we hierna.

Het eerste wat we moeten doen is ons ontdoen van alle instanties van $ctrl in deze template. Ze zijn niet langer nodig in Angular. We kunnen gewoon een zoek en vervang doen om te zoeken naar $ctrl. (let op de punt), en het vervangen door niets.

Nu laten we de data-ng-click in onze knop op regel 13 vervangen. In Angular gebruiken we in plaats van ng-click gewoon het click event, met haakjes om aan te geven dat het om een event gaat. Haakjes geven een input aan, en haakjes geven een output of een event aan.

<button type="button" (click)="goToCreateOrder()" class="btn btn-info">Create Order</button>

We zeggen hier alleen dat op de click event, de goToCreateOrder functie op onze orders component afgaat.

Voordat we verder gaan, laten we even een minuutje nemen om te bewijzen dat onze component daadwerkelijk aan het laden is. Commentarieer de hele div die onze orders laadt (vanaf regel 17). Om de applicatie te draaien, open een terminal en voer de volgende commando’s uit:

cd servernpm start

Dat zal de Express server starten. Om de Webpack dev server te draaien, opent u een andere terminal en voert u uit:

cd publicnpm run dev

(U kunt deze processen draaiende houden voor de rest van deze tutorial.)

U zou moeten zien dat onze applicatie weer laadt. Als u naar de bestelroute gaat, ziet u dat de bestelcomponent correct wordt weergegeven.

App screenshot

We kunnen ook op de knop Bestel maken klikken en dan worden we correct naar onze bestelroute en ons bestelformulier gestuurd.

Okee, laten we teruggaan naar de HTML. Un-comment dat div (onze app zal weer worden gebroken).

Laten we de rest van de instanties data-ng-click vervangen door de (click) event handler. U kunt ofwel Find & Replace gebruiken of gewoon de sneltoets van uw editor voor het selecteren van alle occurrences (in VS Code voor Windows is dit Ctrl+Shift+L).

Volgende, vervang alle occurrences van data-ng-show door *ngIf. Er is eigenlijk geen direct equivalent voor ng-show in Angular, maar dat is niet erg. Het verdient de voorkeur om *ngIf te gebruiken, omdat je op die manier elementen toevoegt en verwijdert uit het DOM in plaats van ze alleen maar te verbergen en te tonen. Dus, alles wat we moeten doen is onze data-ng-shows vinden en vervangen door *ngIf.

Ten slotte moeten we twee dingen doen om onze tabel body te repareren. Ten eerste, data-ng-repeat vervangen door *ngFor="let order of orders". Merk op dat we ook de orderBy en filter filters in die regel verwijderen, zodat de hele tr er als volgt uitziet:

<tr *ngFor="let order of orders">

Ten tweede kunnen we de data-ng prefix verwijderen voor de href link naar de order detail route. AngularJS zorgt hier nog steeds voor de routing, maar we hoeven dat voorvoegsel niet meer te gebruiken omdat dit nu een Angular-sjabloon is.

Als we de applicatie nog eens bekijken, kunt u zien dat de orders correct op het scherm worden geladen:

Orders uit app screenshot

Er zijn natuurlijk wel een paar dingen mis mee. De sorteerlinks werken niet meer, en nu is onze valuta een beetje in de war omdat de valutapijp in Angular iets anders is dan zijn AngularJS-tegenhanger. Daar komen we nog op. Voor nu is dit een goed teken, omdat het betekent dat onze gegevens naar de component gaan en op de pagina worden geladen. Dus, we hebben de basis van deze template omgezet naar Angular. Nu zijn we klaar om het sorteren en filteren aan te pakken!

Step 3 – Sortering toevoegen

We hebben onze bestellingen op het scherm, maar we hebben nog geen manier om ze te ordenen of te sorteren. In AngularJS was het heel gebruikelijk om het ingebouwde orderBy filter te gebruiken om de gegevens op de pagina te sorteren. Angular heeft niet langer een orderBy filter. Dit is omdat het nu sterk wordt aangemoedigd om dat soort business logica in de component te plaatsen in plaats van het op de template te hebben. Dus, dat is wat we hier gaan doen. (Opmerking: we gaan hier gebruik maken van gewone oude functies en gebeurtenissen, niet van een reactieve formulier aanpak. Dit is omdat we proberen om deze dingen in kleine stapjes te begrijpen. Als je eenmaal de basis onder de knie hebt, voel je dan vrij om verder te gaan met observables!)

Sorteren in het Component

We hebben het orderBy filter al verwijderd uit ng-repeat toen we het veranderden in *ngFor. Nu gaan we een sorteer functie maken op de orders component. We kunnen de klikgebeurtenissen op onze tabelkoppen gebruiken om die functie op te roepen en de eigenschap door te geven waarop we willen sorteren. We laten die functie ook heen en weer schakelen tussen oplopend en aflopend.

Open de orders component (./orders/orders.component.ts) en voeg twee publieke eigenschappen toe aan de class. Deze zullen overeenkomen met de twee eigenschappen waar onze template al naar verwijst. De eerste wordt sortType van het type string. De tweede wordt sortReverse van het type boolean en we zetten de standaardwaarde op false. De sortReverse eigenschap houdt alleen bij of de volgorde moet worden omgedraaid – zie het niet als een synoniem voor oplopend of aflopend.

Dus nu zou je dit moeten hebben na de declaratie van de titel in de klasse:

sortType: string;sortReverse: boolean = false;

Volgende voegen we de functie toe die we zullen gebruiken met de Array.sort prototype functie in JavaScript. Voeg dit toe na de functie goToCreateOrder (maar nog steeds binnen de klasse):

dynamicSort(property) { return function (a, b) { let result = (a < b) ? -1 : (a > b) ? 1 : 0; return result; } }

Deze dynamische sorteerfunctie zal de waarde van de eigenschappen van objecten in een array vergelijken. De geneste ternaire functie kan een beetje lastig te begrijpen zijn op het eerste gezicht, maar het komt er op neer dat als de waarde van onze eigenschap van A kleiner is dan B, geef -1 terug. Anders, als het groter is, geef 1 terug. Als de twee gelijk zijn, geef je 0 terug.

Nou, dit is geen super verfijnde of diepe vergelijking. Er zijn veel geavanceerdere hulpfuncties die je zou kunnen schrijven om voor jou te sorteren, en voel je vrij om te experimenteren met hoe je deze kunt breken. Maar voor ons doel is het voldoende, en je kunt deze logica gewoon vervangen door de sorteerlogica die je zelf wilt.

Dat is dus onze helperfunctie. De sort functie op het Array prototype kan een functie doorgegeven krijgen die het kan gebruiken om items in een array te vergelijken. Laten we een functie met de naam sortOrders op onze klasse maken die daarvan gebruik maakt met de nieuwe dynamicSort functie:

sortOrders(property) { }

Het eerste wat we moeten doen is de sortType eigenschap op onze klasse gelijk maken aan de eigenschap die is doorgegeven. Dan willen we de sortReverse eigenschap aanzetten. We krijgen dan dit:

sortOrders(property) { this.sortType = property; this.sortReverse = !this.sortReverse;}

Nu kunnen we de sort functie oproepen op this.orders, maar onze dynamische sorteer functie doorgeven met onze eigenschap:

sortOrders(property) { this.sortType = property; this.sortReverse = !this.sortReverse; this.orders.sort(this.dynamicSort(property));}

En er is nog een laatste ding dat we moeten doen. We moeten onze dynamicSort functie een klein beetje aanpassen om de volgorde van de array te kunnen omkeren voor oplopend of aflopend. Om dit te doen, koppelen we het resultaat van de dynamicSort aan de sortReverse eigenschap van de klasse.

Het eerste wat we doen is een variabele declareren:

let sortOrder = -1;

Dan kunnen we controleren of onze sortReverse eigenschap van onze klasse waar of onwaar is. Als dat waar is, stellen we onze variabele voor de sorteervolgorde in op 1:

if (this.sortReverse) { sortOrder = 1; }

We koppelen onze functies op deze manier aan elkaar omdat we in onze sorteerfunctie omwille van de demonstratie een omschakeling uitvoeren. Om grondiger te zijn, zou een andere benadering zijn om een variabele te hebben met de naam sortDescending in plaats van sortReverse die wordt aangestuurd door een aparte functie. Als u deze route volgt, doet u het omgekeerde – sortOrder zou 1 zijn tenzij sortDescending waar was.

We zouden deze laatste twee dingen ook kunnen combineren in een ternaire expressie, maar voor de duidelijkheid laat ik het een beetje meer omslachtig. En om ons resultaat het tegenovergestelde te maken van wat het normaal zou zijn, kan ik result gewoon vermenigvuldigen met onze sortOrder. Dus onze dynamicSort functie ziet er nu zo uit:

 dynamicSort(property) { let sortOrder = -1; if (this.sortReverse) { sortOrder = 1; } return function(a, b) { let result = a < b ? -1 : a > b ? 1 : 0; return result * sortOrder; }; }

Overnieuw, dit is een demonstratie implementatie van sorteren, zodat u de belangrijkste concepten begrijpt van het gebruik van een aangepaste sorteer functie op uw component.

Laten we eens kijken of het sorteren werkt

Tot nu toe hebben we een dynamicSort helper functie en een sortOrders functie toegevoegd aan onze klasse, zodat we kunnen sorteren op onze component in plaats van op onze template.

Om te zien of deze functies werken, voegen we een standaard sortering toe aan onze ngOnInit functie.

In ons forkJoin abonnement, na de forEach waar we de klantnaam eigenschap toevoegen, roepen we this.sortOrders op en geven we de totale items eigenschap door:

this.sortOrders('totalItems');

Wanneer het scherm wordt vernieuwd, zou u moeten zien dat de bestellingen worden gesorteerd op het totale aantal items.

Nu hoeven we deze sortering alleen nog maar in onze sjabloon te implementeren door de functie sortOrders in de koppelingen van de tabelkop op te roepen.

Sortering toevoegen aan de sjabloon

We hebben onze sortOrders functie correct werkend op onze orders component, wat betekent dat we nu klaar zijn om het toe te voegen aan onze sjabloon, zodat de tabel headers weer klikbaar zijn.

Voordat we dat doen, laten we de standaard sortering in onze ngOnInit functie veranderen in alleen ID:

this.sortOrders('id');

Dat is een beetje normaler dan het gebruik van het totaal aantal items.

Nu kunnen we aan ons sjabloon werken. Het eerste wat we willen doen is de functie sortOrders in al onze klikgebeurtenissen oproepen. U kunt de instanties van sortType = selecteren en ze vervangen door sortOrders(. Vervolgens kunt u de instanties van ; sortReverse = !sortReverse vervangen door ).

We moeten ook twee van de property-namen aanpassen die we hier doorgeven, en ook in de *ngIf instanties. Vervang de 3 instanties van orderId door id en de 3 instanties van customername door customerName.

Het laatste wat ik moet doen is elk van de href tags in de headers tussen haakjes wikkelen zodat Angular het overneemt en deze links eigenlijk nergens heen gaan. De klik gebeurtenis zal het ding zijn dat wordt afgevuurd. Dus, de headers zouden dit patroon moeten volgen:

<th> <a ="" (click)="sortOrders('id')"> Order Id <span *ngIf="sortType == 'id' && !sortReverse" class="fa fa-caret-down"></span> <span *ngIf="sortType == 'id' && sortReverse" class="fa fa-caret-up"></span> </a></th>

Hop naar de browser en test al je tabel header links. Je zou moeten zien dat elk van onze eigenschappen nu sorteert, zowel in oplopende als aflopende volgorde. Geweldig!

Dit is geweldig, maar we zijn een ding kwijt – onze cursor is een selector, geen aanwijzer. Laten we dat met wat CSS oplossen.

Fix the Cursor

We hebben onze sortering goed werkend op onze bestelpagina, maar onze cursor is nu een selector in plaats van een aanwijzer, en dat is vervelend.

Er zijn een paar verschillende manieren waarop we CSS kunnen gebruiken om dit op te lossen:

  • We zouden een klasse kunnen maken in het SCSS bestand van onze hoofd-app.
  • We zouden in-line CSS kunnen schrijven, hoewel dat bijna nooit de voorkeur verdient.
  • We zouden gebruik kunnen maken van Angular’s scoped CSS met behulp van de styles optie in de component decorator

We gaan voor de laatste optie, want alles wat we hoeven te doen is één regel toevoegen aan onze styles voor dit specifieke component.

Open de orders component klasse opnieuw. In de component decorator kunnen we een nieuwe eigenschap toevoegen, genaamd styles. Styles is een array van strings, maar de strings zijn CSS regels. Om onze cursor te repareren, hoeven we alleen maar een regel uit te schrijven die zegt dat als we in een tabelrij een link hebben, we de cursor eigenschap veranderen in pointer. Onze decorator ziet er nu zo uit:

@Component({ selector: 'orders', template: template, styles: })

Nu, als we met de muis over onze rij headers gaan, zie je dat we de aanwijzer cursor hebben. Wat cool is aan deze aanpak is dat deze CSS regel geen invloed zal hebben op andere componenten. Het zal alleen van toepassing zijn op onze orders component!

Nu, laten we eens kijken of we iets kunnen doen aan onze filtering. Dat “filter filter” is verwijderd uit Angular, dus we zullen creatief moeten zijn en een manier moeten verzinnen om het op onze component te implementeren.

Step 4 – Filtering toevoegen

We zijn klaar om onze filterbox te vervangen die vroeger het AngularJS filter gebruikte om door de orders collectie te zoeken op basis van een string die we zochten. De AngularJS filter stond op onze template en had geen code nodig in onze controller of component. Tegenwoordig wordt dat soort logica in de template ontmoedigd. Het verdient de voorkeur om dat soort sorteren en filteren in onze componentklasse te doen.

Een filterfunctie toevoegen

Terug in onze component maken we een nieuwe array van orders genaamd filteredOrders. Vervolgens geven we onze orders-array door aan een filterfunctie die de filteredOrders-array instelt. Tenslotte gebruiken we de filteredOrders op onze template in onze *ngFor in plaats van onze oorspronkelijke array. Op die manier wijzigen we nooit de gegevens die terugkomen van de server, we gebruiken alleen een subset ervan.

Het eerste wat we doen is de nieuwe eigenschap declareren op onze klasse :

filteredOrders: Order;

Dan, in onze forkJoin die onze oorspronkelijke array van orders instelt, kunnen we de initiële staat van filteredOrders instellen op onze orders array:

this.filteredOrders = this.orders;

Nu zijn we klaar om onze functie toe te voegen die daadwerkelijk het filteren voor ons zal doen. Plak deze functie direct na onze sorteerfuncties onderaan onze component:

filterOrders(search: string) { this.filteredOrders = this.orders.filter(o => Object.keys(o).some(k => { if (typeof o === 'string') return o.toLowerCase().includes(search.toLowerCase()); }) ); }

Laten we eens praten over wat er in deze functie gebeurt. Ten eerste geven we de functie een string eigenschap van search. Dan lopen we door onze orders en vinden dan alle sleutels van de objecten. Voor alle sleutels gaan we kijken of er some waarden zijn van die eigenschappen die overeenkomen met onze zoekterm. Dit stukje JavaScript kan er in het begin een beetje verwarrend uitzien, maar dat is in principe wat er gebeurt.

Merk op dat, in onze if verklaring, we expliciet testen op strings. In ons voorbeeld beperken we onze query tot strings. We gaan niet proberen om te gaan met geneste eigenschappen, nummer eigenschappen, of iets dergelijks. Onze zoekterm zal overeenkomen met onze klantnaam eigenschap, en als we er ooit voor kiezen om ons adres of een andere string eigenschap weer te geven, zal het ook daar doorheen zoeken.

Natuurlijk kunnen we deze functie ook aanpassen om te testen op getallen, of door een andere laag van geneste objecten te kijken, en dat is helemaal aan jou. Net als bij het sorteren, beginnen we met een demonstratie-implementatie en laten je je fantasie gebruiken om het complexer te maken.

Over de functie sortOrders gesproken, voordat we verder gaan, moeten we nog een laatste ding doen aan de component. We hoeven alleen maar sortOrders te wijzigen om nu filteredOrders te gebruiken en niet onze oorspronkelijke orders, omdat we willen dat het filter voorrang krijgt boven het sorteren. Verander het gewoon in deze:

sortOrders(property) { this.sortType = property; this.sortReverse = !this.sortReverse; this.filteredOrders.sort(this.dynamicSort(property));}

Nu zijn we klaar om deze filtering op het sjabloon te implementeren.

Filtering toevoegen aan de sjabloon

Laten we teruggaan naar onze sjabloon en deze aanpassen om onze filtering te gebruiken.

Het eerste wat we moeten doen is data-ng-model vervangen. In plaats daarvan gaan we het keyup event gebruiken, dus we schrijven, “keyup” en zetten er haakjes omheen ((keyup)). Dit is een ingebouwd event in Angular waarmee we een functie kunnen uitvoeren op het moment dat een invoer wordt ingedrukt. Omdat we onze functie filterOrders hebben genoemd, wat de naam was van de eigenschap die we in het AngularJS filter doorgaven, hoeven we er alleen maar haakjes naast te zetten. Onze invoer ziet er tot nu toe zo uit:

<input type="text" class="form-control" placeholder="Filter Orders (keyup)="filterOrders()">

Maar wat geven we door aan de filter orders functie? Nou, standaard geven events iets door dat $event heet. Dit bevat iets dat target heet, en dat bevat dan de waarde van de invoer. Er is een probleem met het gebruik van $event. Het is erg moeilijk om die nevelige types bij te houden, omdat target.value echt van alles kan zijn. Dit maakt het moeilijk om te debuggen of om te weten welk type waarde wordt verwacht. In plaats daarvan heeft Angular een heel handig ding dat we kunnen doen, namelijk een template variabele toewijzen aan deze invoer.

Gelukkig biedt Angular ons een methode om dit te doen. Na onze input tag, kunnen we het hash teken (#) toevoegen en dan de naam van ons gewenste model. Laten we het #ordersFilter noemen. Het maakt niet uit waar in de tag je dit zet of hoe je het noemt, maar ik zet het graag na de input, zodat je ziet welk model bij welke input hoort als ik even op de pagina kijk.

Nu kan ik die variabele doorgeven aan onze filterOrders functie op de keyup gebeurtenis. We hebben het hash symbool er niet voor nodig, maar we moeten wel .value toevoegen. Dit zal de werkelijke waarde van het model doorgeven en niet het hele model zelf. Onze voltooide invoer ziet er zo uit:

<input #ordersFilter type="text" class="form-control" placeholder="Filter Orders" (keyup)="filterOrders(ordersFilter.value)">

Tot slot moeten we onze *ngFor aanpassen om de filteredOrders array te gebruiken in plaats van de gewone orders array:

<tr *ngFor="let order of filteredOrders">

Inspect the Product

U kunt zien hoeveel schoner ons sjabloon is nu onze filtering en sortering in de component zit.

Nu laten we dit eens bekijken in de browser. Als u wat tekst in het vak invoert, zoals “sally”, zou u moeten zien dat onze bestellingen veranderen en dat de sortering erbovenop werkt:
Animatie van werkende app
Tof, we hebben weer een AngularJS-functie vervangen!

Nu hoeven we nog maar één ding te doen aan dit component: de valutapijp repareren.

Stap 5 – Repareer de valutapijp

De laatste stap is het bijwerken van het voormalige valutafilter, dat in Angular nu de valutapijp wordt genoemd. We hoeven alleen maar een paar parameters toe te voegen aan de pijp in het sjabloon die we in AngularJS niet hoefden op te geven. Dit gedeelte verschilt als je Angular 4 of Angular 5 gebruikt:.

In Angular 4, doe je dit:
<td>{{order.totalSale | currency:'USD':true}}</td>

In Angular 5+, doe je dit:
<td>{{order.totalSale | currency:'USD':'symbol'}}</td>

De eerste optie is de valutacode (er zijn er veel, je bent niet beperkt tot Amerikaanse dollars!). De tweede is de symboolweergave. In Angular 4 is dit een boolean die aangeeft of het valutasymbool of de code moet worden gebruikt. In Angular 5+ zijn de opties symbol, code, of symbol-narrow als strings.

U zou nu het verwachte symbool moeten zien:
Screenshot van kolom Totale verkoop van app

En we zijn klaar! Om de afgewerkte code te zien, bekijk deze commit.

Conclusie

Jullie hebben goed werk geleverd door dit tot het einde vol te houden! Hier is wat we bereikt hebben in deze handleiding:

  1. Vervangen van AngularJS template syntax met Angular syntax
  2. Verplaatsen van sorteren naar de component
  3. Gebruik van scoped CSS styles
  4. Verplaatsen van filteren naar de component
  5. Vervangen van het AngularJS valuta filter met de Angular valuta pijp

Waar moet je nu verder mee? Er zijn veel dingen die je zou kunnen doen:

  • Het sorteren geavanceerder maken (bijvoorbeeld: moet de volgorde worden gereset of hetzelfde blijven als de gebruiker op een nieuwe header klikt?)
  • Het filteren geavanceerder maken (zoeken op getallen of geneste eigenschappen)
  • Overgaan op een reactieve aanpak. Je zou kunnen luisteren naar een observable van waardeveranderingen in plaats van de keyup functie en daar sorteren en filteren in doen. Met observabelen kun je ook leuke dingen doen, zoals de input debouncen!