#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. Не приводит ли возврат значения из канала к тому, что оно становится пустой строкой?