Sådan opgraderes Angular Sorteringsfiltre

Indledning

AngularJS, En af de mest nyttige funktioner i AngularJS’s oprindelige tilbud var evnen til at filtrere og sortere data på siden ved hjælp af skabelonvariabler og filtre. Tovejs databinding vandt mange konvertitter til AngularJS.

I dag foretrækker mange front-end-udviklere dog envejs databinding, og disse orderBy og filter filtre er blevet solnedgang med fremkomsten af Angular. (Bemærk: I hele denne artikel vil jeg bruge “AngularJS” til at henvise til 1.x og blot “Angular” til at henvise til 2+.)

Men hvordan skal vi opnå den samme effekt? Svaret ligger i vores komponenter, så lad os se på et ngUpgrade-projekt og lære, hvordan vi gør det!

Stræk 1 – Træk kode fra Git

Vi vil gennemgå trinvis opdatering af skabelonen for en nyskrevet komponent. Derefter tilføjer vi sortering og filtrering for at genoprette alle de funktioner, som den havde i AngularJS. Dette er en vigtig færdighed at udvikle til ngUpgrade-processen.

For at komme i gang skal du tage et øjeblik til at klone det eksempelprojekt, som vi skal bruge (glem ikke at køre npm install i både mapperne public og server). Tjek dette commit for vores udgangspunkt:

git checkout 9daf9ab1e21dc5b20d15330e202f158b4c065bc3

Dette eksempelprojekt er et ngUpgrade-hybridprojekt, der bruger både AngularJS 1.6 og Angular 4. Det har et fungerende Express API og en Webpack builds til både udvikling og produktion. Du er velkommen til at udforske, fork det, og bruge mønstrene i dine egne projekter. Hvis du gerne vil se på en version af dette projekt, der bruger Angular 5, kan du tjekke denne repo. I forbindelse med denne tutorial vil forskellene mellem de to versioner være ligegyldige (jeg påpeger alt mindre vigtigt).

Stræk 2 – Udskift AngularJS-syntaksen

På dette tidspunkt i vores applikation er vores ordrekomponent omskrevet i Angular, med alle dens afhængigheder injiceret og løst. Hvis vi prøvede at køre vores applikation, ville vi dog se fejl i konsollen, der indikerer problemer med vores skabelon. Det er det, vi skal rette først. Vi skal erstatte AngularJS-syntaksen i ordreskabelonen (orders/orders.html), så vi kan få ruten indlæst og ordrerne vist på siden. Vi retter filtreringen og sorteringen næste gang.

Den første ting, vi skal gøre, er at fjerne alle forekomster af $ctrl i denne skabelon. De er ikke længere nødvendige i Angular. Vi kan bare lave en find og erstatte for at finde $ctrl. (bemærk prikken) og erstatte det med ingenting.

Nu skal vi erstatte data-ng-click i vores knap i linje 13. I Angular bruger vi i stedet for ng-click bare click-hændelsen, med parenteser for at angive, at det er en begivenhed. Parenteser angiver et input, og parenteser angiver et output eller en hændelse.

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

Vi siger bare her, at vi ved klikhændelsen skal affyre goToCreateOrder-funktionen på vores ordrekomponent.

Hvor vi fortsætter, skal vi lige tage et øjeblik for at bevise, at vores komponent faktisk indlæses. Kommenter hele div, der indlæser vores ordrer (fra linje 17 og frem), ud. For at køre programmet skal du åbne en terminal og køre følgende kommandoer:

cd servernpm start

Det vil starte Express-serveren. For at køre Webpack dev-serveren skal du åbne en anden terminal og køre:

cd publicnpm run dev

(Du kan lade disse processer køre i resten af denne vejledning.)

Du bør se, at vores applikation indlæses igen. Hvis du går til ordre-ruten, vil du se, at ordrekomponenten vises korrekt.

Skærmbillede af app

Vi kan også klikke på knappen Opret ordre, og det vil sende os korrekt over til vores Opret ordre-rute og formular.

Okay, lad os komme tilbage til HTML’en. Fjern kommentaren i den div (vores app vil blive ødelagt igen).

Lad os erstatte alle resten af instanserne data-ng-click med (click)-hændelseshåndteringen. Du kan enten bruge Find & Erstat eller bare bruge din editors genvej til at vælge alle forekomster (i VS Code for Windows er det Ctrl+Shift+L).

Næst erstatter vi alle forekomsterne af data-ng-show med *ngIf. Der er faktisk ingen direkte ækvivalent til ng-show i Angular, men det er helt i orden. Det er at foretrække at bruge *ngIf, for på den måde tilføjer og fjerner du faktisk elementer fra DOM’en i stedet for blot at skjule og vise dem. Så alt, hvad vi skal gøre, er at finde vores data-ng-shows og erstatte dem med *ngIf.

Endeligt skal vi gøre to ting for at rette vores tabelkorpus. For det første skal vi erstatte data-ng-repeat med *ngFor="let order of orders". Bemærk, at vi også fjerner filtrene orderBy og filter i den linje, så hele tr ser således ud:

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

For det andet kan vi slette præfikset data-ng før href-linket til ruten med ordredetaljer. AngularJS håndterer stadig routing her, men vi behøver ikke længere at bruge dette præfiks, da dette nu er en Angular-skabelon.

Hvis vi kigger på programmet igen, kan du se, at ordrerne indlæses korrekt på skærmen:

Order fra app screenshot

Der er selvfølgelig et par ting, der er galt med det. Sorteringslinkene virker ikke længere, og nu er vores valuta lidt forvirret, fordi valutarøret i Angular er lidt anderledes end dets modstykke i AngularJS. Det skal vi nok komme til. Indtil videre er det et godt tegn, fordi det betyder, at vores data når frem til komponenten og indlæses på siden. Så vi har fået det grundlæggende i denne skabelon konverteret til Angular. Nu er vi klar til at tage fat på vores sortering og filtrering!

Stræk 3 – Tilføjelse af sortering

Vi har fået vores ordrer indlæst på skærmen, men vi har endnu ikke en måde at ordne eller sortere dem på. I AngularJS var det meget almindeligt at bruge det indbyggede orderBy-filter til at sortere dataene på siden. Angular har ikke længere et orderBy-filter. Det skyldes, at det nu kraftigt opfordres til at flytte den slags forretningslogik ind i komponenten i stedet for at have den på skabelonen. Så det er det, vi vil gøre her. (Bemærk: Vi vil bruge almindelige gamle funktioner og begivenheder her, ikke en reaktiv formulartilgang. Dette er fordi vi bare forsøger at tage små skridt til at forstå disse ting. Når du har styr på det grundlæggende, er du velkommen til at tage det videre med observables!)

Sortering i komponenten

Vi fjernede allerede filteret orderBy fra ng-repeat, da vi ændrede det til *ngFor. Nu skal vi lave en sorteringsfunktion på ordrekomponenten. Vi kan bruge klikbegivenhederne på vores tabeloverskrifter til at kalde denne funktion og indtaste den egenskab, som vi ønsker at sortere efter. Vi vil også have funktionen til at skifte frem og tilbage mellem stigende og faldende.

Lad os åbne ordrekomponenten (./orders/orders.component.ts) og tilføje to offentlige egenskaber til klassen. Disse kommer til at matche de to egenskaber, som vores skabelon allerede refererer til. Den første vil være sortType af typen string. Den anden vil være sortReverse af typen boolean, og vi indstiller standardværdien til false. Egenskaben sortReverse holder blot styr på, om rækkefølgen skal vendes – tænk ikke på den som et synonym for stigende eller faldende.

Så du bør nu have dette efter deklarationen af titlen i klassen:

sortType: string;sortReverse: boolean = false;

Næst tilføjer vi den funktion, som vi skal bruge med prototypefunktionen Array.sort i JavaScript. Tilføj dette efter goToCreateOrder-funktionen (men stadig inden for klassen):

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

Denne dynamiske sorteringsfunktion sammenligner egenskabsværdien af objekter i et array. Den indlejrede ternære funktion kan være lidt vanskelig at forstå ved første øjekast, men den siger i bund og grund bare, at hvis værdien af vores egenskab af A er mindre end B, skal du returnere -1. Ellers, hvis den er større, returneres 1. Hvis de to er lige store, returneres 0.

Nu er dette ikke super sofistikeret eller dyb sammenligning. Der er langt mere sofistikerede hjælpefunktioner, du kan skrive til at sortere for dig, og du er velkommen til at eksperimentere med, hvordan du kan bryde denne. Det er dog nok til vores formål, og du kan bare udskifte denne logik med den brugerdefinerede sorteringslogik, du ønsker.

Så det er vores hjælpefunktion. Sort-funktionen på Array-prototypen kan få overdraget en funktion, som den så kan bruge til at sammenligne elementer i et array. Lad os lave en funktion kaldet sortOrders på vores klasse, der udnytter dette med den nye dynamicSort-funktion:

sortOrders(property) { }

Den første ting, vi skal gøre, er at sætte sortType-egenskaben på vores klasse lig med den egenskab, der er overgivet i. Derefter ønsker vi at skifte sortReverse-egenskaben. Vi får dette:

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

Nu kan vi kalde sort-funktionen på this.orders, men overfører vores dynamiske sorteringsfunktion med vores egenskab:

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

Og der er en sidste ting, vi skal gøre. Vi skal ændre vores dynamicSort-funktion bare en lille smule for at kunne vende rækkefølgen i arrayet for stigende eller faldende. For at gøre dette skal vi binde resultatet af dynamicSort til sortReverse-egenskaben på klassen.

Den første ting, vi gør, er at deklarere en variabel:

let sortOrder = -1;

Derpå kan vi kontrollere, om vores sortReverse-egenskab på vores klasse er sand eller falsk. Hvis det er sandt, sætter vi vores variabel for sorteringsrækkefølge lig med 1:

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

Vi binder vores funktioner sammen på denne måde, fordi vi for demonstrationens skyld laver en omskiftning i vores sorteringsfunktion. For at være mere grundig ville en anden fremgangsmåde være at have en variabel, der hedder sortDescending i stedet for sortReverse, og som styres via en separat funktion. Hvis du går denne vej, vil du gøre det modsatte – sortOrder vil være 1, medmindre sortDescending er sandt.

Vi kunne også kombinere de to sidste ting til et ternært udtryk, men for overskuelighedens skyld vil jeg lade det være lidt mere mundret. Og for så bare at gøre vores resultat det modsatte af, hvad det normalt ville være, kan jeg bare gange result med vores sortOrder. Så vores dynamicSort-funktion ser nu således ud:

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

Dette er igen en demonstrationsimplementering af sortering, så du forstår de vigtigste begreber ved at bruge en brugerdefineret sorteringsfunktion på din komponent.

Lad os se, om sorteringen virker

Så langt har vi tilføjet en dynamicSort-hjælpefunktion og en sortOrders-funktion til vores klasse, så vi kan sortere på vores komponent i stedet for på vores skabelon.

For at se, om disse funktioner fungerer, skal vi tilføje en standardsortering til vores ngOnInit-funktion.

Inden for vores forkJoin-abonnement, efter forEach, hvor vi tilføjer egenskaben kundenavn, kalder vi this.sortOrders og indsender egenskaben samlede varer:

this.sortOrders('totalItems');

Når skærmen opdateres, bør du se, at ordrerne bliver sorteret efter de samlede varer.

Nu skal vi bare implementere denne sortering på vores skabelon ved at kalde sortOrders-funktionen i fra tabellen header links.

Føj sortering til skabelonen

Vi har fået vores sortOrders-funktion til at fungere korrekt på vores ordrekomponent, hvilket betyder, at vi nu er klar til at tilføje den til vores skabelon, så tabeloverskrifterne igen er klikbare.

Hvor vi gør det, skal vi ændre standardsorteringen i vores ngOnInit-funktion til bare at være ID:

this.sortOrders('id');

Det er lidt mere normalt end at bruge de samlede varer.

Nu kan vi arbejde på vores skabelon. Det første, vi vil gøre, er at kalde sortOrders-funktionen i alle vores klikbegivenheder. Du kan vælge forekomsterne af sortType = og erstatte dem med sortOrders(. Derefter kan du erstatte forekomsterne af ; sortReverse = !sortReverse med ).

Vi skal også rette to af de egenskabsnavne, som vi sender ind her, samt i *ngIf-instanserne. Udskift de 3 forekomster af orderId med id og de 3 forekomster af customername med customerName.

Den sidste ting, jeg skal gøre, er at pakke hvert af href-tagsene i overskrifterne ind i parenteser, så Angular tager over, og disse links faktisk ikke kommer nogen steder hen. Klikhændelsen vil være den ting, der bliver affyret. Så overskrifterne skal følge dette mønster:

<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 over til browseren og test alle dine tabeloverskriftslinks. Du bør se, at hver af vores egenskaber nu sorterer, både i stigende og faldende rækkefølge. Fantastisk!

Det er fantastisk, men vi har mistet en ting – vores markør er en selector og ikke en pointer. Lad os rette det med noget CSS.

Fiks cursoren

Vi har fået vores sortering til at fungere korrekt på vores ordreside, men vores cursor er nu en selector i stedet for en pointer, og det er irriterende.

