Использование standalone-компонентов в Angular
В Angular 14 был представлен альтернативный способ написания приложений — standalone (автономные) компоненты, директивы и пайпы. Такие компоненты могут использоваться независимо от NgModule.
Для наглядности попробуем создать свое тестовое приложение:
npm init @angular ng17
Следующим шагом является удаление файла app.module.ts и замена функции bootstrapModule() в main.ts на bootstrapApplication():
import { enableProdMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
bootstrapApplication(AppComponent)Функция bootstrapApplication()может принимать список провайдеров, которые потребуются для корневому компоненту, а так же всем дочерним:
import { importProvidersFrom } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { HttpClientModule } from '@angular/common/http'
bootstrapApplication(AppComponent, {
providers: [importProvidersFrom(HttpClientModule)]
}).catch(err => console.error(err));Функция importProviderstFrom извлекает провайдеры из модуля.
Далее доработаем AppComponent, чтобы он стал автономным. Просто добавим флаг standalone со значением true:
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
standalone: true,
styleUrls: ['./app.component.scss']
})
export class AppComponent {}Сделав компонент автономным, у разработчика появляется возможность использовать новое свойство imports (аналогично NgModule).
Одни автономные компоненты могут импортировать другие автономные компоненты, директивы, пайпы, а так же NgModules.
Для примера создадим автономную директиву, и переиспользуем в компоненте.
npx ng g directive foo --standalone
import { Directive } from '@angular/core';
@Directive({
selector: '[appFoo]',
standalone: true
})
export class FooDirective {}import { CommonModule } from '@angular/common';
import { FooDirective } from './foo.directive';
@Component({
selector: 'app-root',
template: `
<div appFoo *ngIf="bar">Foo</div>
`,
standalone: true,
imports: [FooDirective, CommonModule]
})
export class AppComponent {}Далее добавим маршрутизацию к приложению. Возможно вы захотите добавить её в AppComponent:
const routes: Routes = [{
path: 'todos',
component: TodosPageComponent
}]
@Component({
selector: 'app-root',
template: `
<a routerLink="/todos">Todos</a>
<router-outlet></router-outlet>
`,
standalone: true,
imports: [RouterModule.forRoot(routes)],
styleUrls: ['./app.component.scss']
})
export class AppComponent {}К сожалению, это невозможно, потому что Angular не позволяет использовать ModuleWithProvider в автономном компоненте. Для этого можно использовать новую функцию importProvidersFrom в провайдерах компонента:
const routes: Routes = [{
path: 'todos',
component: TodosPageComponent
}]
@Component({
selector: 'app-root',
template: `
<a routerLink="/todos">Todos</a>
<router-outlet></router-outlet>
`,
standalone: true,
providers: importProvidersFrom(RouterModule.forRoot(routes)),
imports: [FooDirective, CommonModule],
styleUrls: ['./app.component.scss']
})
export class AppComponent {}Использование навигации в приложении будет работать; однако маршрутизатор пропустит первый переход. Инициализация маршрутизатора должна выполняться в процессе запуска:
bootstrapApplication(AppComponent, {
providers: [importProvidersFrom(RouterModule.forRoot(routes))]
}).catch(err => console.error(err));Далее реализуем ленивую загрузку:
import { Routes } from '@angular/router';
export const todosRoutes: Routes = [
{
path: 'todos',
title: 'Todos Page',
children: [
{
path: '',
loadComponent: () =>
import('./todos-page.component').then((m) => m.TodosPageComponent),
children: [
{
path: ':id',
loadComponent: () =>
import('./todo-page/todo-page.component').then(
(m) => m.TodoPageComponent
),
},
],
},
],
},
];Вместо использования loadChildren и передачи NgModule используется свойство loadComponent. Также можно объявить провайдеры для этого маршрута и его дочерних элементов, используя новое свойство providers:
import { Routes } from '@angular/router';
export const todosRoutes: Routes = [
{
path: 'todos',
title: 'Todos Page',
providers: [
{
provide: 'Angular',
useValue: 'v14',
},
],
children: [
{
path: '',
loadComponent: () =>
import('./todos-page.component').then((m) => m.TodosPageComponent),
children: [
{
path: ':id',
loadComponent: () =>
import('./todo-page/todo-page.component').then(
(m) => m.TodoPageComponent
),
},
],
},
],
},
];... или передать массив маршрутов в loadChildren:
export const ROUTES: Route[] = [
{ path: 'child', component: ChildCmp},
]{
path: 'parent',
loadChildren: () => import('./children').then(m => m.ROUTES),
}