Angular
linkedSignal: зависимое перезаписываемое состояние
На странице товара есть выпадающий список размеров и выбранный размер. Когда покупатель переходит к другому товару, набор размеров меняется, и старый выбор становится неактуальным. Нужно сбросить выбор на первый доступный размер нового товара - но при этом покупатель всё ещё должен иметь право выбрать любой другой размер вручную. computed не подходит: он только для чтения. Обычный signal не подходит: он не сбрасывается сам при смене товара. Ровно для этого зазора между ними и создан linkedSignal: зависимое состояние, которое сбрасывается от источника, но остаётся перезаписываемым.
- Выбор элемента в списке: при смене списка выбор сбрасывается на первый, но пользователь может выбрать другой
- Редактируемая копия данных с сервера: значение приходит из источника, но пользователь правит его в форме
- Пагинация: текущая страница сбрасывается на первую при смене фильтра, но листается вручную
- Выбор размера или варианта товара: обнуляется при переходе к другому товару в каталоге
- Активная вкладка: возвращается к первой при смене набора вкладок, но переключается кликом
Предварительные знания
- Понимание computed: производное значение только для чтения с автоотслеживанием зависимостей
- Понимание перезаписываемого signal: методы set и update
- Знание, почему писать сигналы из effect - антипаттерн
Зачем понадобился linkedSignal
После выхода signal и computed обнаружился частый паттерн, который ни один из них не покрывал чисто. Разработчикам нужно было состояние, которое по умолчанию следует за источником, но при этом допускает ручную перезапись пользователем. Без специального инструмента это решали через effect с записью сигнала, что было антипаттерном, или через громоздкие связки computed и отдельного флага. Команда Angular добавила linkedSignal в developer preview в версии 19 (конец 2024 года), закрыв этот зазор. К Angular 21 linkedSignal стал штатным способом выражать зависимое, но перезаписываемое состояние.
linkedSignal: производное, но перезаписываемое
linkedSignal в простой форме принимает функцию вычисления, как computed, и возвращает перезаписываемый сигнал. Пока источник не меняется, сигнал ведёт себя как обычный: его можно читать, а также писать через set и update. Но как только меняется любой сигнал, прочитанный в функции вычисления, значение сбрасывается на свежий результат этой функции, отменяя предыдущую ручную правку.
Ключевое отличие от computed видно во второй строке примера: selectedSize.set('L') невозможно для computed, потому что тот доступен только для чтения. linkedSignal же допускает ручную запись. И при этом сохраняет связь с источником: смена sizes сбрасывает выбор.
- computed — Зависит от источника, всегда отражает его, но недоступен для записи. Ручной выбор пользователя выразить нельзя
- linkedSignal — Зависит от источника и сбрасывается при его изменении, но между сбросами допускает ручную запись через set и update
Чем linkedSignal отличается от computed?
Сброс выбора при смене источника
Главный сценарий linkedSignal - сброс зависимого состояния при изменении источника. Рассмотрим список и выбранный в нём элемент. Пока список тот же, выбор сохраняется и пользователь может его менять. Но при замене списка старый выбор может стать недействительным, и логично сбросить его на разумное значение по умолчанию.
Расширенная форма принимает объект с полями source, computation и необязательным equal. В computation доступен второй аргумент previous - объект с прежним значением сигнала и прежним значением источника. Это позволяет сохранить выбор пользователя, если выбранный элемент всё ещё присутствует в новом списке, и сбросить только если он исчез.
- source задаёт сигнал-источник, изменение которого запускает пересчёт
- computation получает текущее значение источника и объект previous с прежними значениями
- Возвращённое значение становится новым значением сигнала, пока пользователь не перепишет его вручную
- Необязательный equal управляет тем, считается ли значение изменившимся
Доступ к previous - то, чего нельзя получить в обычном computed. Благодаря ему linkedSignal способен принимать умное решение: сохранять прежний выбор или сбрасывать его, исходя из того, что было раньше.
Что даёт аргумент previous в функции computation расширенной формы linkedSignal?
Где linkedSignal среди других инструментов
У трёх сигнальных инструментов чёткое разделение ролей. signal - полностью независимое состояние, которое ни от чего не выводится. computed - производное значение, которое всегда отражает источники и недоступно для записи. linkedSignal - зависимое состояние, которое следует за источником по умолчанию, но допускает ручную перезапись между сбросами.
| Инструмент | Зависит от источника | Перезаписываемый | Когда выбирать |
|---|---|---|---|
| signal | Нет | Да | Независимое состояние без связи с другими данными |
| computed | Да | Нет | Чистое производное значение, всегда отражающее источники |
| linkedSignal | Да (с возможностью переопределить) | Да | Состояние со значением по умолчанию из источника плюс ручная правка |
Практический критерий выбора. Если значение целиком определяется другими данными и менять его руками не нужно - берётся computed. Если состояние ни от чего не выводится - берётся signal. А если нужно и то, и другое: значение по умолчанию из источника, которое пользователь может переопределить, а при смене источника оно снова сбрасывается - это ровно случай linkedSignal.
Соблазн решить эту задачу через effect, который при изменении источника вызывает set у обычного сигнала, ведёт к антипаттерну из прошлого урока: неявный каскад и риск цикла. linkedSignal делает то же самое декларативно и безопасно, без записи сигналов из эффектов.
Нужна текущая страница пагинации, которая сбрасывается на первую при смене фильтра, но листается пользователем вручную. Что выбрать?
Связь с другими темами
linkedSignal заполняет пробел между чистым производным значением и независимым состоянием:
- computed — Тоже зависит от источников, но доступен только для чтения. linkedSignal добавляет возможность ручной записи
- effect — Попытку сбрасывать зависимый сигнал через effect заменяет linkedSignal, без побочных каскадов
- Сигналы — Результат linkedSignal - перезаписываемый сигнал, у него есть set и update
Итог
- linkedSignal создаёт перезаписываемый сигнал, значение которого по умолчанию вычисляется из источника
- При изменении источника значение сбрасывается на пересчитанное, отменяя предыдущую ручную перезапись
- Между сбросами с сигналом можно работать как с обычным: вызывать set и update
- Расширенная форма с source, computation и equal даёт доступ к предыдущему значению и контроль над сравнением
- linkedSignal занимает место между computed (только чтение) и signal (полностью независимый), решая задачу зависимого перезаписываемого состояния
Связанные уроки
- ng-12-computed — linkedSignal развивает идею производного состояния из computed, добавляя возможность записи
- ng-13-effect — linkedSignal - правильная замена попытке писать зависимый сигнал из effect
- ng-11-signals-intro — linkedSignal возвращает перезаписываемый сигнал с set и update