Двусторонняя привязка: model()
Иногда значение должны менять обе стороны: родитель задаёт начальный фильтр, а компонент фильтра меняет его по щелчку. Вместо пары «вход плюс выход» для этого есть модельное свойство — функция model() и двусторонняя привязка [(...)].
Компонент фильтра
ng generate component todo-filter// src/app/todo-filter/todo-filter.ts
import { Component, model } from '@angular/core';
export type Filter = 'all' | 'active' | 'done';
@Component({
selector: 'app-todo-filter',
templateUrl: './todo-filter.html',
styleUrl: './todo-filter.css',
})
export class TodoFilter {
value = model<Filter>('all');
}model() создаёт сигнал, который можно и читать, и менять методом set(). Изменение уходит обратно к родителю.
<!-- src/app/todo-filter/todo-filter.html -->
<button [class.active]="value() === 'all'" (click)="value.set('all')">Все</button>
<button [class.active]="value() === 'active'" (click)="value.set('active')">Активные</button>
<button [class.active]="value() === 'done'" (click)="value.set('done')">Готовые</button>Три кнопки, у выбранной — класс active.
Использование в родителе
Двусторонняя привязка записывается квадратными и круглыми скобками вместе — «банан в коробке». Справа ставят сигнал родителя без скобок вызова:
// src/app/todo-list/todo-list.ts
import { Component, computed, signal } from '@angular/core';
import { TodoItem } from '../todo-item/todo-item';
import { Filter, TodoFilter } from '../todo-filter/todo-filter';
import { Todo } from '../todo';
@Component({
selector: 'app-todo-list',
imports: [TodoItem, TodoFilter],
templateUrl: './todo-list.html',
})
export class TodoList {
todos = signal<Todo[]>([]);
filter = signal<Filter>('all');
visible = computed(() => {
const f = this.filter();
return this.todos().filter((t) =>
f === 'all' ? true : f === 'done' ? t.done : !t.done,
);
});
}<!-- src/app/todo-list/todo-list.html -->
<app-todo-filter [(value)]="filter" />
<ul>
@for (todo of visible(); track todo.id) {
<li><app-todo-item [todo]="todo" /></li>
}
</ul>Нажмите «Готовые» — компонент фильтра вызовет value.set('done'), сигнал filter родителя получит новое значение, visible пересчитается, и в списке останутся только выполненные задачи.
Что происходит внутри
Запись [(value)]="filter" — сокращение для двух привязок:
<!-- src/app/todo-list/todo-list.html -->
<app-todo-filter [value]="filter()" (valueChange)="filter.set($event)" />model() автоматически создаёт выход с именем valueChange. Можно писать и так, но короткая форма удобнее.
Когда что выбирать
| Ситуация | Что использовать |
|---|---|
| Ребёнок только показывает данные | input() |
| Ребёнок сообщает о действии | output() |
| Ребёнок редактирует значение родителя | model() |
Так устроены собственные поля ввода: переключатели, выбор даты, рейтинг звёздами.
Частая ошибка
Писать в привязке вызов сигнала: [(value)]="filter()". Так в компонент уйдёт просто строка 'all', и изменить её обратно будет некуда — сборка выдаст ошибку. В двусторонней привязке указывайте сам сигнал: [(value)]="filter".