Como atualizar os filtros de classificação angular

Introdução

AngularJS, Uma das características mais úteis da oferta inicial da AngularJS foi a capacidade de filtrar e classificar dados na página usando apenas variáveis e filtros de modelo. Vinculação de dados bidirecionais ganhou muitos convertidos para AngularJS.

Hoje, no entanto, muitos desenvolvedores front-end preferem a vinculação de dados one-way, e aqueles orderBy e filter filtros têm sido por do sol com o advento do Angular. (Nota: ao longo deste artigo estarei usando “AngularJS” para me referir a 1.x e apenas “Angular” para me referir a 2+.)

Mas como é que vamos conseguir esse mesmo efeito? A resposta está em nossos componentes, então vamos olhar para um projeto ngUpgrade e aprender como fazer isso!

Passo 1 – Pull Code from Git

Passamos a atualizar o template de um componente reescrito recentemente. Depois, vamos adicionar classificação e filtragem para restaurar toda a funcionalidade que tinha no AngularJS. Esta é uma habilidade chave a desenvolver para o processo ngUpgrade.

Para começar, tome um momento para clonar o projeto modelo que vamos usar (não se esqueça de executar npm install em ambas as pastas public e server). Veja este commit para o nosso ponto de partida:

git checkout 9daf9ab1e21dc5b20d15330e202f158b4c065bc3

Este projecto de amostra é um projecto híbrido ngUpgrade que usa tanto o AngularJS 1.6 como o Angular 4. Tem uma API Express funcional e um Webpack construído tanto para desenvolvimento como para produção. Sinta-se à vontade para explorar, garfo, e usar os padrões em seus próprios projetos. Se você gostaria de ver uma versão deste projeto que usa o Angular 5, confira este repo. Para os propósitos deste tutorial, as diferenças entre as duas versões não importam (vou apontar qualquer coisa menor).

Passo 2 – Substituir a sintaxe AngularJS

Neste estágio de nossa aplicação, nosso componente de pedidos é reescrito em Angular, com todas as suas dependências injetadas e resolvidas. Se tentarmos executar nossa aplicação, porém, veremos erros na console indicando problemas com o nosso template. Isso é o que precisamos corrigir primeiro. Vamos substituir a sintaxe do AngularJS no template de ordens (orders/orders.html) para que possamos obter o carregamento da rota e as ordens exibidas na página. Vamos corrigir a filtragem e classificação a seguir.

A primeira coisa que precisamos de fazer é livrar-nos de todas as instâncias de $ctrl neste template. Elas não são mais necessárias em Angular. Podemos apenas fazer um find e substituir por $ctrl. (note o ponto), e substitui-lo por nada.

Agora vamos substituir o data-ng-click no nosso botão da linha 13. Em Angular, em vez de ng-click, usamos apenas o evento click, com parênteses para indicar que se trata de um evento. Parênteses indicam uma entrada, e parênteses indicam uma saída ou um evento.

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

Estamos apenas dizendo aqui que no evento clique, dispare a função goToCreateOrder no nosso componente de ordens.

Antes de continuarmos, vamos tirar um minuto para provar que o nosso componente está realmente carregando. Comente o conjunto div que carrega as nossas ordens (a partir da linha 17). Para executar a aplicação, abra um terminal e execute os seguintes comandos:

cd servernpm start

Que irá iniciar o servidor Express. Para executar o servidor Webpack dev, abra outro terminal e execute:

cd publicnpm run dev

(Você pode manter esses processos em execução durante o restante deste tutorial.)

Você deve ver que nossa aplicação está carregando novamente. Se você for para a rota de ordens, você verá que o componente de ordens está exibindo corretamente.

Ap screenshot

>

Também podemos clicar no botão Create Order e ele nos enviará corretamente para nossa rota e formulário Create Order.

Okay, vamos voltar para o HTML. Descomente que div (nosso aplicativo será quebrado novamente).

Substitua todas as demais instâncias data-ng-click pelo gerenciador de eventos (click). Você pode usar o Localizador & Substituir ou simplesmente usar o atalho do seu editor para selecionar todas as ocorrências (em VS Code for Windows, isto é Ctrl+Shift+L).

