Использование 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), }