On this page
Getting started with standalone components
The standalone component feature is available for developer preview. It's ready for you to try, but it might change before it is stable.
In v14 and higher, standalone components provide a simplified way to build Angular applications. Standalone components, directives, and pipes aim to streamline the authoring experience by reducing the need for NgModule
s. Existing applications can optionally and incrementally adopt the new standalone style without any breaking changes.
Creating standalone components
The standalone
flag and component imports
Components, directives, and pipes can now be marked as standalone: true
. Angular classes marked as standalone do not need to be declared in an NgModule
(the Angular compiler will report an error if you try).
Standalone components specify their dependencies directly instead of getting them through NgModule
s. For example, if PhotoGalleryComponent
is a standalone component, it can directly import another standalone component ImageGridComponent
:
@Component({
standalone: true,
selector: 'photo-gallery',
imports: [ImageGridComponent],
template: `
... <image-grid [images]="imageList"></image-grid>
`,
})
export class PhotoGalleryComponent {
// component logic
}
imports
can also be used to reference standalone directives and pipes. In this way, standalone components can be written without the need to create an NgModule
to manage template dependencies.
Using existing NgModules in a standalone component
When writing a standalone component, you may want to use other components, directives, or pipes in the component's template. Some of those dependencies might not be marked as standalone, but instead declared and exported by an existing NgModule
. In this case, you can import the NgModule
directly into the standalone component:
@Component({
standalone: true,
selector: 'photo-gallery',
// an existing module is imported directly into a standalone component
imports: [MatButtonModule],
template: `
...
<button mat-button>Next Page</button>
`,
})
export class PhotoGalleryComponent {
// logic
}
You can use standalone components with existing NgModule
-based libraries or dependencies in your template. Standalone components can take full advantage of the existing ecosystem of Angular libraries.
Using standalone components in NgModule-based applications
Standalone components can also be imported into existing NgModules-based contexts. This allows existing applications (which are using NgModules today) to incrementally adopt the new, standalone style of component.
You can import a standalone component (or directive, or pipe) just like you would an NgModule
- using NgModule.imports
:
@NgModule({
declarations: [AlbumComponent],
exports: [AlbumComponent],
imports: [PhotoGalleryComponent],
})
export class AlbumModule {}
Bootstrapping an application using a standalone component
An Angular application can be bootstrapped without any NgModule
by using a standalone component as the application's root component. This is done using the bootstrapApplication
API:
// in the main.ts file
import {bootstrapApplication} from '@angular/platform-browser';
import {PhotoAppComponent} from './app/photo.app.component';
bootstrapApplication(PhotoAppComponent);
Configuring dependency injection
When bootstrapping an application, often you want to configure Angular’s dependency injection and provide configuration values or services for use throughout the application. You can pass these as providers to bootstrapApplication
:
bootstrapApplication(PhotoAppComponent, {
providers: [
{provide: BACKEND_URL, useValue: 'https://photoapp.looknongmodules.com/api'},
// ...
]
});
The standalone bootstrap operation is based on explicitly configuring a list of Provider
s for dependency injection. However, existing libraries may rely on NgModule
s for configuring DI. For example, Angular’s router uses the RouterModule.forRoot()
helper to set up routing in an application. You can use these existing NgModule
s in bootstrapApplication
via the importProvidersFrom
utility:
bootstrapApplication(PhotoAppComponent, {
providers: [
{provide: BACKEND_URL, useValue: 'https://photoapp.looknongmodules.com/api'},
importProvidersFrom(
RouterModule.forRoot([/* app routes */]),
),
// ...
]
});
Routing and lazy-loading
The router APIs were updated and simplified to take advantage of the standalone components: an NgModule
is no longer required in many common, lazy-loading scenarios.
Lazy loading a standalone component
Any route can lazily load its routed, standalone component by using loadComponent
:
export const ROUTES: Route[] = [
{path: 'admin', loadComponent: () => import('./admin/panel.component').then(mod => mod.AdminPanelComponent)},
// ...
];
This works as long as the loaded component is standalone.
Lazy loading many routes at once
The loadChildren
operation now supports loading a new set of child Route
s without needing to write a lazy loaded NgModule
that imports RouterModule.forChild
to declare the routes. This works when every route loaded this way is using a standalone component.
// In the main application:
export const ROUTES: Route[] = [
{path: 'admin', loadChildren: () => import('./admin/routes').then(mod => mod.ADMIN_ROUTES)},
// ...
];
// In admin/routes.ts:
export const ADMIN_ROUTES: Route[] = [
{path: 'home', component: AdminHomeComponent},
{path: 'users', component: AdminUsersComponent},
// ...
];
Providing services to a subset of routes
The lazy loading API for NgModule
s (loadChildren
) creates a new "module" injector when it loads the lazily loaded children of a route. This feature was often useful to provide services only to a subset of routes in the application. For example, if all routes under /admin
were scoped using a loadChildren
boundary, then admin-only services could be provided only to those routes. Doing this required using the loadChildren
API, even if lazy loading of the routes in question was unnecessary.
The Router now supports explicitly specifying additional providers
on a Route
, which allows this same scoping without the need for either lazy loading or NgModule
s. For example, scoped services within an /admin
route structure would look like:
export const ROUTES: Route[] = [
{
path: 'admin',
providers: [
AdminService,
{provide: ADMIN_API_KEY, useValue: '12345'},
],
children: [
{path: 'users', component: AdminUsersComponent},
{path: 'teams', component: AdminTeamsComponent},
],
},
// ... other application routes that don't
// have access to ADMIN_API_KEY or AdminService.
];
It's also possible to combine providers
with loadChildren
of additional routing configuration, to achieve the same effect of lazy loading an NgModule
with additional routes and route-level providers. This example configures the same providers/child routes as above, but behind a lazy loaded boundary:
// Main application:
export const ROUTES: Route[] = {
// Lazy-load the admin routes.
{path: 'admin', loadChildren: () => import('./admin/routes').then(mod => mod.ADMIN_ROUTES)},
// ... rest of the routes
}
// In admin/routes.ts:
export const ADMIN_ROUTES: Route[] = [{
path: '',
pathMatch: 'prefix',
providers: [
AdminService,
{provide: ADMIN_API_KEY, useValue: 12345},
],
children: [
{path: 'users', component: AdminUsersCmp},
{path: 'teams', component: AdminTeamsCmp},
],
}];
Note the use of an empty-path route to host providers
that are shared among all the child routes.
Advanced topics
This section goes into more details that are relevant only to more advanced usage patterns. You can safely skip this section when learning about standalone components, directives, and pipes for the first time.
Standalone components for library authors
Standalone components, directives, and pipes can be exported from NgModule
s that import them:
@NgModule({
imports: [ImageCarouselComponent, ImageSlideComponent],
exports: [ImageCarouselComponent, ImageSlideComponent],
})
export class CarouselModule {}
This pattern is useful for Angular libraries that publish a set of cooperating directives. In the above example, both the ImageCarouselComponent
and ImageSlideComponent
need to be present in a template to build up one logical "carousel widget".
As an alternative to publishing a NgModule
, library authors might want to export an array of cooperating directives:
export const CAROUSEL_DIRECTIVES = [ImageCarouselComponent, ImageSlideComponent] as const;
Such an array could be imported by applications using NgModule
s and added to the @NgModule.imports
. Please note the presence of the TypeScript’s as const
construct: it gives Angular compiler additional information required for proper compilation and is a recommended practice (as it makes the exported array immutable from the TypeScript point of view).
Dependency injection and injectors hierarchy
Angular applications can configure dependency injection by specifying a set of available providers. In a typical application, there are two different injector types:
- module injector with providers configured in
@NgModule.providers
or@Injectable({providedIn: "..."})
. Those application-wide providers are visible to all components in as well as to other services configured in a module injector. - node injectors configured in
@Directive.providers
/@Component.providers
or@Component.viewProviders
. Those providers are visible to a given component and all its children only.
Environment injectors
Making NgModule
s optional will require new ways of configuring "module" injectors with application-wide providers (for example, HttpClient). In the standalone application (one created with bootstrapApplication
), “module” providers can be configured during the bootstrap process, in the providers
option:
bootstrapApplication(PhotoAppComponent, {
providers: [
{provide: BACKEND_URL, useValue: 'https://photoapp.looknongmodules.com/api'},
{provide: PhotosService, useClass: PhotosService},
// ...
]
});
The new bootstrap API gives us back the means of configuring “module injectors” without using NgModule
s. In this sense, the “module” part of the name is no longer relevant and we’ve decided to introduce a new term: “environment injectors”.
Environment injectors can be configured using one of the following:
@NgModule.providers
(in applications bootstrapping through anNgModule
);@Injectable({provideIn: "..."})
(in both the NgModule-based and the “standalone” applications);providers
option in thebootstrapApplication
call (in fully “standalone” applications);providers
field in aRoute
configuration.
Angular v14 introduces a new TypeScript type EnvironmentInjector
to represent this new naming. The accompanying createEnvironmentInjector
API makes it possible to create environment injectors programmatically:
import {createEnvironmentInjector} from '@angular/core';
const parentInjector = … // existing environment injector
const childInjector = createEnvironmentInjector([{provide: PhotosService, useClass: CustomPhotosService}], parentInjector);
Environment injectors have one additional capability: they can execute initialization logic when an environment injector gets created (similar to the NgModule
constructors that get executed when a module injector is created):
import {createEnvironmentInjector, ENVIRONMENT_INITIALIZER} from '@angular/core';
createEnvironmentInjector([
{provide: PhotosService, useClass: CustomPhotosService},
{provide: ENVIRONMENT_INITIALIZER, useValue: () => {
console.log("This function runs when this EnvironmentInjector gets created");
}}
]);
Standalone injectors
In reality, the dependency injectors hierarchy is slightly more elaborate in applications using standalone components. Let’s consider the following example:
// an existing "datepicker" component with an NgModule
@Component({
selector: 'datepicker',
template: '...',
})
class DatePickerComponent {
constructor(private calendar: CalendarService) {}
}
@NgModule({
declarations: [DatePickerComponent],
exports: [DatePickerComponent]
providers: [CalendarService],
})
class DatePickerModule {
}
@Component({
selector: 'date-modal',
template: '<datepicker></datepicker>',
standalone: true,
imports: [DatePickerModule]
})
class DateModalComponent {
}
In the above example, the component DateModalComponent
is standalone - it can be consumed directly and has no NgModule which needs to be imported in order to use it. However, DateModalComponent
has a dependency, the DatePickerComponent,
which is imported via its NgModule (the DatePickerModule
). This NgModule may declare providers (in this case: CalendarService
) which are required for the DatePickerComponent
to function correctly.
When Angular creates a standalone component, it needs to know that the current injector has all of the necessary services for the standalone component's dependencies, including those based on NgModules. To guarantee that, in some cases Angular will create a new "standalone injector" as a child of the current environment injector. Today, this happens for all bootstrapped standalone components: it will be a child of the root environment injector. The same rule applies to the dynamically created (for example, by the router or the ViewContainerRef
API) standalone components.
A separate standalone injector is created to ensure that providers imported by a standalone component are “isolated” from the rest of the application. This lets us think of standalone components as truly self-contained pieces that can’t “leak” their implementation details to the rest of the application.
© 2010–2023 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://v14.angular.io/guide/standalone-components