Передача callback функции в дочерний компонент Angular через @Input как в AngularJS
Проблема с передачей callback через @Input
Вы столкнулись с типичной ситуацией при миграции с AngularJS на Angular. Ваш код почти правильный, но есть несколько нюансов, которые нужно учесть. Давайте разберем их по порядку.
Почему возникает ошибка?
Ошибка this.onSave is not a function может возникать по нескольким причинам:
- Контекст выполнения — в Angular при передаче метода класса как callback, контекст
thisможет теряться. - Жизненный цикл компонента — если вы вызываете
this.onSaveдо того, как Angular установит входное свойство (например, в конструкторе илиngOnInitдо привязки). - Типизация — несоответствие сигнатуры функции.
Правильное решение с @Input
Вот как должен выглядеть корректный код:
// ParentComponent
@Component({
template: `<child [onSave]="saveHandler"></child>`
})
export class ParentComponent {
// Используем стрелочную функцию для сохранения контекста
saveHandler = (data: any): void => {
console.log('Saved:', data);
}
}
// ChildComponent
@Component({...})
export class ChildComponent implements OnInit {
@Input() onSave!: (data: any) => void;
ngOnInit() {
// Проверяем, что функция определена
if (typeof this.onSave === 'function') {
this.onSave({name: 'test'});
}
}
}
Ключевые моменты
- Используйте стрелочные функции для методов, которые передаются как callback — это автоматически привязывает контекст
this. - Добавьте проверку
typeof this.onSave === 'function'перед вызовом, чтобы избежать ошибок, если свойство еще не установлено. - Используйте оператор definite assignment assertion (
!) в типе, чтобы показать TypeScript, что свойство будет инициализировано позже.
Рекомендуемый подход: @Output и EventEmitter
Хотя технически можно передавать функции через @Input, в Angular это антипаттерн. Правильный способ — использовать @Output() и EventEmitter:
// ParentComponent
@Component({
template: `<child (save)="saveHandler($event)"></child>`
})
export class ParentComponent {
saveHandler(data: any) {
console.log('Saved:', data);
}
}
// ChildComponent
@Component({...})
export class ChildComponent {
@Output() save = new EventEmitter<any>();
someMethod() {
this.save.emit({name: 'test'});
}
}
Этот подход:
- Соответствует архитектуре Angular и принципу однонаправленного потока данных.
- Не требует дополнительных проверок на существование функции.
- Лучше поддерживается инструментами разработки (например, Ivy компилятором).
- Обеспечивает лучшую производительность за счет оптимизации Change Detection.
Когда @Input с функцией оправдан?
Иногда передача функции через @Input может быть удобна для:
- Передачи статических функций-утилит (например, форматирования).
- Создания кастомных декораторов или HOC-компонентов.
- Интеграции с библиотеками, которые ожидают callback (например, для drag-and-drop).
Но в 95% случаев лучше использовать @Output.
Альтернативный подход: использование сервиса для обмена данными
Если вам нужно передать callback в глубоко вложенный компонент (через несколько уровней), можно использовать сервис с Subject или BehaviorSubject:
// save.service.ts
@Injectable({ providedIn: 'root' })
export class SaveService {
private saveSubject = new Subject<any>();
save$ = this.saveSubject.asObservable();
emitSave(data: any) {
this.saveSubject.next(data);
}
}
// ParentComponent
export class ParentComponent {
constructor(private saveService: SaveService) {
this.saveService.save$.subscribe(data => {
console.log('Saved:', data);
});
}
}
// ChildComponent
export class ChildComponent {
constructor(private saveService: SaveService) {}
someMethod() {
this.saveService.emitSave({name: 'test'});
}
}
Этот подход полезен, когда:
- Компоненты находятся в разных модулях и не связаны иерархически.
- Нужно передать событие через 3+ уровня вложенности (избегая "prop drilling").
- Требуется, чтобы несколько компонентов реагировали на одно событие.
Недостаток: сервис создает глобальное состояние, что может усложнить отладку. Используйте этот метод только когда @Output становится неудобным из-за глубокой вложенности.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги