Jak upgradovat filtry třídění Angularu

Úvod

AngularJS, Jednou z nejužitečnějších funkcí původní nabídky AngularJS byla možnost filtrovat a třídit data na stránce pouze pomocí proměnných šablony a filtrů. Obousměrná vazba dat si získala mnoho konvertitů k AngularJS.

Dnes však mnoho vývojářů front-endů dává přednost jednosměrné vazbě dat a tyto orderBy a filter filtry byly s příchodem Angularu na výsluní. (Poznámka: v celém článku budu používat „AngularJS“ pro označení verze 1.x a pouze „Angular“ pro označení verze 2+.)

Jak ale máme dosáhnout stejného efektu? Odpověď se skrývá v našich komponentách, takže se podíváme na projekt ngUpgrade a naučíme se, jak na to!

Krok 1 – Vytáhněte kód ze systému Git

Projdeme postupně aktualizací šablony čerstvě přepsané komponenty. Poté přidáme třídění a filtrování, abychom obnovili všechny funkce, které měla v AngularJS. To je klíčová dovednost, kterou je třeba rozvíjet pro proces ngUpgrade.

Chcete-li začít, věnujte chvíli klonování ukázkového projektu, který budeme používat (nezapomeňte spustit npm install ve složkách public i server). Podívejte se na tuto revizi, která je naším výchozím bodem:

git checkout 9daf9ab1e21dc5b20d15330e202f158b4c065bc3

Tento ukázkový projekt je hybridní projekt ngUpgrade, který využívá jak AngularJS 1.6, tak Angular 4. Má funkční rozhraní Express API a sestavení Webpack pro vývoj i produkci. Neváhejte jej prozkoumat, forknout a použít vzory ve svých vlastních projektech. Pokud se chcete podívat na verzi tohoto projektu, která používá Angular 5, podívejte se na toto repo. Pro účely tohoto návodu nebudou rozdíly mezi oběma verzemi důležité (upozorním na vše nepodstatné).

Krok 2 – Vyměňte syntaxi AngularJS

V této fázi naší aplikace je naše komponenta orders přepsána v jazyce Angular, přičemž všechny její závislosti jsou injektovány a vyřešeny. Pokud bychom se však pokusili naši aplikaci spustit, v konzoli bychom viděli chyby indikující problémy s naší šablonou. To je to, co musíme nejprve opravit. Nahradíme syntaxi AngularJS v šabloně objednávek (orders/orders.html), aby se nám načetla trasa a objednávky se zobrazily na stránce. Filtrování a třídění opravíme příště.

První věc, kterou musíme udělat, je zbavit se všech instancí $ctrl v této šabloně. V systému Angular již nejsou potřeba. Můžeme prostě provést vyhledání a nahrazení, abychom našli $ctrl. (všimněte si tečky) a nenahradili ho ničím.

Nyní nahradíme data-ng-click v našem tlačítku na řádku 13.

. V jazyce Angular místo ng-click použijeme pouze událost click se závorkami, které označují, že se jedná o událost. Závorky označují vstup a závorky označují výstup nebo událost.

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

Tady jen říkáme, že při události kliknutí spustíme funkci goToCreateOrder na naší komponentě orders.

Než budeme pokračovat, pojďme na chvíli dokázat, že se naše komponenta skutečně načítá. Zakomentujte celou funkci div, která načítá naše objednávky (od řádku 17). Chcete-li aplikaci spustit, otevřete terminál a spusťte následující příkazy:

cd servernpm start

Tím se spustí server Express. Chcete-li spustit dev server Webpack, otevřete další terminál a spusťte:

cd publicnpm run dev

(Tyto procesy můžete nechat spuštěné po zbytek tohoto tutoriálu.)

Měli byste vidět, že se naše aplikace opět načítá. Pokud přejdete na trasu objednávek, uvidíte, že se komponenta objednávek zobrazuje správně.

Snímek obrazovky aplikace

Můžeme také kliknout na tlačítko Vytvořit objednávku a správně nás to pošle na naši trasu a formulář Vytvořit objednávku.

Přejděme zpět k HTML. Odkomentujte tento div (naše aplikace bude opět nefunkční).

Nahradíme všechny ostatní instance data-ng-click obsluhou události (click). Můžete buď použít funkci Najít & Nahradit, nebo prostě použít klávesovou zkratku editoru pro výběr všech výskytů (ve VS Code pro Windows je to Ctrl+Shift+L).