Próximo, substitua todas as ocorrências de data-ng-show por *ngIf. Na verdade não há um equivalente directo a ng-show em Angular, mas não faz mal. É preferível usar *ngIf, porque dessa forma você está realmente adicionando e removendo elementos do DOM ao invés de apenas escondê-los e mostrá-los. Então, tudo o que precisamos fazer é encontrar os nossos data-ng-shows e substituí-los por *ngIf.

Finalmente, precisamos de fazer duas coisas para fixar o corpo da nossa mesa. Primeiro, substitua data-ng-repeat por *ngFor="let order of orders". Note que também estamos removendo os filtros orderBy e filter nessa linha para que o inteiro tr fique assim:

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

Segundo, podemos apagar o prefixo data-ng antes do link href para a rota de detalhes da ordem. AngularJS ainda está lidando com o roteamento aqui, mas não precisamos mais usar esse prefixo já que agora este é um template Angular.

Se olharmos para o aplicativo novamente, você pode ver que os pedidos estão carregando corretamente na tela:

 Encomendas da tela do aplicativo

Há algumas coisas erradas com ele, é claro. Os links de ordenação já não funcionam, e agora a nossa moeda está um pouco confusa porque o tubo da moeda em Angular é ligeiramente diferente da sua contraparte AngularJS. Vamos chegar a isso. Por enquanto, isto é um ótimo sinal, porque significa que nossos dados estão chegando ao componente e carregando na página. Então, temos o básico deste modelo convertido para Angular. Agora estamos prontos para lidar com nossa ordenação e filtragem!

Passo 3 – Adicionando Ordenação

Temos nossas ordens carregando na tela, mas ainda não temos uma maneira de ordenar ou classificar. No AngularJS, era muito comum usar o filtro incorporado orderBy para ordenar os dados na página. O AngularJS já não tem um filtro orderBy. Isto porque agora é fortemente encorajado a mover esse tipo de lógica de negócio para o componente em vez de tê-lo no template. Então, é isso que vamos fazer aqui. (Nota: vamos usar funções e eventos antigos, não uma abordagem de forma reativa. Isto é porque estamos apenas a tentar dar passos de bebé para compreender estas coisas. Uma vez que você tenha o básico, sinta-se à vontade para levá-lo adiante com observáveis!)

Selecionar no Componente

Já removemos o filtro orderBy de ng-repeat quando o alteramos para *ngFor. Agora vamos fazer uma função de ordenação no componente de ordens. Podemos usar os eventos de clique em nossos cabeçalhos de tabela para chamar essa função e passar na propriedade pela qual queremos ordenar. Também vamos ter essa função alternando entre ascendente e descendente.

Vamos abrir o componente de ordens (./orders/orders.component.ts) e adicionar duas propriedades públicas à classe. Estas vão corresponder às duas propriedades que o nosso template já refere. A primeira será sortType do tipo string. A segunda será sortReverse do tipo boolean e vamos definir o valor padrão como falso. A propriedade sortReverse apenas mantém a ordem – não pense nela como um sinónimo de ascendente ou descendente.

Então agora você deve ter isto após a declaração do título na classe:

sortType: string;sortReverse: boolean = false;

Next, vamos adicionar a função que vamos usar com a função Array.sort prototype em JavaScript. Adicione isto após a função goToCreateOrder (mas ainda dentro da classe):

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

Esta função de ordenação dinâmica irá comparar o valor da propriedade dos objetos em um array. A função ternária aninhada pode ser um pouco complicada de entender à primeira vista, mas está basicamente dizendo que se o valor da nossa propriedade de A é menor que B, retorne -1. Caso contrário, se for maior, retorne 1. Se os dois forem iguais, retorne 0.

Agora, isto não é uma comparação super sofisticada ou profunda. Há funções de ajuda muito mais sofisticadas que você poderia escrever para classificar para você, e sinta-se livre para experimentar como você pode quebrar esta. No entanto, isto serve para os nossos propósitos, e você pode simplesmente trocar esta lógica com qualquer lógica de ordenação personalizada que você goste.

Então esta é a nossa função de helper. A função de ordenação no protótipo do Array pode ser passada uma função que pode então ser usada para comparar itens em um array. Vamos fazer uma função chamada sortOrders na nossa classe que tire vantagem disso com a nova dynamicSort function:

sortOrders(property) { }

