Оглавление
Angular — тяжеловес. Он никогда не стремился быть «простым как React». Зато стабилен, мощен, корпоративен.
Но время меняется. Разработчики хотят меньше бойлерплейта, больше предсказуемости и меньше RxJS-танцев. Angular слышит. И вот — Signals.
Я работаю с Angular давно. Пробовал всё: NgRx
, ComponentStore
, BehaviorSubject
, кастомные сервисы. Когда появились Signals, я не поверил, что это встроено в Angular. Без библиотек. Без подписок. Почти как в React. Только без магии.
Разберёмся, что это такое, зачем нужно, и правда ли Signals могут заменить React Hooks — или хотя бы сделать Angular таким же приятным в работе.
Что такое Signals?
Signals — это реактивные переменные. Они умеют:
— Хранить значение
— Давать доступ к нему через функцию: count()
— Изменяться через .set()
или .update()
— Автоматически обновлять всех, кто от них зависит
Это — основа новой реактивной модели Angular. Не RxJS. Не костыли. Встроенная реактивность, прямо в ядре фреймворка.
Простой пример
import { signal, computed, effect } from '@angular/core';
const count = signal(0);
const double = computed(() => count() * 2);
effect(() => {
console.log(`Count is: ${count()}, Double is: ${double()}`);
});
count.set(2); // автоматически вызовется effect
Ключевое отличие: вы не подписываетесь. Вы просто вызываете count()
— и Angular сам понимает, кто кого слушает.
Почему это важно?
До Signals у нас было два пути:
1. Использовать @Input()
и отслеживать изменения вручную
2. Использовать RxJS
Но RxJS в Angular — это:
— Много бойлерплейта
— Потенциальные утечки из-за subscribe()
— Сложный трекинг зависимостей
— Проблемы с читаемостью кода
Signals решают эти проблемы:
— Без подписок
— Без отписок
— Без ngOnDestroy
— Без Observable-операторов
Просто пишешь как обычный код — и оно работает.
Signals vs React Hooks
Сходство видно невооружённым глазом. Вот типичный компонент:
React:
const [count, setCount] = useState(0);
const double = useMemo(() => count * 2, [count]);
useEffect(() => {
console.log(count);
}, [count]);
Angular + Signals:
const count = signal(0);
const double = computed(() => count() * 2);
effect(() => {
console.log(count());
});
В React мы явно указываем зависимости. В Angular — не нужно. Сигналы сами строят граф зависимостей.
Разбор по пунктам
Особенность | React | Angular |
---|---|---|
Инициализация | useState() | signal() |
Побочные эффекты | useEffect() | effect() |
Мемоизация | useMemo() | computed() |
Обновление | setState() | set() или update() |
Зависимости | Явные в массиве | Автоматические |
Удаление эффекта | Возвращаем функция из useEffect | Не нужно — Angular сам удалит |
Angular Signals проще. Меньше кода, меньше ошибок.
Когда это особенно удобно
1. Простой UI-стейт
Забываем про BehaviorSubject
.
// Было:
const count$ = new BehaviorSubject(0);
count$.next(1);
// Стало:
const count = signal(0);
count.set(1);
2. Зависимые значения
const firstName = signal('John');
const lastName = signal('Doe');
const fullName = computed(() => `${firstName()} ${lastName()}`);
Измените firstName
— и fullName
пересчитается автоматически.
3. Побочные эффекты
effect(() => {
console.log('User changed:', user());
});
Не нужно useEffect
, takeUntil
, ngOnDestroy
. Просто effect()
.
Signals и шаблон
Вы можете использовать сигналы прямо в шаблоне Angular:
@Component({
standalone: true,
imports: [CommonModule],
template: `
<button (click)="count.update(c => c + 1)">Increment</button>
<p>{{ count() }}</p>
`,
})
export class CounterComponent {
count = signal(0);
}
Обновляется мгновенно. И — внимание — без ChangeDetectionStrategy.OnPush.
А как же RxJS?
RxJS остаётся — но теперь его не нужно использовать в 100% случаев. Signals хорошо подходят:
— Для UI-состояния
— Для derived state (вычисляемых значений)
— Для изоляции логики в компонентах
RxJS нужен, когда:
— У вас стримы данных (вебсокеты, таймеры)
— Сложная цепочка операторов
— Множественные источники событий
Комбинирование:
Можно обернуть Observable в signal:
toSignal(this.http.get('/user'), { initialValue: null });
Это уже работает. А в Angular 18 будет ещё лучше — fromObservable
и toObservable
.
Signals в больших проектах
Я пробовал внедрить Signals в реальном проекте — админке на Angular 17. И вот что понял:
— Локальные компоненты стало писать проще
— Тестировать — быстрее
— Поддерживать — легче
Вместо отдельных сервисов со Subject
, я держу сигналы прямо в компоненте. Без стейтовых либ, без отписок.
Пример — форма создания пользователя:
export class UserFormComponent {
firstName = signal('');
lastName = signal('');
email = signal('');
fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
}
В шаблоне:
<input [value]="firstName()" (input)="firstName.set($event.target.value)" />
<p>Full name: {{ fullName() }}</p>
Ограничения
Чтобы не звучало как реклама — вот минусы:
— Пока не работает в директивах и пайпах
— Не умеет асинхронность из коробки (async
надо вручную оборачивать)
— Придётся менять привычки, особенно если вы с RxJS
Но всё это уже в разработке — Signals активно развивают.
Стоит ли переписывать старый код?
Нет. Signals — это не миграция, а эволюция.
Начинайте использовать их в новых компонентах. Где нужно быстрое UI-состояние — используйте signal
. Где сложная бизнес-логика — оставайтесь на NgRx
или ComponentStore
.
Angular всегда был «enterprise», и Signals тут не чтобы всё сломать, а чтобы облегчить жизнь.
Будущее
— Angular 18 уже улучшает поддержку Signals
— NgRx
адаптирует Signals (ngrx-signals
)
— Команда Angular делает ставку на signals-first подход
— Signals могут заменить не только Hooks, но и state-менеджеры в UI-слое
Выводы
— Signals — это реактивные переменные внутри Angular
— Они проще, чем RxJS, и чище, чем Hooks
— Angular с ними становится понятнее, быстрее, современнее
— Не нужно переписывать всё. Достаточно начать использовать их в новых компонентах.
Мой опыт
Я был скептиком. Но после пары компонентов на Signals — не хочется возвращаться к BehaviorSubject
. Это тот редкий случай, когда Angular стал проще, а не сложнее. Что дальше? Если ты хочешь почувствовать Angular по-новому — начни с Signals. Один компонент. Один signal
. Скорее всего, дальше ты не остановишься.
Комментарии: