Come aggiornare i filtri di ordinamento di Angular

Introduzione

AngularJS, Una delle caratteristiche più utili dell’offerta iniziale di AngularJS era la capacità di filtrare e ordinare i dati sulla pagina usando solo variabili di template e filtri. Il data binding bidirezionale ha conquistato molti convertiti ad AngularJS.

Oggi, però, molti sviluppatori front-end preferiscono il data binding unidirezionale, e quei filtri orderBy e filter sono tramontati con l’avvento di Angular. (Nota: in questo articolo userò “AngularJS” per riferirmi alla 1.x e solo “Angular” per riferirmi alla 2+.)

Ma come possiamo ottenere lo stesso effetto? La risposta sta nei nostri componenti, quindi guardiamo un progetto ngUpgrade e impariamo come farlo!

Step 1 – Pull Code from Git

Passeremo attraverso l’aggiornamento del template di un componente appena riscritto. Poi, aggiungeremo l’ordinamento e il filtraggio per ripristinare tutte le funzionalità che aveva in AngularJS. Questa è un’abilità chiave da sviluppare per il processo ngUpgrade.

Per iniziare, prenditi un momento per clonare il progetto di esempio che useremo (non dimenticare di eseguire npm install in entrambe le cartelle public e server). Controlla questo commit per il nostro punto di partenza:

git checkout 9daf9ab1e21dc5b20d15330e202f158b4c065bc3

Questo progetto di esempio è un progetto ibrido ngUpgrade che usa sia AngularJS 1.6 che Angular 4. Ha un’API Express funzionante e una build di Webpack sia per lo sviluppo che per la produzione. Sentitevi liberi di esplorare, fare un fork e usare i pattern nei vostri progetti. Se volete vedere una versione di questo progetto che usa Angular 5, guardate questo repo. Per gli scopi di questo tutorial, le differenze tra le due versioni non avranno importanza (sottolineerò qualsiasi cosa minore).

Step 2 – Sostituire la sintassi di AngularJS

A questo punto della nostra applicazione, il nostro componente ordini è riscritto in Angular, con tutte le sue dipendenze iniettate e risolte. Se provassimo ad eseguire la nostra applicazione, però, vedremmo degli errori nella console che indicano problemi con il nostro template. Questo è ciò che dobbiamo risolvere prima. Sostituiremo la sintassi di AngularJS nel template degli ordini (orders/orders.html) in modo da poter caricare il percorso e visualizzare gli ordini sulla pagina. Sistemeremo il filtraggio e l’ordinamento dopo.

La prima cosa che dobbiamo fare è sbarazzarci di tutte le istanze di $ctrl in questo template. Non sono più necessari in Angular. Possiamo semplicemente fare un trova e sostituisci per trovare $ctrl. (nota il punto), e sostituirlo con niente.

Ora sostituiamo il data-ng-click nel nostro pulsante alla linea 13. In Angular, invece di ng-click, usiamo semplicemente l’evento click, con le parentesi per indicare che è un evento. Le parentesi indicano un input, e le parentesi indicano un output o un evento.

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

Stiamo solo dicendo che sull’evento click, attiviamo la funzione goToCreateOrder sul nostro componente ordini.

Prima di continuare, prendiamoci un minuto per provare che il nostro componente si sta effettivamente caricando. Commentate tutto il div che carica i nostri ordini (dalla linea 17 in poi). Per eseguire l’applicazione, aprite un terminale ed eseguite i seguenti comandi:

cd servernpm start

Questo avvierà il server Express. Per eseguire il server Webpack dev, apri un altro terminale ed esegui:

cd publicnpm run dev

(Puoi mantenere questi processi in esecuzione per il resto di questo tutorial.)

Dovresti vedere che la nostra applicazione si sta caricando di nuovo. Se vai alla rotta orders, vedrai che il componente orders viene visualizzato correttamente.

Schermo dell'applicazione

Possiamo anche cliccare sul pulsante Create Order e ci manderà correttamente alla nostra rotta Create Order e al modulo.

Ok, torniamo all’HTML. Togliete il commento a quel div (la nostra app sarà di nuovo rotta).

Sostituiamo tutto il resto delle istanze data-ng-click con il gestore di eventi (click). Potete usare Find & Replace o semplicemente usare la scorciatoia del vostro editor per selezionare tutte le occorrenze (in VS Code for Windows, questa è Ctrl+Shift+L).

Poi, sostituite tutte le occorrenze di data-ng-show con *ngIf. In realtà non c’è un equivalente diretto a ng-show in Angular, ma va bene così. È preferibile usare *ngIf, perché in questo modo stai effettivamente aggiungendo e rimuovendo elementi dal DOM invece di nasconderli e mostrarli e basta. Quindi, tutto quello che dobbiamo fare è trovare i nostri data-ng-show e sostituirli con *ngIf.

Infine, dobbiamo fare due cose per sistemare il corpo della nostra tabella. Primo, sostituire data-ng-repeat con *ngFor="let order of orders". Notate che stiamo anche rimuovendo i filtri orderBy e filter in quella linea in modo che l’intero tr assomigli a questo:

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

In secondo luogo, possiamo eliminare il prefisso data-ng prima del collegamento href al percorso dei dettagli dell’ordine. AngularJS sta ancora gestendo il routing qui, ma non abbiamo più bisogno di usare quel prefisso dato che questo è ora un template Angular.

Se guardiamo di nuovo l’applicazione, si può vedere che gli ordini vengono caricati correttamente sullo schermo:

Ordini dalla schermata dell'app

Ci sono un paio di cose sbagliate, naturalmente. I link di ordinamento non funzionano più, e ora la nostra valuta è un po’ incasinata perché il tubo della valuta in Angular è leggermente diverso dalla sua controparte AngularJS. Ci arriveremo. Per ora, questo è un ottimo segno, perché significa che i nostri dati stanno arrivando al componente e si stanno caricando sulla pagina. Quindi, abbiamo le basi di questo template convertito in Angular. Ora siamo pronti per affrontare l’ordinamento e il filtraggio!

Passo 3 – Aggiungere l’ordinamento

Abbiamo i nostri ordini che si caricano sullo schermo, ma non abbiamo ancora un modo per ordinarli o ordinarli. In AngularJS, era molto comune usare il filtro integrato orderBy per ordinare i dati sulla pagina. Angular non ha più un filtro orderBy. Questo perché ora è fortemente incoraggiato a spostare quel tipo di logica di business nel componente invece di averla nel template. Quindi, questo è ciò che faremo qui. (Nota: qui useremo semplici funzioni ed eventi, non un approccio reattivo ai moduli. Questo perché stiamo cercando di fare dei piccoli passi nella comprensione di queste cose. Una volta che hai capito le basi, sentiti libero di andare oltre con gli osservabili!)

Ordinamento nel componente

Abbiamo già rimosso il filtro orderBy da ng-repeat quando lo abbiamo cambiato in *ngFor. Ora faremo una funzione di ordinamento sul componente ordini. Possiamo usare gli eventi click sulle nostre intestazioni di tabella per chiamare questa funzione e passare la proprietà che vogliamo ordinare. Faremo anche in modo che questa funzione passi avanti e indietro tra ascendente e discendente.

Apriamo il componente orders (./orders/orders.component.ts) e aggiungiamo due proprietà pubbliche alla classe. Queste corrisponderanno alle due proprietà a cui il nostro template fa già riferimento. La prima sarà sortType di tipo string. La seconda sarà sortReverse di tipo boolean e imposteremo il valore di default a false. La proprietà sortReverse tiene solo traccia di se invertire l’ordine – non pensatela come un sinonimo di ascendente o discendente.

Ora dovreste avere questo dopo la dichiarazione del titolo nella classe:

sortType: string;sortReverse: boolean = false;

Prossimo, aggiungeremo la funzione che useremo con il prototipo Array.sort in JavaScript. Aggiungetela dopo la funzione goToCreateOrder (ma ancora all’interno della classe):

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

Questa funzione di ordinamento dinamico confronterà il valore delle proprietà degli oggetti in un array. La funzione ternaria annidata può essere un po’ difficile da capire a prima vista, ma fondamentalmente sta solo dicendo che se il valore della nostra proprietà di A è inferiore a B, ritorna -1. Altrimenti, se è maggiore, restituisce 1. Se i due sono uguali, restituisce 0.

Ora, questo non è un confronto super sofisticato o profondo. Ci sono funzioni di aiuto molto più sofisticate che potreste scrivere per ordinare per voi, e sentitevi liberi di sperimentare come potete rompere questo. Andrà bene per i nostri scopi, però, e potete semplicemente scambiare questa logica con qualsiasi logica di ordinamento personalizzata che vi piace.

Quindi questa è la nostra funzione di aiuto. La funzione di ordinamento sul prototipo Array può essere passata ad una funzione che può essere usata per confrontare gli elementi di un array. Creiamo una funzione chiamata sortOrders sulla nostra classe che sfrutta questo con la nuova funzione dynamicSort:

sortOrders(property) { }

