⚠️ This article was written before we released standalone NgRx APIs. They are available from v14.3.0. Read more here.
In this article, we'll look into the standalone Angular APIs introduced in version 14. We will then explore ways how to use NgRx packages with standalone features.
Contents
- Standalone Angular APIs
- Angular Modules from NgRx Packages
- Standalone NgRx APIs
- Source Code
- Resources
- Peer Reviewers
Standalone Angular APIs
With standalone Angular APIs, we can build Angular applications without NgModules. In other words, components, directives, and pipes can be used without declaration in any Angular module.
💡 In Angular 14, standalone APIs are in developer preview and may change in the future without backward compatibility.
Creating Standalone Components
To create a standalone component, we need to set the standalone
flag to true
and register template dependencies using the imports
property within the component configuration. The imports
array can accept Angular modules or other standalone components, directives, or pipes:
// header.component.ts
@Component({
selector: 'app-header',
template: `
<a routerLink="/">Home</a>
<a *ngIf="isAuthenticated$ | async" routerLink="/">Musicians</a>
`,
standalone: true,
// importing modules whose declarables are used in the template
imports: [CommonModule, RouterModule],
})
export class HeaderComponent {
readonly isAuthenticated$ = this.authService.isAuthenticated$;
constructor(private readonly authService: AuthService) {}
}
// app.component.ts
@Component({
selector: 'app-root',
template: `
<app-header></app-header>
<router-outlet></router-outlet>
`,
standalone: true,
// importing `HeaderComponent` as a template dependency
imports: [RouterModule, HeaderComponent],
})
export class AppComponent {}
AppModule
is no longer required to bootstrap the application. Instead, we can use the bootstrapApplication
function from the @angular/platform-browser
package that accepts the root component as an input argument:
// main.ts
bootstrapApplication(AppComponent);
The bootstrapApplication
function accepts an object with providers as a second argument, so we can provide services at the root level as follows:
bootstrapApplication(AppComponent, {
providers: [
{ provide: ErrorHandler, useClass: CustomErrorHandler },
],
});
Interop with Angular Modules
Now the question is, how to provide services from existing Angular modules. Fortunately, there is a new function importProvidersFrom
from the @angular/core
package that accepts a sequence of Angular modules as an input argument and returns their providers as a result:
const providers = importProvidersFrom(
HttpClientModule,
// ... other modules
);
Providers returned by the importProvidersFrom
function can be registered at the root level in the following way:
bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(HttpClientModule),
],
});
Configuring Angular Router
In Angular 14, there is an option to register providers at the route level by adding the providers
array to the Route
object. This gives the ability to define feature-level providers in the following way:
// musicians.routes.ts
export const musiciansRoutes: Route[] = [
{
path: '',
// registering providers for the route and all its children
providers: [
{ provide: MusiciansService, useClass: MusiciansHttpService },
importProvidersFrom(NgModule1, NgModule2),
],
children: [
{
path: '',
component: MusicianListComponent,
},
{
path: ':id',
component: MusicianDetailsComponent,
canActivate: [MusicianExistsGuard],
},
],
},
];
Then, we can lazy load feature routes using the loadChildren
property in the application routes configuration:
// app.routes.ts
export const appRoutes: Route[] = [
{ path: '', component: HomeComponent },
{
path: 'musicians',
// importing `musiciansRoutes` using the `loadChildren` property
loadChildren: () =>
import('@musicians/musicians.routes').then(
(m) => m.musiciansRoutes
),
},
];
The next step is to register application routes using the RouterModule
as follows:
// main.ts
bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(RouterModule.forRoot(appRoutes)),
],
});
When bootstrapping the application, Angular will initialize the root RouterModule
, register application routes, and provide Router
, ActivatedRoute
, and other providers from the RouterModule
at the root level.
Angular Modules from NgRx Packages
As we have seen in the case of the RouterModule
, Angular modules are not only used to declare components or provide services. They are also used to configure various application and library functionalities. In the case of NgRx, we use the EffectsModule.forRoot
method to provide the Actions
observable at the root level of an Angular application, initialize the effects runner, and run root effects. Therefore, importing root modules from other NgRx packages will configure their functionalities and/or provide services:
// app.module.ts
@NgModule({
imports: [
// provide `Store` at the root level
// register initial reducers
// initialize runtime checks mechanism
StoreModule.forRoot({ router: routerReducer, auth: authReducer }),
// connect NgRx Store with Angular Router
StoreRouterConnectingModule.forRoot(),
// connect NgRx Store with Redux Devtools extension
StoreDevtoolsModule.instrument(),
// provide `Actions` at the root level
// initialize effects runner
// run root effects
EffectsModule.forRoot([RouterEffects, AuthEffects]),
],
})
export class AppModule {}
Also, NgRx exposes APIs for registering additional reducers and effects in feature modules:
// musicians.module.ts
@NgModule({
imports: [
// register feature reducer
StoreModule.forFeature('musicians', musiciansReducer),
// run feature effects
EffectsModule.forFeature([MusiciansApiEffects]),
],
})
export class MusiciansModule {}
Using NgRx Modules with Standalone Angular APIs
Similar to the root RouterModule
, NgRx modules can be configured at the application level using the bootstrapApplication
function:
// main.ts
bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(
RouterModule.forRoot(appRoutes),
// configure NgRx modules
StoreModule.forRoot({
router: routerReducer,
auth: authReducer,
}),
StoreRouterConnectingModule.forRoot(),
StoreDevtoolsModule.instrument(),
EffectsModule.forRoot([RouterEffects, AuthEffects])
),
],
});
The feature reducer and effects can be lazily registered in the route configuration for a specific feature as follows:
// musicians.routes.ts
export const musiciansRoutes: Route[] = [
{
path: '',
providers: [
importProvidersFrom(
// register feature reducer
StoreModule.forFeature('musicians', musiciansReducer),
// run feature effects
EffectsModule.forFeature([MusiciansApiEffects])
),
],
children: [
{
path: '',
component: MusicianListComponent,
},
{
path: ':id',
component: MusicianDetailsComponent,
canActivate: [MusicianExistsGuard],
},
],
},
];
Standalone NgRx APIs
Instead of using NgModules to configure NgRx packages and/or provide their services, we could use functions for a "module-free" developer experience. For example, we could use a function named provideStore
instead of StoreModule.forRoot
. The same principle can be applied to other NgRx packages. Using standalone NgRx functions would look like this:
// main.ts
bootstrapApplication(AppComponent, {
providers: [
// alternative to `StoreModule.forRoot`
provideStore({ router: routerReducer, auth: AuthReducer }),
// alternative to `StoreRouterConnectingModule.forRoot`
provideRouterStore(),
// alternative to `StoreDevtoolsModule.instrument`
provideStoreDevtools(),
// alternative to `EffectsModule.forRoot`
provideEffects([RouterEffects, AuthEffects]),
),
});
Feature reducers and effects would also be registered using functions instead of NgModules:
// musicians.routes.ts
export const musiciansRoutes: Route[] = [
{
path: '',
providers: [
// alternative to `StoreModule.forFeature`
provideStoreFeature('musicians', musiciansReducer),
// alternative to `EffectsModule.forFeature`
provideFeatureEffects([MusiciansApiEffects]),
],
children: [
{
path: '',
component: MusicianListComponent,
},
{
path: ':id',
component: MusicianDetailsComponent,
canActivate: [MusicianExistsGuard],
},
],
},
];
💡 The design of standalone NgRx APIs is still under consideration. If you have any suggestions, leave a comment here.
Source Code
The source code of the proposed standalone NgRx APIs and sample project is available here.
Resources
- Angular v14 is now available! by Emma Twersky
- RFC: Standalone Angular APIs
- RFC: Standalone NgRx APIs
Peer Reviewers
Many thanks to Tim Deschryver and Brandon Roberts for reviewing this article!
Top comments (8)
Thank you ! Quiet exciting to see substantial things happening in Angular's eco system
It's the same thing but in a different colour. You just have a separate file for your module. It's like every component has a module but you don't have to create a file for the module, instead, you do what would you do with a module in the component.
It's absolutely not the same thing but in a different colour.
Now that V16 is out, standalone apps are becoming truly awesome and powerful!! Cheers
Impressive. Thank you. You saved me with the standalone approach using the
provideStore
part.Hi, where does the provideStoreFeature come from?
This article was written before we released standalone NgRx APIs. They are available from v14.3.0. Read more here.
How can i share a state or how can i access different lazy loaded route state in standalone ?