Реакт уже никогда в жизни не догонит angular по производительности и функциональности) Рано или поздно от реакта откажутся, потому что библиотека всегда будет уступать полноценному фреймворку. Жду коммы от джунов, которые в жизни не работали с крупными проектами

Angular *
JavaScript-фреймворк

Приглашаем на бесплатный вебинар «Эффективное использование RxJS в Angular: шаблоны, подходы, лучшие практики».
📅 Дата: 15.07.2025
⏰ Время: 17:00-18:00 (Мск)
Этот семинар углубит ваши знания в RxJS и Angular. Вы изучите, как организовать потоки данных, управлять подписками, избегать утечек памяти и эффективно настроить change detection. Мы рассмотрим полезные RxJS-операторы, частые ошибки и лучшие практики. Обсудим, как выстраивать архитектуру на основе RxJS, использовать AsyncPipe и делить компоненты на умные и глупые.
На вебинаре:
✔️ Как устроен RxJS под капотом.
✔️ Проблемы RxJS в реальных приложениях.
✔️ Tестирование потоков.
✔️ Лучшие практики и архитектурные паттерны.
✔️ Работа с операторами.
✔️ Работа с данными в реальном времени.
✔️ Кастомные операторы.
✍️Записаться
Сила RxJS. scan + mergeScan = 'Загрузить еще'
Кнопка 'Загрузить еще' (либо автоматическая подгрузка данных при скролле) довольно часто встречается в проектах и обычно решение связано с большим количеством подписок и переменных.
Как всегда, для оптимизации чего либо нам на помощь приходит великий и могучий RxJS, а в данной ситуации конкретно операторы scan & mergeScan.

Код:
readonly loadTrigger$ = new Subject<void>();
private readonly batchSize = 5;
private readonly posts$ = this.loadTrigger$.pipe(
startWith(void 0),
scan((offset) => offset + this.batchSize, -this.batchSize),
mergeScan(
(accPosts: Post[], offset: number) =>
getPosts(offset, this.batchSize).pipe(
map((newPosts) => [...accPosts, ...newPosts]),
),
[] as Post[],
),
);
scan
– калькулятор + хранитель состояния для offset:Управляет состоянием загрузки (текущее смещение)
Начинается с
-batchSize
, чтобы первая загрузка была с0
Увеличивает смещение на
batchSize
при каждом срабатывании
mergeScan
– волшебный оператор для инкрементальной загрузки:Сохраняет массив накопленных постов
Объединяет новые данные с существующими
Корректно обрабатывает параллельные запросы (в отличие от обычного
scan
)
Где полезен этот паттерн?
Постраничные API (пагинация)
Бесконечная прокрутка
Порционная загрузка данных
Любые сценарии накопления асинхронных данных
scan - https://rxjs.dev/api/operators/scan
mergeScan - https://rxjs.dev/api/operators/mergeScan
Больше об Angular - https://t.me/grandgular
afterEveryRender и afterNextRender

В 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
в своих проектах - есть ли у вас практические примеры использования? Поделитесь в комментариях!"
Больше об 🅰️ngular в моём Telegram-канале
withComponentInputBinding()
Упрощение работы с параметрами маршрутизатора в Angular.

Как было раньше?
Создаем переменную/свойство (Signal, BehaivorSubject, Observable, неважно)
Инжектим и подписываемся на ActivatedRoute
Получаем параметры маршрута
Записываем в BS/Signal
😵💫😵💫😵💫
Манипуляций довольно много, но мы все к этому привыкли и это кажется нормальным.
Но с 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 за эту функциональность 🙏.
Больше об 🅰️ngular в моём Telegram-канале
🦥 RxJS defer — ленивая инициализация Observable

defer — это фабрика, которая создает Observable только при подписке, а не во время объявления. Идеально подходит для:
HTTP-запросов (чтобы избежать преждевременного выполнения)
динамических данных (которые должны быть свежими при каждой подписке)
условных потоков (когда Observable зависит от состояния времени выполнения)
📌 Основные варианты использования
Свежие данные при каждой подписке
const freshData$ = defer(() => of(Date.now()));
// Новая временная метка при каждой подписке()
Работа с изменяемым состоянием
const token$ = defer(() => of(localStorage.getItem('token')));
// Всегда получает текущий токен, даже если обновлен
Условные наблюдаемые
const api$ = defer(() => isLoggedIn ? http.get('/user') : http.get('/guest') );
Генерация случайного значения
const random$ = defer(() => of(Math.random()));
// Новое случайное число на подписку
🚫 Ограничения defer
нет кэширования → используйте shareReplay, если вам нужно повторно использовать результаты.
нет отмены запроса → объедините с switchMap/takeUntil для управления отменой
⚡Когда следует выбирать defer вместо обычных наблюдаемых?
данные должны быть свежими при каждом subscribe()
cоздание наблюдаемого стоит дорого и должно быть отложено
поток зависит от изменяемых условий (флаги функций, статус аутентификации и т. д.)
Больше об 🅰️ngular в моём Telegram-канале
😎 Кастомный signal в Angular
Мы, как разработчики, использующие в работе самый крутой фреймворк (по моему мнению🙂), немного избалованы обилием его возможностей, особенно в последнее время. Но иногда хочется либо побаловаться, либо возникает реальная потребность в функционале, которого нет, но очень хотелось бы.
И вот, как-то я в очередной раз писал подобные строки:
isOpened = signal(false);
toggle() {
this.isOpened.update(value => !value);
}
#isOpenedEffect = effect(() => {
console.log('New state:', this.isOpened())
})
- и подумал: 'Было бы удобно, если бы был булевый signal с методом toggle'
В этом не было прям сильной необходимости, однако было бы немного удобнее (процентов на 10 😅). И появилась идея написать свой сигнал (реализация на фото)

