Выходные события: output()
Выход (output) — это собственное событие компонента. Дочерний компонент не меняет данные родителя сам, а сообщает: «пользователь отметил задачу». Родитель подписывается на это событие так же, как на click, и решает, что делать.
Объявление выхода
Функция output() создаёт событие. В угловых скобках — тип данных, которые оно передаёт:
// src/app/todo-item/todo-item.ts
import { Component, input, output } from '@angular/core';
import { Todo } from '../todo';
@Component({
selector: 'app-todo-item',
templateUrl: './todo-item.html',
styleUrl: './todo-item.css',
})
export class TodoItem {
todo = input.required<Todo>();
toggle = output<number>();
onToggle() {
this.toggle.emit(this.todo().id);
}
}Метод emit() отправляет событие с номером задачи.
<!-- src/app/todo-item/todo-item.html -->
<label>
<input type="checkbox" [checked]="todo().done" (change)="onToggle()" />
{{ todo().title }}
</label>Щелчок по флажку вызывает onToggle(), а тот — emit().
Подписка в родителе
Родитель пишет имя выхода в круглых скобках. Значение, переданное в emit(), доступно как $event:
<!-- src/app/todo-list/todo-list.html -->
<ul>
@for (todo of todos(); track todo.id) {
<li><app-todo-item [todo]="todo" (toggle)="toggleTodo($event)" /></li>
}
</ul>// src/app/todo-list/todo-list.ts
toggleTodo(id: number) {
this.todos.update((list) =>
list.map((t) => (t.id === id ? { ...t, done: !t.done } : t)),
);
}Отметьте флажок — задача станет выполненной, а счётчик «Осталось сделать» из урока о сигналах уменьшится. Данные изменил тот, кто ими владеет, — родитель.
Событие без данных
Если передавать нечего, тип не указывают и emit() вызывают без аргумента. Добавим кнопку удаления:
// src/app/todo-item/todo-item.ts
remove = output();<!-- src/app/todo-item/todo-item.html -->
<button (click)="remove.emit()">Удалить</button><!-- src/app/todo-list/todo-list.html -->
<app-todo-item
[todo]="todo"
(toggle)="toggleTodo($event)"
(remove)="removeTodo(todo.id)"
/>// src/app/todo-list/todo-list.ts
removeTodo(id: number) {
this.todos.update((list) => list.filter((t) => t.id !== id));
}Номер задачи родитель знает сам — он берёт его из переменной todo цикла @for.
Входы и выходы вместе
| Направление | Функция | Запись в шаблоне родителя |
|---|---|---|
| Родитель → ребёнок | input() | [todo]="todo" |
| Ребёнок → родитель | output() | (toggle)="toggleTodo($event)" |
Данные идут вниз, события — вверх. Это главное правило связи компонентов.
Частая ошибка
Называть выход как встроенное событие браузера, например click или change. На теге компонента сработают оба события: ваше и всплывшее из кнопки внутри, — и обработчик вызовется дважды, причём один раз с объектом MouseEvent. Давайте выходам говорящие имена: toggle, remove, saved.