Com o Angular, você pode fazer qualquer coisa. Ou quase tudo. Mas às vezes esse "quase" insidioso leva ao fato de que o desenvolvedor está perdendo tempo criando soluções alternativas ou tentando entender por que algo está acontecendo ou por que algo não está funcionando como o esperado.

O autor do artigo que estamos traduzindo hoje diz que deseja compartilhar dicas que ajudarão os desenvolvedores Angular a economizar algum tempo. Ele vai falar sobre as armadilhas do Angular, que ele (e não apenas ele) teve a chance de conhecer.
No. 1. A diretiva personalizada que você aplicou não funciona
Então, você encontrou uma boa diretiva Angular de terceiros e decidiu usá-la com elementos padrão no modelo Angular. Ótimo! Vamos tentar fazer isso:
<span awesomeTooltip="'Tooltip text'"> </span>
Você inicia o aplicativo ... E nada acontece. Você, como qualquer programador experiente e normal, olha para o console das Ferramentas do desenvolvedor do Chrome. E você não vê nada lá. A diretiva não funciona e Angular é silencioso.
Então, uma cabeça brilhante da sua equipe decide colocar a diretiva entre colchetes.
<span [awesomeTooltip]="'Tooltip text'"> </span>
Depois disso, tendo perdido um pouco de tempo, vemos o seguinte no console.
Aqui está a coisa: nós apenas esquecemos de importar o módulo com a diretivaAgora, a causa do problema é bastante óbvia: esquecemos de importar o módulo de diretiva para o módulo de aplicativo Angular.
Isso leva a uma regra importante: nunca use diretivas sem colchetes.
Você pode experimentar as diretrizes
aqui .
No. 2. ViewChild retorna indefinido
Suponha que você tenha criado um link para um elemento de entrada de texto descrito em um modelo Angular.
<input type="text" name="fname" #inputTag>
Você está usando a função RxJS
fromEvent para criar um fluxo no qual o que é inserido no campo cairá. Para fazer isso, você precisa de um link para o campo de entrada, que pode ser obtido usando o decorador Angular
ViewChild
:
class SomeComponent implements AfterViewInit { @ViewChild('inputTag') inputTag: ElementRef; ngAfterViewInit(){ const input$ = fromEvent(this.inputTag.nativeElement, 'keyUp') } ... }
Aqui, usando a função RxJS
fromEvent
, criamos um fluxo no qual os dados inseridos no campo cairão.
Teste este código.
ErroO que aconteceu
De fato, a regra a seguir se aplica aqui: se
ViewChild
retornar
undefined
, procure
*ngIf
no modelo.
<div *ngIf="someCondition"> <input type="text" name="fname" #inputTag> </div>
Aqui ele é o culpado do problema.
Além disso, verifique o modelo para outras diretivas estruturais ou
ng-template
acima do elemento do problema.
Considere possíveis soluções para esse problema.
Ar Variante de resolver o problema №1
Você pode simplesmente ocultar o elemento do modelo se não precisar dele. Nesse caso, o elemento sempre continuará existindo e o
ViewChild
poderá retornar um link para ele no gancho
ngAfterViewInit
.
<div [hidden]="!someCondition"> <input type="text" name="fname" #inputTag> </div>
Ar Variante de resolver o problema №2
Outra maneira de resolver esse problema é usar setters.
class SomeComponent { @ViewChild('inputTag') set inputTag(input: ElementRef|null) { if(!input) return; this.doSomething(input); } doSomething(input) { const input$ = keysfromEvent(input.nativeElement, 'keyup'); ... } }
Aqui, assim que Angular atribui um valor específico à propriedade
inputTag
, criamos um fluxo a partir dos dados inseridos no campo de entrada.
Aqui estão alguns recursos úteis relacionados a esse problema:
- Aqui você pode ler que os resultados do
ViewChild
no Angular 8 podem ser estáticos e dinâmicos. - Se você está tendo dificuldades para trabalhar com RxJs, dê uma olhada neste curso em vídeo.
Número 3. Execução de código ao atualizar a lista gerada com * ngFor (após os elementos aparecerem no DOM)
Suponha que você tenha alguma diretiva personalizada interessante para organizar listas roláveis. Você o aplicará a uma lista criada usando a
*ngFor
Angular
*ngFor
.
<div *ngFor="let item of itemsList; let i = index;" [customScroll] > <p *ngFor="let item of items" class="list-item">{{item}}</p> </div>
Normalmente, nesses casos, ao atualizar a lista, você precisa chamar algo como
scrollDirective.update
para configurar o comportamento da rolagem, levando em consideração as alterações que ocorreram na lista.
Pode parecer que isso possa ser feito usando o gancho
ngOnChanges
:
class SomeComponent implements OnChanges { @Input() itemsList = []; @ViewChild(CustomScrollDirective) scroll: CustomScrollDirective; ngOnChanges(changes) { if (changes.itemsList) { this.scroll.update(); } } ... }
É verdade que aqui estamos diante de um problema. O gancho é chamado antes do navegador exibir a lista atualizada. Como resultado, o recálculo dos parâmetros da diretiva para rolar a lista é realizado incorretamente.
Como fazer uma ligação logo após
*ngFor
terminar o trabalho?
Você pode fazer isso seguindo estas 3 etapas simples:
▍Etapa número 1
Coloque os links para os elementos em que
*ngFor
(
#listItems
) é
#listItems
.
<div [customScroll]> <p *ngFor="let item of items" #listItems>{{item}}</p> </div>
▍Etapa número 2
Obtenha uma lista desses elementos usando o decorador Angular
ViewChildren
. Retorna uma entidade do tipo
QueryList
.
▍Etapa número 3
A classe
QueryList
possui uma propriedade de
alterações somente leitura que
QueryList
eventos toda vez que a lista é alterada.
class SomeComponent implements AfterViewInit { @Input() itemsList = []; @ViewChild(CustomScrollDirective) scroll: CustomScrollDirective; @ViewChildren('listItems') listItems: QueryList<any>; private sub: Subscription; ngAfterViewInit() { this.sub = this.listItems.changes.subscribe(() => this.scroll.update()) } ... }
Agora o problema está resolvido.
Aqui você pode experimentar o exemplo correspondente.
Número 4. Problemas com ActivatedRoute.queryParam que ocorrem quando as consultas podem ser executadas sem parâmetros
O código a seguir nos ajudará a entender a essência desse problema.
Alguns fragmentos deste código são feitos comentários como
#x
. Considere-os:
- No módulo principal do aplicativo, definimos as rotas e adicionamos o
RouterModule
lá. As rotas são configuradas para que, se uma rota não for fornecida no URL, redirecionemos o usuário para a /home
page. - Como componente para download, especificamos no módulo principal
AppComponent
. AppComponent
usa <router-outlet>
para AppComponent
os componentes de rota apropriados.- Agora a coisa mais importante. Precisamos obter
queryParams
para a rota a partir do URL
Suponha que tenhamos o seguinte URL:
https:
Nesse caso,
queryParams
terá a seguinte aparência:
{accessToken: 'someTokenSequence'}
Vejamos o trabalho de tudo isso no navegador.
Testando um aplicativo que implementa um sistema de roteamentoAqui você pode ter uma pergunta sobre a essência do problema. Temos os parâmetros, tudo funciona como esperado ...
Dê uma olhada na captura de tela acima do navegador e no que é exibido no console. Aqui você pode ver que o objeto
queryParams
é emitido duas vezes. O primeiro objeto está vazio e é emitido durante o processo de inicialização do roteador Angular. Somente depois disso obtemos um objeto que contém os parâmetros de solicitação (no nosso caso -
{accessToken: 'someTokenSequence'}
).
O problema é que, se não houver parâmetros de solicitação no URL, o roteador não retornará nada. Ou seja - depois de emitir o primeiro objeto vazio, o segundo objeto, também vazio, que pode indicar a ausência de parâmetros, não será emitido.
Ao executar uma solicitação sem parâmetros, o segundo objeto não é emitidoComo resultado, verifica-se que, se o código esperar um segundo objeto do qual possa receber dados de solicitação, ele não será iniciado se não houver parâmetros de solicitação no URL.
Como resolver este problema? Aqui os RxJs podem nos ajudar. Vamos criar dois objetos observáveis baseados em
ActivatedRoute.queryParams
. Como sempre - considere uma solução passo a passo para o problema.
▍Etapa número 1
O primeiro objeto observável,
paramsInUrl$
,
paramsInUrl$
dados se
queryParams
não
queryParams
vazio:
export class AppComponent implements OnInit { constructor(private route: ActivatedRoute, private locationService: Location) { } ngOnInit() {
▍Etapa número 2
O segundo objeto observável,
noParamsInUrl$
,
noParamsInUrl$
valor vazio apenas se nenhum parâmetro de solicitação for encontrado na URL:
export class AppComponent implements OnInit { title = 'QueryTest'; constructor(private route: ActivatedRoute, private locationService: Location) { } ngOnInit() { ...
▍Etapa número 3
Agora combine os objetos observados usando a função de
mesclagem RxJS:
export class AppComponent implements OnInit { title = 'QueryTest'; constructor(private route: ActivatedRoute, private locationService: Location) { } ngOnInit() {
Agora, o
param$
object observado
param$
valor apenas uma vez - independentemente de algo estar
queryParams
em
queryParams
(um objeto com parâmetros de consulta é
queryParams
) ou não (um objeto vazio é
queryParams
).
Você pode experimentar esse código
aqui .
No. 5. Páginas lentas
Suponha que você tenha um componente que exiba alguns dados formatados:
Este componente resolve dois problemas:
- Ele exibe uma matriz de elementos (pressupõe-se que esta operação seja executada uma vez). Além disso, ele formata o que é exibido chamando o método
formatItem
. - Ele exibe as coordenadas do mouse (esse valor obviamente será atualizado com muita frequência).
Você não espera que este componente tenha problemas de desempenho. Portanto, execute um teste de desempenho apenas para cumprir todas as formalidades. No entanto, algumas curiosidades aparecem durante este teste.
Muitas chamadas formatItem e bastante carga de CPUQual é o problema? Mas o fato é que, quando o Angular redesenha o modelo, ele chama todas as funções do modelo (no nosso caso, a função
formatItem
). Como resultado, se qualquer cálculo pesado for realizado nas funções do modelo, isso sobrecarrega o processador e afeta a maneira como os usuários percebem a página correspondente.
Como consertar isso? Basta realizar os cálculos realizados no
formatItem
com antecedência e exibir os dados já prontos na página.
Agora, o teste de desempenho parece muito mais decente.
Chamadas de item e baixa carga do processadorAgora, o aplicativo funciona muito melhor. Mas a solução usada aqui tem alguns recursos que nem sempre são agradáveis:
- Como exibimos as coordenadas do mouse no modelo, o evento
mousemove
ainda aciona uma verificação de alteração. Mas, como precisamos das coordenadas do mouse, não podemos nos livrar disso. - Se o manipulador de eventos
mousemove
precisar executar apenas alguns cálculos (que não afetam o que é exibido na página), para acelerar o aplicativo, você pode fazer o seguinte:
- Você pode usar
NgZone.runOutsideOfAngular
dentro da função de manipulador de eventos. Isso impede que a verificação das alterações seja iniciada quando o evento mousemove
(isso afetará apenas esse manipulador). - Você pode impedir o patch zone.js para alguns eventos usando a seguinte linha de código em polyfills.ts . Isso afetará todo o aplicativo Angular.
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove'];
Se você estiver interessado nas questões de melhoria do desempenho de aplicativos Angular -
aqui ,
aqui ,
aqui e
aqui - materiais úteis sobre isso.
Sumário
Agora que você leu este artigo, cinco novas ferramentas devem aparecer no seu arsenal de desenvolvedores Angular com o qual você pode resolver alguns problemas comuns. Esperamos que as dicas que você encontrou aqui ajudem você a economizar algum tempo.
Caros leitores! Você sabe alguma coisa que ajuda a economizar tempo ao desenvolver aplicativos Angular?
