За годы менторства по Angular (в том числе в HTML Academy) я заметил одну системную проблему: студенты и даже миддлы часто знают синтаксис RxJS, но не понимают реактивного мышления. В итоге мы получаем subscribe внутри subscribe и императивную лапшу.
Я искал интерактивные курсы, но большинство бесплатных ресурсов ограничиваются основами.
Курс бесплатный. Делал для себя и студентов, но теперь делюсь со всеми.
Буду рад фидбеку и баг-репортам (проект активно допиливаю).
Реакт уже никогда в жизни не догонит angular по производительности и функциональности) Рано или поздно от реакта откажутся, потому что библиотека всегда будет уступать полноценному фреймворку. Жду коммы от джунов, которые в жизни не работали с крупными проектами
Этот семинар углубит ваши знания в RxJS и Angular. Вы изучите, как организовать потоки данных, управлять подписками, избегать утечек памяти и эффективно настроить change detection. Мы рассмотрим полезные RxJS-операторы, частые ошибки и лучшие практики. Обсудим, как выстраивать архитектуру на основе RxJS, использовать AsyncPipe и делить компоненты на умные и глупые.
Кнопка 'Загрузить еще' (либо автоматическая подгрузка данных при скролле) довольно часто встречается в проектах и обычно решение связано с большим количеством подписок и переменных.
Как всегда, для оптимизации чего либо нам на помощь приходит великий и могучий RxJS, а в данной ситуации конкретно операторы scan & mergeScan.
В Angular 20 afterRender был переименован в afterEveryRender, и это очень логично, так как теперь он более четко отражает суть (нейминг решает). Сам afterRender (далее afterEveryRender) и его брат afterNextRender появились в версии 17. Рассмотрим, почему эти два мощных инструмента управления рендерингом — не просто альтернативы ngAfterViewInit, а полноценные хуки жизненного цикла с бесшовной поддержкой SSR!
Это хуки? Да! Это хуки нового типа, которые выполняются после рендеринга компонента:
Они не заменяют ngAfterViewInit/ngAfterContentInit, а дополняют их
Включают гранулярные реакции на рендеры, включая обновления
Почему идеально подходит для SSR? Главное преимущество: обратные вызовы выполняются только на клиенте! ✅ После гидратации (в SSR) ✅ После первоначального рендеринга (в CSR) ✅ Больше никаких ошибок «документ не определен»
Использование: constructor() { // 🚫 Не запускается на сервере // ✅ Запускается только один раз после загрузки браузера! // 📊 Идеально подходит для однократной инициализации afterNextRender(() => { console.log('Next'); });
// 🚫 Не запускается на сервере // 🔄 Запускается после каждого цикла обнаружения изменений // ✨ Отлично подходит для обновлений, зависящих от DOM afterEveryRender(() => { console.log('Every'); }); }
Когда использовать?
afterNextRender
Одноразовые операции (инициализация библиотеки, загрузка данных)
Безопасная замена ngAfterViewInit для SSR
afterEveryRender
Отслеживание изменений DOM (измерения элементов, позиции)
⚠️ Внимание: может повлиять на производительность
Основные выводы
Интегрировано в систему жизненного цикла Angular
Автоматический пропуск на стороне сервера - больше никаких хаков isPlatformBrowser!
afterNextRender - "один раз после рендеринга"
afterEveryRender - "после каждого обновления"
"Я пока не использовал afterEveryRender в своих проектах - есть ли у вас практические примеры использования? Поделитесь в комментариях!"
Манипуляций довольно много, но мы все к этому привыкли и это кажется нормальным.
Но с withComponentInputBinding() все стало намного проще: 1. Создаем сигнальный инпут... и... Вот и все!
Никаких дополнительных манипуляций, и значение «у вас в кармане». Все, что вам нужно, чтобы это работало, — это передать withComponentInputBinding() в качестве аргумента в provideRouter().
Функция не новая (кажется, появилась в Angular 16), но я редко ее видел в проектах.
Немного технической информации из документации:
🔍Маршрутизатор передает данные в input() из:
Параметров запроса (?page=1&sort=asc)
Параметров пути и матрицы (/users/123;details=true)
Статических данных маршрута (data: { role: 'admin' })
Результатов резолвера (resolve: { user: userResolver })
🔍 Приоритеты: Если есть дублирующиеся ключи, данные переопределяются в порядке выше — резолверы имеют наивысший приоритет и перезапишут остальные.
🚩 Важный нюанс Если в маршруте нет данных для input(), он получит undefined (например, если параметр запроса удален из URL).
ℹ️ Как задать значения по умолчанию?
✔ Через resolver (чтобы данные всегда были в маршруте) ✔ Через transform в input() (если нужно обрабатывать undefined)
Спасибо разработчикам Angular за эту функциональность 🙏.
Мы, как разработчики, использующие в работе самый крутой фреймворк (по моему мнению🙂), немного избалованы обилием его возможностей, особенно в последнее время. Но иногда хочется либо побаловаться, либо возникает реальная потребность в функционале, которого нет, но очень хотелось бы.
И вот, как-то я в очередной раз писал подобные строки:
isOpened = signal(false);
toggle() {
this.isOpened.update(value => !value);
}
#isOpenedEffect = effect(() => {
console.log('New state:', this.isOpened())
})
- и подумал: 'Было бы удобно, если бы был булевый signal с методом toggle'
В этом не было прям сильной необходимости, однако было бы немного удобнее (процентов на 10 😅). И появилась идея написать свой сигнал (реализация на фото)
Пример, согласен, так себе.
👍🏼 меньше кода, более аккуратно
👎🏼 всей команде придется подстроится
Но этим постом я просто хотел показать, что есть такая возможность создания своего переиспользуемого сигнала под нужды вашего проекта, где это будет выглядеть уместно и целесообразно.
RxJS: Почему shareReplay(1) может вызывать утечки памяти
Если вы используете shareReplay(1) в RxJS, будьте осторожны — в некоторых случаях это может привести к утечкам памяти! Давайте разберёмся, почему так происходит и как это исправить.
❌ Проблема с shareReplay(1). По умолчанию:
Сохраняет последнее значение в бесконечном буфере, даже если подписчиков больше нет
Не отписывается от источника, когда никто не слушает (например, interval, Subject, HTTP-запросы) Это означает, что данные могут накапливаться, а ненужные Observable продолжают работать в фоне
@EnvMode('dev')
public setFeatureToggle(): void {
// Сработает только в dev-режиме
}
@EnvMode('prod')
public sendAnalytics(): void {
// Сработает только в prod-режиме
}
Иногда нужно отобразить несколько одинаковых элементов чисто для ui-целей: скелетоны загрузки, звёзды рейтинга, пустые таблицы и т.д., но без реальных данных для итерации.
Вот мой способ (по крайней мере, я нигде такого не видел):
@for (_ of [].constructor(10); track $index) { <div class="item"></div> }
Используется Array.constructor, чтобы создать пустую массив фиксированной длины, который @for может перебрать по индексам.
Плюсы ✅
Чудо-код (удивит коллег)
Минимум кода (не нужно объявлять массив в компоненте)
Минусы ⚠️
Чудо-код (может ненадолго ввести в ступор чающего код человека)
Конечно, можно просто использовать Array.from({length: 10})... но так все делают, не интересно)
Немного о резолверах в Angular 19 (теперь в них есть редиректы).
В логике использования гвардов применяется подход:
проверить что-то, если все ок то вернуть true или кинуть редирект на другую страницу
Выглядит достаточно удобно. Но если мне не изменяет память в резолверах такого нет, вместо этого приходилось натягивать Router и рулить navigate или navigateByUrl и т.д.
В 19 же версии нам немного упростили жизнь и резолвер научили в RedirectCommand.