Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Двусторонняя привязка: 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".

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.