1. Зачем нужны вложенные компоненты?
Angular — это не просто набор файлов и шаблонов, а целая архитектура, построенная вокруг компонентов. Каждый компонент — как деталь конструктора LEGO: сам по себе он что-то умеет, но настоящая магия начинается, когда вы соединяете детали вместе.
В реальном приложении редко бывает ситуация, когда на странице один-единственный компонент. Обычно есть "главная" страница (root component), внутри которой есть шапка, меню, контент, подвал, карточки, списки, формы и так далее. Всё это — компоненты, которые вложены друг в друга.
Аналогия: Представьте себе сайт как дом. Дом состоит из комнат (компонентов), комнаты — из мебели (подкомпонентов), мебель — из деталей… и так далее. В Angular вы строите такой дом с помощью иерархии компонентов.
Как устроена иерархия компонентов
В Angular всё начинается с "корневого" компонента — обычно это AppComponent. Он объявляется в файле main.ts как точка входа, а дальше строится дерево компонентов.
AppComponent
├── HeaderComponent
├── MenuComponent
├── ContentComponent
│ ├── ArticleListComponent
│ │ ├── ArticleCardComponent
│ │ ├── ArticleCardComponent
│ │ └── ArticleCardComponent
│ └── ArticleDetailsComponent
└── FooterComponent
В жизни:
— AppComponent — весь сайт.
— Внутри — header, меню, контент, подвал.
— Внутри контента — список статей, каждая статья — отдельный компонент.
2. Создание вложенных компонентов с помощью CLI
Angular CLI умеет создавать компоненты в подпапках, чтобы поддерживать порядок в проекте.
Пример
ng generate component shared/header
ng generate component shared/footer
ng generate component articles/article-list
ng generate component articles/article-card
Это создаст структуру:
src/app/
shared/
header/
header.component.ts
header.component.html
...
footer/
footer.component.ts
...
articles/
article-list/
article-card/
Совет: Чем больше приложение, тем важнее логически группировать компоненты по папкам.
3. Как вставить компонент в шаблон другого компонента
Каждый компонент имеет свой "селектор" (selector) — строку, по которой Angular узнаёт, где разместить компонент в DOM.
Пример:
В компоненте HeaderComponent селектор будет, например, 'app-header'.
Чтобы вставить этот компонент в шаблон другого компонента, просто используйте селектор как HTML-тег:
<!-- app.component.html -->
<app-header></app-header>
<main>
<app-article-list></app-article-list>
</main>
<app-footer></app-footer>
Важно:
— Имя селектора пишется в kebab-case (app-header, не AppHeader).
— Angular автоматически "узнает" тег и вставит туда содержимое компонента.
4. Standalone-компоненты и вложенность
В Angular 15+ можно создавать standalone-компоненты (без модулей). Но чтобы вложить один standalone-компонент в другой, нужно явно указать зависимость через массив imports.
Пример
// article-list.component.ts
import { Component } from '@angular/core';
import { ArticleCardComponent } from '../article-card/article-card.component';
@Component({
selector: 'app-article-list',
standalone: true,
imports: [ArticleCardComponent], // <-- импортируем вложенный компонент
template: `
<h2>Список статей</h2>
<app-article-card></app-article-card>
<app-article-card></app-article-card>
`
})
export class ArticleListComponent {}
Если забыть про imports:
Angular не узнает, что <app-article-card> — это компонент, и покажет ошибку в консоли.
5. Пример: строим мини-приложение «Список статей»
Давайте соберём простой пример с вложенными компонентами.
У нас будет:
- AppComponent — корневой.
- HeaderComponent — шапка.
- ArticleListComponent — список статей.
- ArticleCardComponent — карточка статьи.
Создаём компоненты
ng generate component shared/header --standalone
ng generate component articles/article-list --standalone
ng generate component articles/article-card --standalone
Реализуем компонент карточки статьи
// article-card.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-article-card',
standalone: true,
template: `
<div class="card">
<h3>{{ title }}</h3>
<p>{{ summary }}</p>
</div>
`
})
export class ArticleCardComponent {
@Input() title = '';
@Input() summary = '';
}
Список статей — вставляем карточки
// article-list.component.ts
import { Component } from '@angular/core';
import { ArticleCardComponent } from '../article-card/article-card.component';
@Component({
selector: 'app-article-list',
standalone: true,
imports: [ArticleCardComponent],
template: `
<h2>Статьи</h2>
<app-article-card
*ngFor="let article of articles"
[title]="article.title"
[summary]="article.summary">
</app-article-card>
`
})
export class ArticleListComponent {
articles = [
{ title: 'Первый пост', summary: 'Краткое описание первого поста' },
{ title: 'Второй пост', summary: 'Что-то интересное про Angular' }
];
}
Корневой компонент — собираем всё вместе
// app.component.ts
import { Component } from '@angular/core';
import { HeaderComponent } from './shared/header/header.component';
import { ArticleListComponent } from './articles/article-list/article-list.component';
@Component({
selector: 'app-root',
standalone: true,
imports: [HeaderComponent, ArticleListComponent],
template: `
<app-header></app-header>
<app-article-list></app-article-list>
`
})
export class AppComponent {}
Итоговая структура
AppComponent
├── HeaderComponent
└── ArticleListComponent
└── ArticleCardComponent (много раз)
6. Полезные нюансы
Как Angular связывает компоненты между собой
Angular строит дерево компонентов (component tree) во время рендеринга. Каждый компонент — это отдельный класс + шаблон. Angular "понимает", что <app-article-card> — это не просто тег, а отдельный компонент, потому что вы его импортировали.
Важный момент:
Если один компонент вложен в другой, но не указан в imports, Angular не сможет его найти и покажет ошибку компиляции.
Как работает передача данных между компонентами
Вложенные компоненты часто получают данные от родительских через свойства с декоратором @Input().
Пример:
Родитель (ArticleListComponent) передаёт данные карточке (ArticleCardComponent):
<app-article-card
[title]="article.title"
[summary]="article.summary">
</app-article-card>
Внутри ArticleCardComponent есть свойства @Input() title и @Input() summary, которые получают значения.
Глубина вложенности и best practices
- Не стоит делать слишком глубокую иерархию (10+ уровней) — это усложняет понимание кода.
- Каждый компонент должен отвечать за свою "зону ответственности" (Single Responsibility Principle).
- Вложенные компоненты помогают переиспользовать код и упрощают тестирование.
Схема: дерево компонентов
graph TD;
AppComponent --> HeaderComponent
AppComponent --> ArticleListComponent
ArticleListComponent --> ArticleCardComponent
ArticleCardComponent --> AuthorInfoComponent
7. Типичные ошибки при работе с вложенными компонентами
Ошибка №1: забыли добавить компонент в imports.
Angular не поймёт, что <app-child> — это компонент, и покажет ошибку:
NG0903: Component 'AppChild' is not included in the module's imports array!
Ошибка №2: опечатка в селекторе.
Селектор должен совпадать с тем, что указан в компоненте. Если в шаблоне написано <app-articlecard>, а селектор — 'app-article-card', компонент не найдётся.
Ошибка №3: передача данных не через @Input().
Если попытаться передать данные просто как свойство (без декоратора @Input()), Angular проигнорирует это свойство.
Ошибка №4: циклическая вложенность.
Если компонент A вставляет компонент B, а B — снова A, возникает бесконечная рекурсия и приложение падает.
Ошибка №5: слишком большая ответственность компонента.
Если один компонент делает всё: и отображает список, и отдельные элементы, и логику фильтрации, и формы, — код становится нечитаемым. Разбивайте на вложенные компоненты!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