La prima cosa che dobbiamo fare è impostare la proprietà sortType sulla nostra classe uguale alla proprietà passata. Poi vogliamo attivare la proprietà sortReverse. Avremo questo:

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

Ora possiamo chiamare la funzione sort su this.orders, ma passare la nostra funzione di ordinamento dinamico con la nostra proprietà:

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

E c’è un’ultima cosa che dobbiamo fare. Dobbiamo modificare un po’ la nostra funzione dynamicSort per essere in grado di invertire l’ordine dell’array per ascendente o discendente. Per farlo, legheremo il risultato della dynamicSort alla proprietà sortReverse della classe.

La prima cosa che faremo è dichiarare una variabile:

let sortOrder = -1;

Poi, possiamo controllare se la nostra proprietà sortReverse sulla nostra classe è vera o falsa. Se è vero, imposteremo la nostra variabile di ordinamento a 1:

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

Colleghiamo le nostre funzioni in questo modo perché stiamo facendo un toggle nella nostra funzione di ordinamento a scopo dimostrativo. Per essere più precisi, un altro approccio sarebbe quello di avere una variabile chiamata sortDescending invece di sortReverse che è controllata attraverso una funzione separata. Se prendete questa strada, farete il contrario – sortOrder sarebbe 1 a meno che sortDescending sia vero.

Potremmo anche combinare queste ultime due cose in un’espressione ternaria, ma per il bene della chiarezza, la lascerò un po’ più verbosa. E poi per rendere il nostro risultato l’opposto di quello che sarebbe normalmente, posso semplicemente moltiplicare result per il nostro sortOrder. Quindi la nostra funzione dynamicSort ora assomiglia a questa:

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

Ancora una volta, questa è un’implementazione dimostrativa dell’ordinamento, in modo che tu capisca i concetti chiave dell’uso di una funzione di ordinamento personalizzata sul tuo componente.

Vediamo se l’ordinamento funziona

Finora, abbiamo aggiunto una funzione helper dynamicSort e una funzione sortOrders alla nostra classe in modo che possiamo ordinare sul nostro componente invece che sul nostro template.

Per vedere se queste funzioni funzionano, aggiungiamo un ordinamento predefinito alla nostra funzione ngOnInit.

All’interno della nostra sottoscrizione forkJoin, dopo la forEach dove aggiungiamo la proprietà nome cliente, chiamiamo this.sortOrders e passiamo la proprietà totale articoli:

this.sortOrders('totalItems');

Quando la schermata si aggiorna, dovresti vedere che gli ordini vengono ordinati per il totale articoli.

Ora abbiamo solo bisogno di implementare questo ordinamento sul nostro template chiamando la funzione sortOrders nei link dell’intestazione della tabella.

Aggiungere l’ordinamento al template

Abbiamo la nostra funzione sortOrders che funziona correttamente sul nostro componente ordini, il che significa che ora siamo pronti ad aggiungerla al nostro template in modo che le intestazioni della tabella siano di nuovo cliccabili.

Prima di farlo, cambiamo l’ordinamento di default nella nostra funzione ngOnInit per essere solo ID:

this.sortOrders('id');

Questo è un po’ più normale che usare il totale degli articoli.

Ora possiamo lavorare sul nostro template. La prima cosa che vogliamo fare è chiamare la funzione sortOrders in tutti i nostri eventi click. Potete selezionare le istanze di sortType = e sostituirle con sortOrders(. Poi, potete sostituire le istanze di ; sortReverse = !sortReverse con ).

Abbiamo anche bisogno di correggere due dei nomi delle proprietà che stiamo passando qui, così come nelle istanze di *ngIf. Sostituite le 3 istanze di orderId con id e le 3 istanze di customername con customerName.

L’ultima cosa che devo fare è avvolgere ciascuno dei tag href nelle intestazioni tra parentesi in modo che Angular prenda il controllo e questi link non vadano da nessuna parte. L’evento click sarà la cosa che verrà sparata. Quindi, le intestazioni dovrebbero seguire questo schema:

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

Passa al browser e prova tutti i tuoi link di intestazione della tabella. Dovreste vedere che ognuna delle nostre proprietà ora è ordinata, sia in ordine crescente che decrescente. Fantastico!

Questo è fantastico, ma abbiamo perso una cosa – il nostro cursore è un selettore, non un puntatore. Sistemiamo la cosa con un po’ di CSS.

Fix the Cursor

Abbiamo ottenuto il nostro ordinamento che funziona correttamente sulla nostra pagina degli ordini, ma il nostro cursore è ora un selettore invece di un puntatore, e questo è fastidioso.

