1. Введение
Давайте представим себе ситуацию: у вас есть родительский компонент, который знает обо всём (например, хранит список товаров, пользователя или параметры приложения). А есть дочерний компонент — отдельная "карточка товара", которая должна отобразить данные о конкретном товаре. Как передать данные о товаре из родителя в дочернего? Вот тут и вступает в игру декоратор @Input().
Если говорить образно, то @Input() — это как открытое окно, через которое родитель может передать своему ребёнку (дочернему компоненту) пирожок (данные). А ребёнок уже решает, что с этим пирожком делать: съесть, показать друзьям или спрятать под подушку.
Как работает @Input(): теория и аналогии
В Angular, когда вы вставляете дочерний компонент в шаблон родителя, вы можете передать ему значения через свойства, помеченные как @Input(). Это похоже на то, как вы задаёте атрибуты обычному HTML-элементу:
<img src="cat.jpg" alt="Котик" />
Только теперь вместо <img> у вас свой компонент:
<app-product-card [product]="selectedProduct"></app-product-card>
Здесь [product] — это входной параметр (input property) дочернего компонента, а selectedProduct — переменная из родителя.
2. Синтаксис: как объявить @Input() в компоненте
Чтобы ваш компонент мог принимать данные извне, нужно:
- Импортировать декоратор Input из '@angular/core'.
- Пометить публичное свойство класса декоратором @Input().
Пример:
// product-card.component.ts
import { Component, Input } from '@angular/core';
import { Product } from '../models/product.model';
@Component({
selector: 'app-product-card',
template: `
<div class="card">
<h2>{{ product.name }}</h2>
<p>Цена: {{ product.price }} руб.</p>
<p *ngIf="product.inStock">В наличии</p>
<p *ngIf="!product.inStock">Нет в наличии</p>
</div>
`
})
export class ProductCardComponent {
@Input() product!: Product; // теперь это свойство может получать данные от родителя
}
Здесь @Input() сообщает Angular: "Это свойство может быть установлено из родительского компонента".
3. Передача данных: пример на практике
Давайте разовьём наше учебное приложение. Пусть у нас есть родительский компонент, который хранит список товаров:
// app.component.ts (родитель)
import { Component } from '@angular/core';
import { Product } from './models/product.model';
@Component({
selector: 'app-root',
template: `
<h1>Каталог товаров</h1>
<app-product-card
*ngFor="let item of products"
[product]="item">
</app-product-card>
`
})
export class AppComponent {
products: Product[] = [
{ name: 'Ноутбук', price: 50000, inStock: true },
{ name: 'Мышка', price: 1200, inStock: false },
{ name: 'Клавиатура', price: 3000, inStock: true }
];
}
А дочерний компонент (app-product-card) мы уже определили выше.
Что здесь происходит?
- Родительский компонент перебирает массив товаров с помощью *ngFor.
- Для каждого товара создаётся экземпляр дочернего компонента <app-product-card>.
- Через [product]="item" каждому дочернему компоненту передаётся свой объект товара.
4. Полезные нюансы
Как это выглядит в шаблоне: квадратные скобки и связывание
В Angular квадратные скобки означают "связать свойство дочернего компонента с выражением из родителя":
<app-product-card [product]="item"></app-product-card>
- [product] — имя входного параметра (input property) дочернего компонента.
- "item" — выражение в контексте родителя (может быть переменная, результат функции и т.д.).
Если вы забыли квадратные скобки, Angular воспримет значение как строковый литерал, а не как переменную!
Несколько @Input(): передаём больше данных
Вы можете объявить сколько угодно входных параметров в компоненте. Например, помимо товара, можно передать режим отображения:
// product-card.component.ts
@Input() product!: Product;
@Input() mode: 'compact' | 'full' = 'full';
В шаблоне родителя:
<app-product-card [product]="item" [mode]="'compact'"></app-product-card>
Псевдонимы для @Input(): когда имя в родителе и дочернем отличается
Иногда хочется, чтобы свойство в родителе называлось иначе, чем поле класса дочернего компонента. Для этого указывают псевдоним:
@Input('productInfo') product!: Product;
Теперь в шаблоне родителя:
<app-product-card [productInfo]="item"></app-product-card>
Это может пригодиться, если вы хотите избежать конфликтов имён или сделать API компонента более читаемым.
Когда происходит обновление @Input()?
Angular отслеживает все изменения входных параметров. Если значение, переданное через [product], изменится у родителя, дочерний компонент получит новое значение.
Важный момент:
Если передаётся объект, Angular не клонирует его, а просто передаёт ссылку. Если вы мутируете объект внутри дочернего компонента — изменения будут видны и родителю (и наоборот). Обычно это нежелательно: старайтесь не изменять входные данные внутри дочернего компонента, если только это не оговорено явно.
Жизненный цикл и ngOnChanges
Если вам нужно выполнить какое-то действие при изменении входных параметров, используйте хук жизненного цикла ngOnChanges:
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-product-card',
template: `...`
})
export class ProductCardComponent implements OnChanges {
@Input() product!: Product;
ngOnChanges(changes: SimpleChanges) {
if (changes['product']) {
// product изменился!
console.log('Получен новый продукт:', this.product);
}
}
}
ngOnChanges вызывается каждый раз, когда Angular обнаруживает изменение значения входного параметра.
5. Типичные ошибки при работе с @Input()
Ошибка №1: Забыли импортировать Input.
Если вы написали @Input() product: Product без import { Input } from '@angular/core';, получите ошибку компиляции. Angular не сможет найти декоратор.
Ошибка №2: Не используете квадратные скобки.
Если написать <app-product-card product="item">, Angular воспримет item как строку "item", а не как переменную. В результате в дочернем компоненте вы получите строку, а не объект, и логика сломается.
Ошибка №3: Мутируете входные данные в дочернем компоненте.
Если внутри дочернего компонента вы изменяете объект, полученный через @Input(), эти изменения увидит родитель. Это может привести к трудноуловимым багам. Лучше воспринимать входные данные как "только для чтения".
Ошибка №4: Несовпадение имён при использовании псевдонимов.
Если задали @Input('info') product: Product, а в родителе пишете [product]="item", Angular не найдёт такого input property. Нужно писать [info]="item".
Ошибка №5: Несоответствие типов.
Если в родителе передаёте строку, а в дочернем ожидается объект, получите ошибку на этапе выполнения или шаблон будет вести себя неожиданно.
Ошибка №6: Используете @Input() для передачи данных "снизу вверх".
@Input() работает только в одну сторону — от родителя к дочернему. Для передачи событий вверх используется @Output() и EventEmitter (об этом — в следующей лекции).
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