A primeira coisa que precisamos fazer é definir a propriedade sortType na nossa classe igual à propriedade que é passada. Depois queremos alternar a propriedade sortReverse. Vamos ter isto:

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

Agora podemos chamar a função sort em this.orders, mas passar na nossa função de ordenação dinâmica com a nossa propriedade:

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

E há uma última coisa que precisamos de fazer. Precisamos modificar a nossa função dynamicSort apenas um pouco para poder inverter a ordem do array para ascendente ou descendente. Para fazer isso, vamos amarrar o resultado do dynamicSort à propriedade sortReverse na classe.

A primeira coisa que vamos fazer é declarar uma variável:

let sortOrder = -1;

Então, podemos verificar se a nossa sortReverse propriedade na nossa classe é verdadeira ou falsa. Se for verdade, vamos definir a nossa variável de ordenação igual a 1:

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

Estamos a ligar as nossas funções assim porque estamos a fazer uma alternância na nossa função de ordenação para fins de demonstração. Para ser mais completo, outra abordagem seria ter uma variável chamada sortDescending ao invés de sortReverse que é controlada através de uma função separada. Se você seguir esta rota, você fará o oposto – sortOrder seria 1 a menos que sortDescending fosse verdade.

Também poderíamos combinar estas duas últimas coisas em uma expressão ternária, mas por uma questão de clareza, vou deixá-la um pouco mais verbosa. E para fazer do nosso resultado o oposto do que normalmente seria, eu posso multiplicar result pelo nosso sortOrder. Então nossa função dynamicSort agora se parece com isto:

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

Again, esta é uma implementação demonstrativa de ordenação, para que você entenda os conceitos chave de usar uma função de ordenação personalizada em seu componente.

Vejamos se a ordenação funciona

Até agora, adicionamos uma função dynamicSort helper e uma função sortOrders à nossa classe, para que possamos ordenar em nosso componente ao invés de em nosso template.

Para ver se estas funções estão a funcionar, vamos adicionar uma ordenação padrão à nossa ngOnInit function.

Inside of our forkJoin subscription, after the forEach where we add the customer name property, let’s call this.sortOrders and pass in the total items property:

this.sortOrders('totalItems');

When the screen refreshhes, you should see that the orders are being sorted by the total items.

Agora só precisamos de implementar esta ordenação no nosso template chamando a função sortOrders nos links do cabeçalho da tabela.

Adicionar ordenação ao template

Temos a nossa função sortOrders a funcionar correctamente no nosso componente de ordens, o que significa que agora estamos prontos para a adicionar ao nosso template para que os cabeçalhos da tabela possam ser clicados novamente.

Antes disso, vamos mudar a ordenação padrão na nossa função ngOnInit para ser apenas ID:

this.sortOrders('id');

Isso é um pouco mais normal do que usar o total de itens.

Agora podemos trabalhar no nosso template. A primeira coisa que queremos fazer é chamar a função sortOrders em todos os nossos eventos de clique. Você pode selecionar as instâncias de sortType = e substituí-las por sortOrders(. Depois, pode substituir as instâncias de ; sortReverse = !sortReverse por ).

Também precisamos corrigir dois dos nomes das propriedades que estamos passando aqui, assim como nas instâncias de *ngIf. Substitua as 3 instâncias de orderId por id e as 3 instâncias de customername por customerName.

A última coisa que eu preciso fazer é embrulhar cada uma das href tags nos cabeçalhos entre parênteses para que a Angular assuma o controle e estes links não vão a lugar algum. O evento do clique será a coisa que está disparada. Então, os cabeçalhos devem seguir este padrão:

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

Vá até o navegador e teste todos os links do cabeçalho da sua tabela. Você deve ver que cada uma de nossas propriedades agora ordena, tanto em ordem ascendente quanto descendente. Incrível!

Isso é ótimo, mas perdemos uma coisa – o nosso cursor é um seletor, não um ponteiro. Vamos corrigir isso com algum CSS.

Fix o Cursor

Temos nossa ordenação funcionando corretamente em nossa página de ordens, mas nosso cursor agora é um seletor ao invés de um ponteiro, e isso é chato.