Пример, согласен, так себе.
👍🏼 меньше кода, более аккуратно
👎🏼 всей команде придется подстроится
Но этим постом я просто хотел показать, что есть такая возможность создания своего переиспользуемого сигнала под нужды вашего проекта, где это будет выглядеть уместно и целесообразно.
💬 А какого сигнала не хватает вам?
Больше об 🅰️ngular в моём Telegram-канале
RxJS: Почему shareReplay(1) может вызывать утечки памяти
Если вы используете shareReplay(1) в RxJS, будьте осторожны — в некоторых случаях это может привести к утечкам памяти! Давайте разберёмся, почему так происходит и как это исправить.
❌ Проблема с shareReplay(1). По умолчанию:
Сохраняет последнее значение в бесконечном буфере, даже если подписчиков больше нет
Не отписывается от источника, когда никто не слушает (например, interval, Subject, HTTP-запросы)
Это означает, что данные могут накапливаться, а ненужные Observable продолжают работать в фоне
✅ Решение: shareReplay({ bufferSize: 1, refCount: true })
Добавление refCount: true решает проблему:
Автоматически отписывается, когда не остаётся подписчиков
Очищает буфер, предотвращая утечки памяти

💡 Ключевые выводы:
shareReplay(1) - Может оставлять "висящие" подписки
shareReplay({ bufferSize: 1, refCount: true }) - Безопасная альтернатива
Я обнаружил эту проблему во время code review вчера, а впервые узнал о ней из видео Dmytro Mezhenskyi.
🔗 P.S. Самое время быстро исправить это в наших проектах!)
Больше об 🅰️ngular в моём Telegram-канале
"mergeMap: Секрет управления параллелизмом в RxJS"

Вы знаете mergeMap
, concatMap
, exhaustMap
и switchMap
— но знали ли вы, что только один из них принимает второй аргумент?
👉 mergeMap
уникален благодаря параметру concurrent
, который ограничивает параллельные подписки. Это как "регулятор скорости" для ваших Observable.
_____
Пример использования (контролируемая загрузка файлов):
Представьте, что вам нужно:
1️⃣ Обработать несколько файлов
2️⃣ Избежать перегрузки браузера/API
3️⃣ Сохранить порядок загрузки
В этом случае второй аргумент mergeMap становится незаменимым:
from(fileList).pipe(
mergeMap(
file => uploadFile(file), // функция загрузки
3 // одновременно только 3 файла
)
).subscribe();
_____
Преимущества mergeMap с concurrent:
✅ Предотвращает перегрузку API
✅ Оптимизирует нагрузку на сеть
✅ Сохраняет отзывчивость интерфейса
_____
Знали ли об этой возможности? 😉
Больше об Angular в телеграмм-канале
Управление методами в зависимости от окружения в 🅰️ngular

Бывают случаи, когда нужно, чтобы определенные методы работали только:
🛠️ В dev-режиме (например, фича-тогглы, дебаг-логи, экспериментальные функции)
🚀 В prod-режиме (аналитика, мониторинг, продакшен-логика)
Вместо того чтобы писать if (isDevMode()) {...}
везде, можно использовать декораторы Angular для более чистого и декларативного подхода.
_____
Одно из решений:
export function EnvMode(mode: 'dev' | 'prod') {
return function (_: unknown, __: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: unknown[]) {
const shouldExecute = mode === 'dev' ? isDevMode() : !isDevMode();
if (!shouldExecute) return;
return originalMethod.apply(this, args);
};
return descriptor;
};
}
_____
Как использовать:
@EnvMode('dev')
public setFeatureToggle(): void {
// Сработает только в dev-режиме
}
@EnvMode('prod')
public sendAnalytics(): void {
// Сработает только в prod-режиме
}
_____
🤔 А вы создавали кастомные декораторы? Зачем?
Больше про Angular в тг канале
Angular Hack: Цикл без данных (в тэмплейте)

Иногда нужно отобразить несколько одинаковых элементов чисто для 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.
Пример с angular.dev
export const heroResolver: ResolveFn<Hero>= async (
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
) => {
const router = inject(Router);
const heroService = inject(HeroService);
try {
return await heroService.getHero(route.paramMap.get('id')!);
} catch {
return new RedirectCommand(router.parseUrl('/404'));
}
};
Ну просто сказка какая-то, а не только сигналы =)
Ближайшие события
Кнопка со счётчиком: React vs Fusor

Fusor это новый способ разработки вэб приложений https://github.com/fusorjs/dom
Вклад авторов
Waterplea 745.0MarsiBarsi 407.0tamtakoe 396.8ru_vds 352.0nsbarsukov 259.0mnemosha 179.0durovchpoknet 142.0MooooM 139.0aav 136.0splincodewd 135.4