Následujte nahrazení všech výskytů data-ng-show pomocí *ngIf. Ve skutečnosti neexistuje přímý ekvivalent k ng-show v jazyce Angular, ale to nevadí. Je vhodnější použít *ngIf, protože tak skutečně přidáváte a odebíráte prvky z DOM, místo abyste je pouze skrývali a zobrazovali. Stačí tedy najít naše data-ng-show a nahradit je *ngIf.

Nakonec musíme udělat dvě věci, abychom opravili tělo naší tabulky. Za prvé, nahradit data-ng-repeat za *ngFor="let order of orders". Všimněte si, že v tomto řádku odstraníme také filtry orderBy a filter, takže celý tr bude vypadat takto:

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

Druhé můžeme odstranit předponu data-ng před odkazem href na trasu detailu objednávky. AngularJS se zde stále stará o směrování, ale tento prefix již nemusíme používat, protože se nyní jedná o šablonu Angular.

Pokud se na aplikaci podíváme znovu, vidíte, že se objednávky na obrazovce načítají správně:

Objednávky ze snímku obrazovky aplikace

Je tu ovšem několik chyb. Odkazy na třídění už nefungují a teď máme tak trochu zmatek v měně, protože měnová roura v Angularu je trochu jiná než její protějšek v AngularJS. K tomu se ještě dostaneme. Prozatím je to skvělé znamení, protože to znamená, že se naše data dostávají do komponenty a načítají se na stránce. Máme tedy základy této šablony převedené do jazyka Angular. Nyní jsme připraveni řešit naše třídění a filtrování!“

Krok 3 – Přidání třídění

Na obrazovce se nám načítají naše objednávky, ale ještě nemáme způsob, jak je seřadit nebo seřadit. V AngularJS bylo opravdu běžné používat k třídění dat na stránce vestavěný filtr orderBy. Angular již filtr orderBy nemá. Je to proto, že se nyní důrazně doporučuje přesunout tento druh obchodní logiky do komponenty, místo aby byla v šabloně. Takže to je to, co zde uděláme. (Poznámka: budeme zde používat obyčejné staré funkce a události, nikoli reaktivní přístup k formuláři. Je to proto, že se snažíme dělat jen malé krůčky k pochopení těchto věcí. Jakmile zvládnete základy, neváhejte se posunout dál pomocí pozorovatelných objektů!“)

Třídění v komponentě

Filtr orderBy jsme již odstranili z ng-repeat, když jsme jej změnili na *ngFor. Nyní vytvoříme funkci třídění v komponentě orders. K volání této funkce můžeme použít události kliknutí na záhlaví naší tabulky a předat jí vlastnost, podle které chceme třídit. Také necháme tuto funkci přepínat mezi vzestupným a sestupným řazením.

Otevřeme komponentu orders (./orders/orders.component.ts) a přidáme do třídy dvě veřejné vlastnosti. Ty budou odpovídat dvěma vlastnostem, na které se naše šablona již odkazuje. První z nich bude sortType typu string. Druhá bude sortReverse typu boolean a výchozí hodnotu nastavíme na false. Vlastnost sortReverse pouze sleduje, zda se má převrátit pořadí – nepovažujte ji za synonymum pro vzestupné nebo sestupné.

Takže nyní byste měli mít za deklarací nadpisu ve třídě toto:

sortType: string;sortReverse: boolean = false;

Dále přidáme funkci, kterou budeme používat s prototypem funkce Array.sort v jazyce JavaScript. Toto přidáme za funkci goToCreateOrder (ale stále uvnitř třídy):

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

Tato dynamická funkce sort bude porovnávat hodnoty vlastností objektů v poli. Vnořená trojčlenná funkce může být na první pohled trochu složitá na pochopení, ale v podstatě jen říká, že pokud je hodnota naší vlastnosti A menší než B, vrátí -1. V opačném případě, pokud je větší, vrátíme 1. Pokud se obě hodnoty rovnají, vrátíme 0.

No, tohle není žádné super sofistikované nebo hluboké porovnávání. Existují mnohem sofistikovanější pomocné funkce, které můžete napsat, aby třídily za vás, a klidně experimentujte s tím, jak tohle můžete rozbít. Pro naše účely to však postačí a vy můžete tuto logiku prostě vyměnit za jakoukoli vlastní logiku třídění, která se vám líbí.

Takže to je naše pomocná funkce. Funkci sort na prototypu pole lze předat funkci, kterou pak může použít k porovnání položek v poli. Vytvořme na naší třídě funkci sortOrders, která toho využije pomocí nové funkce dynamicSort:

sortOrders(property) { }

První věc, kterou musíme udělat, je nastavit vlastnost sortType na naší třídě rovnou předané vlastnosti. Pak chceme přepnout vlastnost sortReverse. Budeme mít toto:

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

Nyní můžeme zavolat funkci sort na this.orders, ale předáme jí naši funkci dynamického třídění s naší vlastností:

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

A ještě musíme udělat poslední věc. Musíme jen trochu upravit naši funkci dynamicSort, abychom mohli obrátit pořadí pole pro vzestupné nebo sestupné. Abychom toho dosáhli, spojíme výsledek funkce dynamicSort s vlastností sortReverse na třídě.

První věc, kterou uděláme, je deklarace proměnné:

let sortOrder = -1;

Poté můžeme zkontrolovat, zda je naše vlastnost sortReverse na třídě true nebo false. Pokud je true, nastavíme naši proměnnou sort order na hodnotu 1:

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

Takto provážeme naše funkce, protože pro demonstraci provádíme přepínání v naší funkci sort. Abychom byli důkladnější, jiný přístup by spočíval v tom, že bychom místo sortReverse měli proměnnou s názvem sortDescending, která by byla řízena prostřednictvím samostatné funkce. Pokud se vydáte touto cestou, budete postupovat opačně – sortOrder by byla 1, pokud by sortDescending nebyla true.

Tyto dvě poslední věci bychom také mohli spojit do trojčlenného výrazu, ale kvůli přehlednosti to nechám trochu rozvláčnější. A pak, aby byl náš výsledek právě opačný než normálně, mohu prostě vynásobit result naším sortOrder. Takže naše funkce dynamicSort nyní vypadá takto:

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

Znovu opakuji, že se jedná o ukázkovou implementaci třídění, abyste pochopili klíčové koncepty použití vlastní třídicí funkce na vaší komponentě.

Uvidíme, zda třídění funguje

Dosud jsme do naší třídy přidali pomocnou funkci dynamicSort a funkci sortOrders, abychom mohli třídit na naší komponentě místo na naší šabloně.

Abychom zjistili, zda tyto funkce fungují, přidáme do naší funkce ngOnInit výchozí třídění.

Uvnitř našeho odběru forkJoin, za forEach, kde přidáváme vlastnost jméno zákazníka, zavoláme this.sortOrders a předáme vlastnost celkový počet položek:

this.sortOrders('totalItems');

Po obnovení obrazovky byste měli vidět, že se objednávky řadí podle celkového počtu položek.

Nyní stačí toto třídění implementovat do naší šablony voláním funkce sortOrders v odkazech z hlavičky tabulky.

Přidání třídění do šablony

Naší funkci sortOrders máme správně funkční v naší komponentě objednávky, což znamená, že jsme nyní připraveni ji přidat do naší šablony, aby byly hlavičky tabulek opět klikací.

Než to uděláme, změňme výchozí řazení v naší funkci ngOnInit, aby bylo jen ID:

this.sortOrders('id');

To je trochu normálnější než použití celkového počtu položek.

Nyní můžeme pracovat na naší šabloně. První věc, kterou chceme udělat, je zavolat funkci sortOrders ve všech našich událostech kliknutí. Můžete vybrat instance sortType = a nahradit je funkcí sortOrders(. Poté můžete nahradit instance ; sortReverse = !sortReverse za ).

Musíme také opravit dva názvy vlastností, které zde předáváme, stejně jako v instancích *ngIf. Nahraďte 3 instance orderId za id a 3 instance customername za customerName.

Poslední věc, kterou musím udělat, je zabalit každou ze značek href v záhlaví do závorek, aby je Angular převzal a tyto odkazy vlastně nikam nevedly. Vyvolávat se bude událost kliknutí. Záhlaví by tedy měla mít tento vzor:

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

Přejděte do prohlížeče a vyzkoušejte všechny odkazy v záhlaví tabulky. Měli byste vidět, že každá z našich vlastností se nyní řadí, a to jak vzestupně, tak sestupně. Úžasné!“

To je skvělé, ale přišli jsme o jednu věc – náš kurzor je selektor, nikoli ukazatel. Pojďme to napravit pomocí CSS.

Oprava kurzoru

Na naší stránce s objednávkami funguje řazení správně, ale náš kurzor je nyní selektor místo ukazatele, a to je nepříjemné.

Existuje několik různých způsobů, jak to můžeme pomocí CSS opravit:

  • Mohli bychom vytvořit třídu v našem hlavním souboru SCSS aplikace.
  • Mohli bychom napsat in-line CSS, i když to téměř nikdy není vhodnější.
  • Mohli bychom využít rozprostřeného CSS systému Angular pomocí možnosti stylů v dekorátoru komponenty

Volíme poslední možnost, protože stačí přidat jedno pravidlo do našich stylů pro tuto konkrétní komponentu.

Otevřete opět třídu komponenty orders. V dekorátoru komponenty můžeme přidat novou vlastnost s názvem styles. Styly jsou pole řetězců, ale řetězce jsou pravidla CSS. Abychom opravili náš kurzor, stačí vypsat pravidlo, které říká, že v řádku tabulky, pokud máme odkaz, pak změníme vlastnost kurzor na ukazatel. Náš dekorátor bude nyní vypadat takto:

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

Nyní, když najedeme myší na záhlaví našeho řádku, vidíte, že máme kurzor ukazatele. Na tomto přístupu je skvělé, že toto pravidlo CSS neovlivní žádné další komponenty. Bude se vztahovat pouze na naši komponentu objednávek!“

Nyní se podíváme, zda můžeme něco udělat s naším filtrováním. Tento „filtr filtru“ byl z aplikace Angular odstraněn, takže budeme muset být kreativní a vymyslet způsob, jak jej implementovat na naši komponentu.

Krok 4 – přidání filtrování

Jsme připraveni nahradit naše pole filtru, které používalo filtr AngularJS k prohledávání kolekce objednávek na základě hledaného řetězce. Filtr AngularJS žil v naší šabloně a nevyžadoval žádný kód v našem kontroléru nebo komponentě. V dnešní době se tento druh logiky v šabloně nedoporučuje. Je vhodnější provádět tento druh třídění a filtrování v naší třídě komponenty.

Přidání funkce filtru

Zpět v naší komponentě vytvoříme nové pole objednávek s názvem filteredOrders. Pak předáme naše pole orders do funkce filtru, která nastaví pole filteredOrders. Nakonec použijeme filteredOrders v naší šabloně v našem *ngFor místo našeho původního pole. Tímto způsobem nikdy neměníme data, která se vracejí ze serveru, pouze používáme jejich podmnožinu.

První věc, kterou uděláme, je deklarace nové vlastnosti naší třídy :

filteredOrders: Order;

Poté můžeme v naší forkJoin, která nastavuje naše původní pole objednávek, nastavit počáteční stav filteredOrders na naše pole objednávek:

this.filteredOrders = this.orders;

Nyní jsme připraveni přidat naši funkci, která za nás bude skutečně provádět filtrování. Vložte tuto funkci hned za naše třídicí funkce ve spodní části naší komponenty:

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

Povíme si, co se v této funkci děje. Nejprve funkci přidělíme řetězcovou vlastnost search. Poté projdeme smyčkou naše příkazy a vyhledáme všechny klíče objektů. U všech klíčů zjistíme, zda existují hodnoty some těchto vlastností, které odpovídají našemu hledanému výrazu. Tento kousek jazyka JavaScript může zpočátku vypadat trochu zmateně, ale v podstatě jde o to, co se děje.

Všimněte si, že v našem příkazu if explicitně testujeme řetězce. V našem příkladu právě teď omezíme náš dotaz pouze na řetězce. Nebudeme se pokoušet zabývat vnořenými vlastnostmi, číselnými vlastnostmi ani ničím podobným. Náš vyhledávací výraz bude odpovídat naší vlastnosti jméno zákazníka, a pokud se někdy rozhodneme zobrazit naši adresu nebo jinou řetězcovou vlastnost, bude prohledávat i ty.

Tuto funkci bychom samozřejmě mohli upravit tak, aby testovala i čísla nebo prohledávala další vrstvu vnořených objektů, a to je zcela na vás. Stejně jako u našeho třídění začneme s ukázkovou implementací a necháme vás, abyste použili svou představivost a udělali ji složitější.

Když už jsme u funkce sortOrders, než se posuneme dál, musíme na komponentě udělat ještě jednu věc. Stačí upravit sortOrders tak, aby nyní používala filteredOrders, a ne naši původní orders, protože chceme, aby měl filtr přednost před tříděním. Stačí ji změnit na tuto:

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

Nyní jsme připraveni implementovat toto filtrování na šabloně.

Přidání filtrování do šablony

Přejděme zpět k naší šabloně a upravme ji tak, aby používala naše filtrování.

První věc, kterou musíme udělat, je nahradit data-ng-model. Místo toho použijeme událost keyup, takže napíšeme: „keyup“ a obklopíme ji závorkami ((keyup)). Jedná se o vestavěnou událost v systému Angular, která nám umožňuje spustit funkci při stisknutí klávesy na vstupu. Protože jsme naši funkci pojmenovali filterOrders, což býval název vlastnosti, kterou jsme předávali do filtru AngularJS, stačí vedle ní přidat závorky. Náš vstup zatím vypadá takto:

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

Ale co předáme do funkce příkazů filtru? No, ve výchozím nastavení události předávají něco s názvem $event. To obsahuje něco, co se nazývá target, a to pak obsahuje hodnotu vstupu. S použitím $event je jeden problém. Je velmi obtížné sledovat tyto mlhavé typy, protože target.value může být opravdu cokoli. To ztěžuje ladění nebo zjištění, jaký typ hodnoty je očekáván. Místo toho má Angular opravdu šikovnou věc, kterou můžeme udělat, a to přiřadit tomuto vstupu proměnnou šablony.

Naštěstí nám Angular poskytuje metodu, jak to udělat. Za naši značku vstupu můžeme přidat znak hash (#) a poté název našeho požadovaného modelu. Nazvěme jej třeba #ordersFilter. Ve skutečnosti nezáleží na tom, kam to v tagu umístíte nebo jak to nazvete, ale já to rád dávám za vstup, abyste zachytili, který model je spojen s kterým vstupem, pokud se jen podívám dolů na stránku.

Nyní mohu tuto proměnnou předat do naší funkce filterOrders na události keyup. Nepotřebujeme před ní symbol hash, ale musíme přidat .value. Tím předáme skutečnou hodnotu modelu a ne celý model samotný. Náš hotový vstup vypadá takto:

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

Nakonec musíme upravit naši *ngFor tak, aby místo běžného pole orders používala pole filteredOrders:

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

Prohlédněte si produkt

Vidíte, o kolik je teď naše šablona čistší, když je naše filtrování a třídění v komponentě.

Nyní si to vyzkoušíme v prohlížeči. Pokud do pole zadáte nějaký text, například „sally“, měli byste vidět, že se naše pořadí mění a že nad ním funguje třídění:
Animace fungující aplikace
Skvělé, nahradili jsme další funkci AngularJS!

Nyní nám zbývá poslední věc, kterou musíme na této komponentě udělat – opravit měnovou rouru.

Krok 5 – Oprava měnové roury

Naším posledním krokem je aktualizace bývalého měnového filtru, který se nyní v systému Angular nazývá měnová roura. Stačí, když do roury v šabloně přidáme několik parametrů, které jsme v AngularJS nemuseli zadávat. Tato část se liší, pokud používáte Angular 4 nebo Angular 5:.

V Angularu 4 proveďte toto:
<td>{{order.totalSale | currency:'USD':true}}</td>

V Angularu 5+ proveďte toto:
<td>{{order.totalSale | currency:'USD':'symbol'}}</td>

První možností je kód měny (je jich spousta, nejste omezeni na americké dolary!). Druhou možností je zobrazení symbolu. V Angularu 4 je to boolean, který určuje, zda se má použít symbol měny, nebo kód. V Angularu 5+ jsou možnosti symbol, code nebo symbol-narrow jako řetězce.

Měli byste nyní vidět očekávaný symbol:
Snímek obrazovky sloupce Total Sale aplikace

A máme hotovo! Chcete-li se podívat na hotový kód, podívejte se na tuto revizi.

Závěr

Vydržel jsi s tím až do konce! Zde je přehled toho, čeho jsme v této příručce dosáhli:

  1. Záměna syntaxe šablony AngularJS syntaxí Angularu
  2. Přesunutí třídění do komponenty
  3. Použití oborových stylů CSS
  4. Přesunutí filtrování do komponenty
  5. Záměna filtru měny AngularJS měnovou rourou Angularu

Kam byste měli pokračovat? Je spousta věcí, které byste mohli udělat:

  • Udělat řazení sofistikovanější (například: mělo by se po kliknutí na novou hlavičku řazení resetovat, nebo zůstat stejné?)
  • Udělat filtrování sofistikovanější (hledat čísla nebo vnořené vlastnosti)
  • Přejít na reaktivní přístup. Místo funkce keyup byste mohli naslouchat pozorovatelným změnám hodnot a provádět třídění a filtrování v nich. Použití pozorovatelných objektů by vám také umožnilo dělat opravdu skvělé věci, jako je debounce vstupu!

.