Каковы лучшие способы избежать использования вызовов методов в шаблонах Angular?

#angular #templates #optimization #ngfor #angular-changedetection

#angular #шаблоны #оптимизация #ngfor #angular-changedetection

Вопрос:

Я пытаюсь избегать использования вызовов методов в шаблонах Angular, поскольку они там неэффективны. Допустим, у меня есть список имен:

 const names: string[] = ['Billy', 'Mandy', 'Carl', 'Sheryl']
 

и в моем шаблоне я использую ngFor для итерации списка и печати имен:

 <ng-container *ngFor="let name of names">
  <p>{{ name }}</p>
</ng-container>
 

Но теперь мне нужно отображать имя только в том случае, если оно начинается с ‘S’, поэтому я меняю на:

 <ng-container *ngFor="let name of names">
  <p *ngIf="doesNameStartWithS(name)">{{ name }}</p>
</ng-container>
 

Теперь у меня есть метод в моем шаблоне, который, как я знаю, будет выполняться больше раз, чем необходимо. Чтобы избежать этого, я мог бы сделать что-то вроде:

 // this runs whenever the names list changes
const nameStartsWithSList: boolean[] = this.names.map((name: string): boolean => this.doesNameStartWithS(name));
 

а затем измените мой шаблон на:

 <ng-container *ngFor="let name of names; let i = index;">
  <p *ngIf="nameStartsWithSList[i]">{{ name }}</p>
</ng-container>
 

но это привело к появлению нового списка полностью, чтобы избежать вызова метода в шаблоне. Есть ли лучший способ сделать это?

Ответ №1:

Это очень интересный вопрос.

Одним из возможных решений было бы передать префикс и поле директиве и соответствующим образом манипулировать ими. Вероятно, вы могли бы использовать renderer2 как лучшее решение для представления абзаца с нужным полем, но это было просто для демонстрации его работы.

 @Input() chars: string;
@Input() field: string;

constructor(private el: ElementRef) {}

ngOnInit() {
  if (this.field.toLowerCase().includes(this.chars.toLowerCase())) {
    (this.el.nativeElement as HTMLElement).innerHTML = `<p>${this.field}</p>`;
  }
}
 

Еще одна вещь (которую я на самом деле только что понял) заключается в том, что вы также можете использовать директиву как компонент.

 <ng-container *ngFor="let name of names">
  <showIfStartsWith chars="s" [field]="name"></showIfStartsWith>
</ng-container>
 

Полная демонстрация здесь.

Редактировать: найдено другое решение, менее странное, без использования директивы в качестве компонента. Демо версия V2

Редактировать 2: найдено другое решение, использующее директиву как структурную директиву, демонстрирующую, как вы передаете ей несколько параметров. Демо версия V3

Комментарии:

1. я не знаю, почему я не подумал использовать структурную директиву для этого, я взял ваш пример и попытался создать повторно используемую структурную директиву dynamicRender, которая принимает аргумент и функцию. если результат функции, использующей аргумент, равен true, он показывает компонент, в противном случае он очищает его: stackblitz.com/edit/angular-ivy-6ypu3j . Вы должны иметь возможность комментировать / раскомментировать стандартный цикл for и нажимать кнопку Something, чтобы показать, что функция вызывается ненужное количество раз, если есть стандартный цикл, а не когда есть динамически отображаемый цикл

2. Точно, как я показал в демо версии v3. Итак, это правильно?

Ответ №2:

Я думаю, что лучший подход — это манипулировать вашим списком в файле ts.

если ваш начальный список:

 const names: string[] = ['Billy', 'Mandy', 'Carl', 'Sheryl']
 

настройте его следующим образом

 output(array, modifier) {
***modify array to return only starting with modifier letter***

return modifiedArray
}

modifier: string = 's'

const names: string[] = output(['Billy', 'Mandy', 'Carl', 'Sheryl'], modifier)
 

затем используйте

 <div *ngFor="let name of names"> ...
 

Комментарии:

1. это кажется довольно хорошей идеей, хотя в идеале мы ничего не будем изменять, потому что, если мой компонент изменяет массив, который был введен в него, он также изменит его оттуда, откуда он его получил. Я мог видеть, что ввод, который является моим массивом имен, и в onChanges для этого ввода устанавливает новое поле, выполнив .map в исходном массиве

2. В вашем примере вы устанавливаете новый массив с const в своем компоненте, поэтому я не предполагал, что он должен быть получен из входных данных. Если вы не хотите изменять исходный массив, вы можете просто отобразить измененную копию входного массива.

Ответ №3:

Я думаю, что в этом случае я бы использовал канал, который преобразует или фильтрует данные, что-то вроде этого:

первый-upper.pipe.ts

 import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'firstUpper',
})
export class FirstUpperPipe implements PipeTransform {
  transform(value: string, letter: string): any {
    if(value.startsWith(letter)){
      return value;
    }
  }
}
 

В конце концов, в вашем шаблоне вы можете использовать так:

 <ng-container *ngFor="let name of names">
  <p>{{ name | firstUpper:'S' }}</p>
</ng-container>
 

Вы должны импортировать этот канал в свой pipes.module.ts, если у вас есть или раздел объявления app.module.ts.

Комментарии:

1. похоже, что с этим решением мы будем только преобразовывать данные, и они все равно будут присутствовать в dom. Не приводит ли возврат значения из канала к тому, что оно становится пустой строкой?