Ci sono un paio di modi diversi in cui potremmo usare i CSS per risolvere questo problema:

  • Potremmo creare una classe nel file SCSS della nostra app principale.
  • Potremmo scrivere CSS in linea, anche se questo non è quasi mai preferibile.
  • Potremmo approfittare dello scoped CSS di Angular usando l’opzione styles nel decoratore del componente

Andremo con l’ultima opzione, perché tutto quello che dobbiamo fare è aggiungere una regola ai nostri stili per questo particolare componente.

Apriamo di nuovo la classe del componente ordini. Nel decoratore del componente, possiamo aggiungere una nuova proprietà chiamata styles. Styles è un array di stringhe, ma le stringhe sono regole CSS. Per fissare il nostro cursore, tutto quello che dobbiamo fare è scrivere una regola che dica che in una riga di tabella, se abbiamo un link, allora cambiamo la proprietà cursore in puntatore. Il nostro decoratore sarà ora simile a questo:

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

Ora, quando passiamo sopra le nostre intestazioni di riga, vedete che abbiamo il cursore puntatore. La cosa bella di questo approccio è che questa regola CSS non influenzerà nessun altro componente. Si applicherà solo al nostro componente ordini!

Ora, vediamo se possiamo fare qualcosa per il nostro filtro. Quel “filtro filtro” è stato rimosso da Angular, quindi dovremo essere creativi e trovare un modo per implementarlo sul nostro componente.

Step 4 – Aggiungere il filtraggio

Siamo pronti a sostituire il nostro box filtro che usava il filtro di AngularJS per cercare nella collezione di ordini in base a una stringa che stavamo cercando. Il filtro AngularJS viveva sul nostro template e non richiedeva alcun codice nel nostro controller o componente. Al giorno d’oggi, questo tipo di logica nel template è sconsigliato. È preferibile fare questo tipo di ordinamento e filtraggio nella nostra classe di componente.

Aggiungi una funzione di filtro

Nel nostro componente, creeremo un nuovo array di ordini chiamato filteredOrders. Poi passeremo il nostro array orders in una funzione filtro che imposta l’array filteredOrders. Infine, useremo il filteredOrders sul nostro template nel nostro *ngFor invece del nostro array originale. In questo modo non modificheremo mai i dati che tornano dal server, ne useremo solo un sottoinsieme.

La prima cosa che faremo è dichiarare la nuova proprietà sulla nostra classe :

filteredOrders: Order;

Poi, nel nostro forkJoin che imposta il nostro array originale di ordini, possiamo impostare lo stato iniziale di filteredOrders sul nostro array ordini:

this.filteredOrders = this.orders;

Ora siamo pronti per aggiungere la nostra funzione che effettivamente farà il filtraggio per noi. Incolliamo questa funzione subito dopo le nostre funzioni di ordinamento nella parte inferiore del nostro componente:

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

Parliamo di cosa succede in questa funzione. Per prima cosa, diamo alla funzione una proprietà stringa di search. Poi, facciamo un ciclo attraverso i nostri ordini e poi troviamo tutte le chiavi degli oggetti. Per tutte le chiavi, vedremo se ci sono valori some di quelle proprietà che corrispondono al nostro termine di ricerca. Questo pezzo di JavaScript può sembrare un po’ confuso all’inizio, ma è fondamentalmente quello che sta succedendo.

Nota che, nella nostra dichiarazione if, stiamo testando esplicitamente le stringhe. Nel nostro esempio ora stiamo solo limitando la nostra query alle stringhe. Non cercheremo di trattare le proprietà annidate, le proprietà numeriche o qualcosa del genere. Il nostro termine di ricerca corrisponderà alla nostra proprietà “nome cliente”, e se mai sceglieremo di visualizzare il nostro indirizzo o qualsiasi altra proprietà stringa, cercherà anche in quelle.

Naturalmente, potremmo anche modificare questa funzione per testare i numeri, o guardare attraverso un altro livello di oggetti annidati, e questo è totalmente a vostra scelta. Proprio come con il nostro ordinamento, inizieremo con un’implementazione dimostrativa e vi lasceremo usare la vostra immaginazione per renderla più complessa.

A proposito della funzione sortOrders, prima di andare avanti, dobbiamo fare un’ultima cosa sul componente. Dobbiamo solo modificare sortOrders per usare ora filteredOrders e non il nostro originale orders, perché vogliamo che il filtro abbia la priorità sull’ordinamento. Basta cambiarlo in questo:

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

Ora siamo pronti per implementare questo filtraggio sul template.

Aggiungi filtro al template

Torniamo al nostro template e sistemiamolo per usare il nostro filtro.

