Angular — это мощный инструмент для создания сложных веб-приложений. Но, как и в любом другом фреймворке, возникают свои сложности. Одна из таких проблем — это частые перезапуски тяжелых функций в шаблонах, что сильно бьет по производительности. Если приложение начинает тормозить, значит пора задуматься об оптимизации. И здесь на помощь приходит Memoize Pipe, способный спасти ваш интерфейс от лишних вычислений.
Проблема: Повторные вызовы функций
Каждый раз, когда Angular проверяет изменения, он повторно вызывает функции в шаблоне, игнорируя предыдущий результат. Это может стать проблемой, когда дело касается сложных вычислений или обращений к API.
@Component({
...
template: `
{{ heavyComputation(person, index) }}
`,
})
export class AppComponent {
heavyComputation(name: string, index: number) {
// Очень тяжелые вычисления
}
}
Решение: Memoize Pipe для повышения производительности
Создавать отдельный pipe для каждой функции не всегда удобно, особенно если функция больше нигде не используется. Хочется универсального и гибкого решения, и именно это предлагает Memoize Pipe. Он позволяет запоминать результаты функций, избегая их повторного вызова, если входные данные остались неизменными. Это снижает нагрузку на приложение и улучшает производительность.
Пример использования:
Изначально у вас есть шаблон с вызовом функции:
@Component({
...
template: `
{{ heavyComputation(person, index) }}
`,
})
export class AppComponent {
heavyComputation(name: string, index: number) {
// Очень тяжелые вычисления
}
}
Теперь добавим мемоизацию:
Установим библиотеку:
npm i @ngx-rock/memoize-pipe
Затем импортируем FnPipe и используем его в шаблоне:
@Component({
...
template: `
{{ heavyComputation | fn : person : index }}
`,
})
export class AppComponent {
heavyComputation(name: string, index: number) {
// Очень тяжелые вычисления
}
}
Теперь Angular не будет каждый раз пересчитывать функцию, если значения её аргументов не изменились (примитивы и ссылки для массивов и объектов).
Сохранение контекста this
Если ваша функция зависит от переменных компонента, то нужно сохранить контекст. Это можно сделать с помощью стрелочной функции:
@Component(...)
export class AppComponent {
heavyComputation = (name: string, index: number) => {
// Очень тяжелые вычисления
}
}
Поддержка standalone модулей
Последние версии Memoize Pipe реализованы в виде standalone модулей.
import { FnPipe } from "@ngx-rock/memoize-pipe";
@Component({
...
standalone: true,
imports: [ FnPipe, ... ]
...
})
export class AppComponent { ... }
Заключение: Простая оптимизация — ощутимый результат
Когда речь идет о производительности, порой самые простые решения оказываются наиболее эффективными. Memoize Pipe — это как раз такое решение: оно помогает уменьшить количество вызовов функций, ускоряя работу приложения. Помимо этого упрощает процесс написания кода.
jonic
Все хорошо, только не проще ли тогда использовать computed сигнал? Мемоизация из коробки
BaryshevRS Автор
Тоже имеет право на жизнь, но не всегда применимо. Например, в цикле в шаблоне.
Консоль лог вызывается снова, когда проверяются изменения в шаблоне.
jonic
Я бы в этом примере передал обработанный массив в цикл for. Как минимум не пришлось бы делать N вызовов метода внутри цикла. К тому же тут вообще ужасно создается каждый цикл новый сигнал. Когда они будут освобождены? Вообще на сколько помню сигналы можно создавать только внутри конструктора или контекста инъекции.
BaryshevRS Автор
Код выше у меня синтетический, но при этом работает. Это просто пример того, что computed сигнал не универсальное решение.
На счёт предвычислений можно, конечно, но иногда есть ситуации, когда проще в шаблоне.
jonic
Я бы такое на ревью не пропустил. Все же, учитывая куда все движется, приспосабливаться к сигналам. Это поможет избежать ошибок “value was changed after checked” как минимум. Да я как ангулярщик со стажем (с самых первых версий) был немного скептичен к ним, но учитывая что у нас уже идут input output signals, то.. за ними будущее как и удаление zone.js :)
BaryshevRS Автор
Это всё хорошо, но “value was changed after checked” никак не появится в моём решении. И это никак не касается зоны. Такие перерасчеты есть и при её удалении.
Input output signals пока всё же экспериментальные. И они не замена pipe. Такого позиционирования в документации не видел.
jonic
Нет, это не замена pipe, мне больше не нравится что функция вызывается в pipe трансформ и кэшируется для определенного значения + аргументов. Все же не экспериментальные, а уже developer preview.
в вашем примере нет, а найдется умник который в тяжелом вычислении сделает смену состояния.
Ну и извините, ради одного pipe который вызывает функцию в трансформ тащить зависимость?
BaryshevRS Автор
Вот это можно как раз на ревью увидеть.
Почему нет, уменьшает бордерплейт. Не нужно создавать лишние одноразовые пайпы, а сразу использовать внешние функции утилиты или методы прямо из компонента.
jonic
https://stackblitz.com/edit/stackblitz-starters-mbkbwg
Не знаю, на мой взгляд сигналы выразительнее и не надо ничего тащить или писать.
P.s. Обратите внимание, я заменил пакет целым файлом в 10 строк.