Der er et par forskellige måder, hvorpå vi kan bruge CSS til at rette dette:

  • Vi kunne lave en klasse i vores hovedapp SCSS-fil.
  • Vi kunne skrive in-line CSS, selv om det næsten aldrig er at foretrække.
  • Vi kunne drage fordel af Angular’s scoped CSS ved hjælp af styles-indstillingen i komponentdekoratoren

Vi vælger den sidste mulighed, fordi vi blot skal tilføje én regel til vores styles for denne særlige komponent.

Opnå komponentklassen ordrer igen. I komponentdekoratoren kan vi tilføje en ny egenskab kaldet styles. Styles er et array af strenge, men strengene er CSS-regler. For at rette vores cursor skal vi blot skrive en regel, der siger, at hvis vi i en tabelrække har et link, så skal vi ændre cursor-egenskaben til pointer. Vores decorator vil nu se således ud:

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

Nu, når vi holder musen over vores rækkeoverskrifter, kan du se, at vi har pointer cursor. Det smarte ved denne fremgangsmåde er, at denne CSS-regel ikke vil påvirke andre komponenter. Den vil kun gælde for vores ordrekomponent!

Nu skal vi se, om vi kan gøre noget ved vores filtrering. Det “filterfilter” blev fjernet fra Angular, så vi bliver nødt til at være kreative og finde på en måde at implementere det på vores komponent på.

Stræk 4 – Tilføj filtrering

Vi er klar til at erstatte vores filterboks, der plejede at bruge AngularJS-filteret til at søge gennem ordresamlingen baseret på en streng, som vi søgte. AngularJS-filteret boede på vores skabelon og krævede ikke nogen kode i vores controller eller komponent. I dag frarådes den slags logik i skabelonen. Det er at foretrække at foretage den slags sortering og filtrering i vores komponentklasse.

Føj en filterfunktion

Tilbage i vores komponent vil vi lave et nyt array af ordrer kaldet filteredOrders. Derefter vil vi sende vores orders-array ind i en filterfunktion, der indstiller filteredOrders-arrayet. Til sidst bruger vi filteredOrders på vores skabelon i vores *ngFor i stedet for vores oprindelige array. På den måde ændrer vi aldrig de data, der kommer tilbage fra serveren, vi bruger bare en delmængde af dem.

Den første ting, vi gør, er at deklarere den nye egenskab på vores klasse :

filteredOrders: Order;

Dernæst kan vi i vores forkJoin, der indstiller vores oprindelige array af ordrer, indstille den oprindelige tilstand af filteredOrders til vores ordrer-array:

this.filteredOrders = this.orders;

Nu er vi klar til at tilføje vores funktion, der rent faktisk skal udføre filtreringen for os. Indsæt denne funktion lige efter vores sorteringsfunktioner i bunden af vores komponent:

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

Lad os tale om, hvad der foregår i denne funktion. Først giver vi funktionen en strengegenskab på search. Derefter gennemløber vi vores ordrer og finder derefter alle nøglerne til objekterne. For alle nøglerne vil vi se, om der er some-værdier for disse egenskaber, der passer til vores søgeord. Denne del af JavaScript kan se lidt forvirrende ud i starten, men det er grundlæggende det, der foregår.

Bemærk, at vi i vores if-anvisning udtrykkeligt tester for strenge. I vores eksempel lige nu vil vi bare begrænse vores forespørgsel til strings. Vi vil ikke forsøge at håndtere indlejrede egenskaber, talegenskaber eller noget lignende. Vores søgeudtryk vil matche vores egenskab kundenavn, og hvis vi nogensinde vælger at vise vores adresse eller en anden strengegenskab, vil den også søge gennem disse.

Vi kunne selvfølgelig også ændre denne funktion til at teste for tal eller kigge gennem et andet lag af nested objekter, og det er helt op til dig. Ligesom med vores sortering starter vi med en demonstrationsimplementering og lader dig bruge din fantasi til at gøre det mere komplekst.

Når vi taler om sortOrders-funktionen, skal vi, før vi går videre, gøre en sidste ting ved komponenten. Vi skal blot ændre sortOrders til at bruge filteredOrders nu og ikke vores oprindelige orders, fordi vi ønsker, at filteret skal have prioritet over sorteringen. Du skal blot ændre det til dette:

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

Nu er vi klar til at implementere denne filtrering på skabelonen.

Føj filtrering til skabelonen

Lad os flytte tilbage til vores skabelon og rette den op til at bruge vores filtrering.

Den første ting, vi skal gøre, er at erstatte data-ng-model. I stedet skal vi bruge keyup-hændelsen, så vi skriver: “keyup” og omgiver det med parenteser ((keyup)). Dette er en indbygget begivenhed i Angular, der lader os køre en funktion på tast op af et input. Da vi navngav vores funktion filterOrders, som tidligere var navnet på den egenskab, som vi sendte ind i AngularJS-filteret, skal vi blot tilføje parenteser ved siden af den. Vores input ser indtil videre således ud:

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

Men hvad skal vi sende ind i filterordrefunktionen? Tja, som standard videregiver begivenheder noget, der hedder $event. Dette indeholder noget, der hedder en target, som så indeholder værdien af input. Der er et problem med at bruge $event. Det er meget svært at holde styr på disse tåbelige typer, fordi target.value virkelig kan være hvad som helst. Det gør det svært at fejlfinde eller at vide, hvilken type værdi der forventes. I stedet har Angular en rigtig smart ting, vi kan gøre, nemlig at tildele en skabelonvariabel til dette input.

Glukkeligvis tilbyder Angular en metode, så vi kan gøre dette. Efter vores input-tag kan vi tilføje hash-tegnet (#) og derefter navnet på vores ønskede model. Lad os kalde det #ordersFilter. Det er egentlig ligegyldigt, hvor i tagget du sætter dette, eller hvad du kalder det, men jeg kan godt lide at sætte det efter input, så man fanger hvilken model der er tilknyttet hvilket input, hvis jeg lige kigger ned ad siden.

Nu kan jeg sende denne variabel ind i vores filterOrders-funktion på keyup-hændelsen. Vi har ikke brug for hash-symbolet før det, men vi skal tilføje .value. Dette vil videregive den faktiske værdi af modellen og ikke hele modellen selv. Vores færdige input ser således ud:

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

Endeligt skal vi ændre vores *ngFor til at bruge filteredOrders arrayet i stedet for det almindelige orders array:

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

Inspect the Product

Du kan se, hvor meget renere vores skabelon er nu, hvor vores filtrering og sortering er i komponenten.

Nu skal vi tjekke dette ud i browseren. Hvis du indtaster noget tekst i feltet, f.eks. “sally”, skulle du kunne se, at vores ordrer ændres, og at sorteringen fungerer ovenpå det:
Animation af fungerende app
Awesome, vi har erstattet endnu en AngularJS-funktion!

Nu har vi bare en sidste ting, vi skal gøre på denne komponent – reparere valutarøret.

Stræk 5 – Reparer valutarøret

Vores sidste touch er at opdatere det tidligere valutafilter, som nu kaldes valutarøret i Angular. Vi skal blot tilføje et par parametre til røret i skabelonen, som vi ikke behøvede at angive i AngularJS. Denne del er forskellig, hvis du bruger Angular 4 eller Angular 5:.

I Angular 4 skal du gøre dette:
<td>{{order.totalSale | currency:'USD':true}}</td>

I Angular 5+ skal du gøre dette:
<td>{{order.totalSale | currency:'USD':'symbol'}}</td>

Den første mulighed er valutakoden (der er mange, du er ikke begrænset til amerikanske dollars!). Den anden er symbolvisningen. I Angular 4 er dette en boolean, der angiver, om valutasymbolet eller koden skal bruges. I Angular 5+ er mulighederne symbol, code eller symbol-narrow som strenge.

Du bør nu se det forventede symbol:
Screenshot af kolonnen Total Sale i appen

Og vi er færdige! Hvis du vil se den færdige kode, kan du tjekke dette commit.

Slutning

Du gjorde et godt stykke arbejde ved at holde fast i dette til sidst! Her er, hvad vi har opnået i denne vejledning:

  1. Ombytte AngularJS-skabelon-syntaks med Angular-syntaks
  2. Flytte sortering til komponenten
  3. Anvendelse af scoped CSS-stile
  4. Flytte filtrering til komponenten
  5. Ombytte AngularJS valutafilteret med Angular currency pipe

Hvor skal du gå hen herfra? Der er mange ting, du kunne gøre:

  • Gør sorteringen mere sofistikeret (f.eks.: Skal rækkefølgen nulstilles eller forblive den samme, når brugeren klikker på en ny overskrift?)
  • Gør filtreringen mere sofistikeret (søg efter tal eller indlejrede egenskaber)
  • Opnyt til en reaktiv tilgang. Du kunne lytte til en observabel af værdiændringer i stedet for keyup-funktionen og foretage sortering og filtrering derinde. Ved at bruge observables ville du også kunne gøre rigtig fede ting som at debounce input!