Existem algumas formas diferentes de corrigir isso com o CSS:

  • Podíamos fazer uma classe no nosso ficheiro SCSS principal da aplicação.
  • Podíamos escrever CSS em linha, embora isso quase nunca seja preferível.
  • Podíamos tirar vantagem do CSS escopado da Angular usando a opção estilos no decorador de componentes

Vamos escolher a última opção, porque tudo o que precisamos fazer é adicionar uma regra aos nossos estilos para este componente em particular.

Abrir novamente a classe de componentes de ordens. No decorador de componentes, nós podemos adicionar uma nova propriedade chamada styles. Styles é um array de strings, mas as strings são regras CSS. Para fixar nosso cursor, tudo o que precisamos fazer é escrever uma regra que diz que em uma linha de tabela, se tivermos um link, então mude a propriedade cursor para ponteiro. Nosso decorador agora vai ficar assim:

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

Agora, quando pairarmos sobre os cabeçalhos das nossas linhas, você verá que temos o cursor do ponteiro. O que é legal nesta abordagem é que esta regra CSS não afetará nenhum outro componente. Ela apenas se aplicará ao nosso componente de ordens!

Agora, vamos ver se podemos fazer algo sobre a nossa filtragem. Esse “filtro” foi removido do Angular, então vamos ter que ser criativos e encontrar uma maneira de implementá-lo em nosso componente.

Passo 4 – Adicionar Filtragem

Estamos prontos para substituir a nossa caixa de filtro que usava o filtro AngularJS para pesquisar através da colecção de encomendas com base numa string que estávamos a pesquisar. O filtro AngularJS vivia no nosso template e não requeria nenhum código no nosso controlador ou componente. Hoje em dia, esse tipo de lógica no template é desencorajado. É preferível fazer esse tipo de ordenação e filtragem na nossa classe de componentes.

Adicionar uma função de filtro

Back no nosso componente, vamos fazer um novo array de ordens chamado filteredOrders. Depois vamos passar o nosso array orders para uma função de filtragem que define o array filteredOrders. Finalmente, vamos usar o filteredOrders no nosso template no nosso *ngFor em vez do nosso array original. Dessa forma nunca vamos modificar os dados que voltam do servidor, estamos apenas usando um subconjunto dele.

A primeira coisa que vamos fazer é declarar a nova propriedade em nossa classe :

filteredOrders: Order;

Então, em nosso forkJoin que define nosso array original de ordens, podemos definir o estado inicial de filteredOrders para nosso array de ordens:

this.filteredOrders = this.orders;

Agora estamos prontos para adicionar a nossa função que irá realmente fazer a filtragem para nós. Cole esta função logo após as nossas funções de ordenação no fundo do nosso 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()); }) ); }

Vamos falar sobre o que está a acontecer nesta função. Primeiro, vamos dar à função uma propriedade de string de search. Depois, fazemos um loop através das nossas ordens e depois encontramos todas as chaves dos objectos. Para todas as chaves, vamos ver se existem some valores daquelas propriedades que correspondem ao nosso termo de pesquisa. Este pouco de JavaScript pode parecer um pouco confuso no início, mas é basicamente isso que está acontecendo.

Note que, em nossa declaração if, estamos testando explicitamente por strings. No nosso exemplo neste momento vamos limitar a nossa consulta a strings. Não vamos tentar lidar com propriedades aninhadas, propriedades numéricas, ou qualquer coisa do género. Nosso termo de pesquisa irá corresponder na propriedade do nome do nosso cliente, e se alguma vez optarmos por mostrar nosso endereço ou qualquer outra propriedade string, ele irá pesquisar através dessas também.

Obviamente, nós também poderíamos modificar esta função para testar números, ou procurar através de outra camada de objetos aninhados, e isso depende totalmente de você. Tal como com a nossa ordenação, vamos começar com uma implementação de demonstração e deixá-lo usar a sua imaginação para torná-la mais complexa.

Falando a função sortOrders, antes de avançarmos, precisamos de fazer uma última coisa no componente. Só precisamos modificar sortOrders para usar filteredOrders agora e não o nosso original orders, porque queremos que o filtro tenha prioridade sobre a ordenação. Basta mudá-lo para isto:

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

Agora estamos prontos para implementar esta filtragem no template.

Adicionar filtragem ao Template

Vamos voltar ao nosso template e fixá-lo para usar a nossa filtragem.

A primeira coisa que precisamos de fazer é substituir data-ng-model. Em vez disso, vamos usar o evento keyup, por isso vamos escrever, “keyup” e rodeá-lo de parênteses ((keyup)). Este é um evento incorporado em Angular que nos permite executar uma função na tecla up de um input. Como nomeámos a nossa função filterOrders, que costumava ser o nome da propriedade que estávamos a passar para o filtro AngularJS, só precisamos de adicionar parênteses junto a ele. Nosso input se parece com isto até agora:

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

Mas o que passamos para a função de ordens do filtro? Bem, por padrão, os eventos passam por algo chamado $event. Isto contém algo chamado target, que então contém o valor do input. Há um problema com o uso de $event. É muito difícil acompanhar esses tipos nebulosos porque target.value pode realmente ser qualquer coisa. Isto torna difícil de depurar ou saber que tipo de valor é esperado. Em vez disso, Angular tem uma coisa muito interessante que podemos fazer, que é atribuir uma variável template a este input.

Por sorte, Angular fornece um método para fazermos isto. Depois da nossa tag de input, podemos adicionar o sinal de hash (#) e depois o nome do nosso modelo desejado. Vamos chamá-lo de #ordersFilter. Realmente não importa onde na tag você coloca isso ou como você chama, mas eu gosto de colocar depois da entrada para que você pegue qual modelo está associado com qual entrada se eu apenas olhar para baixo na página.

Agora posso passar essa variável para a nossa função filterOrders no evento keyup. Nós não precisamos do símbolo hash antes dele, mas precisamos adicionar .value. Isto irá passar o valor real do modelo e não o modelo inteiro em si. Nossa entrada final se parece com isto:

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

Finalmente, precisamos modificar nosso *ngFor para usar o array filteredOrders ao invés do regular orders array:

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

Inspecione o Produto

Você pode ver o quanto mais limpo nosso template está agora que nossa filtragem e classificação está no componente.

Agora vamos checar isto no navegador. Se você inserir algum texto na caixa, como “sally”, você deve ver que nossas ordens estão mudando e que a ordenação funciona em cima disso:
Animação de trabalho app
Fantástico, nós substituímos outra funcionalidade do AngularJS!

Agora só temos uma última coisa a fazer neste componente – fixar o tubo da moeda.

Passo 5 – Fixar o tubo da moeda

O nosso toque final é actualizar o antigo filtro da moeda, que agora é chamado de tubo da moeda em Angular. Só precisamos de adicionar alguns parâmetros ao pipe no template que não tivemos de especificar no AngularJS. Esta parte difere se você estiver usando Angular 4 ou Angular 5:.

No Angular 4, faça isto:
<td>{{order.totalSale | currency:'USD':true}}</td>

No Angular 5+, faça isto:
<td>{{order.totalSale | currency:'USD':'symbol'}}</td>

A primeira opção é o código da moeda (há lotes, você não está limitado a dólares americanos!). A segunda é a exibição do símbolo. No Angular 4, este é um booleano que indica se você deve usar o símbolo da moeda ou o código. No Angular 5+, as opções são symbol, code, ou symbol-narrow como strings.

Você deve agora ver o símbolo esperado:
Screenshot da coluna de Venda Total do app

E já está! Para ver o código finalizado, veja este commit.

Conclusão

Você fez um ótimo trabalho, ficando com isto até o fim! Aqui está o que conseguimos com este guia:

  1. Substituir a sintaxe do modelo AngularJS pela sintaxe Angular
  2. Movendo a classificação para o componente
  3. Utilizar estilos CSS escopados
  4. Movendo a filtragem para o componente
  5. Substituir o filtro de moeda AngularJS pelo tubo de moeda Angular

Para onde você deve ir a partir daqui? Há muitas coisas que você poderia fazer:

  • Faça a ordenação mais sofisticada (por exemplo: a ordenação deve reiniciar ou permanecer a mesma quando o usuário clica em um novo cabeçalho?)
  • Faça a filtragem mais sofisticada (procure por números ou propriedades aninhadas)
  • Mude para uma abordagem reativa. Você poderia ouvir uma observação de mudanças de valor em vez da função keyup e fazer a ordenação e filtragem lá dentro. Usando observáveis também lhe permitiria fazer coisas realmente legais como debounce the input!