JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /@Input(): передача дан...

@Input(): передача данных от родителя к дочернему компоненту

Модуль 4: Node.js, Next.js и Angular
14 уровень , 6 лекция
Открыта

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() в компоненте

Чтобы ваш компонент мог принимать данные извне, нужно:

  1. Импортировать декоратор Input из '@angular/core'.
  2. Пометить публичное свойство класса декоратором @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 (об этом — в следующей лекции).

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