La prima cosa che dobbiamo fare è sostituire data-ng-model. Al suo posto, useremo l’evento keyup, quindi scriveremo “keyup” e lo circonderemo di parentesi ((keyup)). Questo è un evento incorporato in Angular che ci permette di eseguire una funzione sulla pressione di un input. Dato che abbiamo chiamato la nostra funzione filterOrders, che era il nome della proprietà che passavamo nel filtro di AngularJS, dobbiamo solo aggiungere le parentesi accanto ad essa. Il nostro input assomiglia finora a questo:

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

Ma cosa passiamo nella funzione ordini del filtro? Beh, per default, gli eventi passano qualcosa chiamato $event. Questo contiene qualcosa chiamato target, che poi contiene il valore dell’input. C’è un problema nell’usare $event. È molto difficile tenere traccia di questi tipi nebulosi perché target.value potrebbe essere davvero qualsiasi cosa. Questo rende difficile il debug o sapere quale tipo di valore è atteso. Invece, Angular ha una cosa davvero ingegnosa che possiamo fare, che è quella di assegnare una variabile template a questo input.

Per fortuna, Angular ci fornisce un metodo per farlo. Dopo il nostro tag di input, possiamo aggiungere il segno hash (#) e poi il nome del nostro modello desiderato. Chiamiamolo #ordersFilter. In realtà non importa dove nel tag metti questo o come lo chiami, ma mi piace metterlo dopo l’input in modo da catturare quale modello è associato a quale input se solo do un’occhiata alla pagina.

Ora posso passare quella variabile nella nostra funzione filterOrders sull’evento keyup. Non abbiamo bisogno del simbolo hash prima, ma dobbiamo aggiungere .value. Questo passerà il valore effettivo del modello e non l’intero modello stesso. Il nostro input finito assomiglia a questo:

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

Infine, dobbiamo modificare il nostro *ngFor per usare l’array filteredOrders invece del normale array orders:

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

Ispeziona il Prodotto

Puoi vedere quanto è più pulito il nostro template ora che il nostro filtraggio e ordinamento è nel componente.

Ora controlliamo questo nel browser. Se inserite del testo nella casella, come “sally”, dovreste vedere che i nostri ordini stanno cambiando e che l’ordinamento funziona sopra di esso:
Animazione dell'app funzionante
Fantastico, abbiamo sostituito un’altra caratteristica di AngularJS!

Ora abbiamo solo un’ultima cosa da fare su questo componente – aggiustare il currency pipe.

Step 5 – Aggiustare il currency pipe

Il nostro tocco finale è aggiornare il vecchio filtro currency, che ora è chiamato currency pipe in Angular. Abbiamo solo bisogno di aggiungere un paio di parametri al tubo nel template che non abbiamo dovuto specificare in AngularJS. Questa parte differisce se stai usando Angular 4 o Angular 5:.

In Angular 4, fai così:
<td>{{order.totalSale | currency:'USD':true}}</td>

In Angular 5+, fai così:
<td>{{order.totalSale | currency:'USD':'symbol'}}</td>

La prima opzione è il codice della valuta (ce ne sono molti, non sei limitato ai dollari USA!) La seconda è la visualizzazione del simbolo. In Angular 4, questo è un booleano che indica se usare il simbolo della valuta o il codice. In Angular 5+, le opzioni sono symbol, code, o symbol-narrow come stringhe.

Ora dovresti vedere il simbolo previsto:
Screenshot della colonna Total Sale dell'app

E abbiamo finito! Per vedere il codice finito, controlla questo commit.

Conclusione

Hai fatto un ottimo lavoro continuando fino alla fine! Ecco cosa abbiamo realizzato in questa guida:

  1. Sostituzione della sintassi dei template AngularJS con la sintassi Angular
  2. Spostamento dell’ordinamento nel componente
  3. Utilizzo degli stili CSS con scoped
  4. Spostamento del filtraggio nel componente
  5. Sostituzione del filtro valuta AngularJS con il tubo valuta Angular

Dove si dovrebbe andare da qui? Ci sono molte cose che potresti fare:

  • Rendere l’ordinamento più sofisticato (per esempio: l’ordinamento dovrebbe resettare o rimanere lo stesso quando l’utente clicca su una nuova intestazione?)
  • Rendere il filtraggio più sofisticato (ricerca di numeri o proprietà annidate)
  • Cambiare con un approccio reattivo. Potreste ascoltare un osservabile dei cambiamenti di valore invece della funzione keyup e fare l’ordinamento e il filtraggio lì. L’uso di osservabili vi permetterebbe anche di fare cose molto belle come il debounce dell’input!

.