Using NgRx Packages with Standalone Angular Features
Tags: ngrx, angular
Published: 2022-06-14 13:06:04

> ⚠️ This article was written before we released standalone NgRx APIs. They are available from v14.3.0. Read more [here](https://dev.to/ngrx/announcing-ngrx-v15-standalone-apis-type-safe-projectors-component-and-componentstore-updates-and-more-l7).

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](#standalone-angular-apis)
  - [Creating Standalone Components](#creating-standalone-components)
  - [Interop with Angular Modules](#interop-with-angular-modules)
  - [Configuring Angular Router](#configuring-angular-router)
- [Angular Modules from NgRx Packages](#angular-modules-from-ngrx-packages)
  - [Using NgRx Modules with Standalone Angular APIs](#using-ngrx-modules-with-standalone-angular-apis)
- [Standalone NgRx APIs](#standalone-ngrx-apis)
- [Source Code](#source-code)
- [Resources](#resources)
- [Peer Reviewers](#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:

```ts
// 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:

```ts
// 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:

```ts
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:

```ts
const providers = importProvidersFrom(
  HttpClientModule,
  // ... other modules
);
```

Providers returned by the `importProvidersFrom` function can be registered at the root level in the following way:

```ts
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:

```ts
// 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:

```ts
// 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:

```ts
// 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:

```ts
// 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:

```ts
// 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:

```ts
// 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:

```ts
// 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:

```ts
// 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:

```ts
// 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](https://github.com/ngrx/platform/discussions/3399).

## Source Code

The source code of the proposed standalone NgRx APIs and sample project is available [here](https://github.com/markostanimirovic/standalone-ngrx).

## Resources

- [Angular v14 is now available!](https://blog.angular.io/angular-v14-is-now-available-391a6db736af) by [Emma Twersky](https://twitter.com/twerske)
- [RFC: Standalone Angular APIs](https://github.com/angular/angular/discussions/45554)
- [RFC: Standalone NgRx APIs](https://github.com/ngrx/platform/discussions/3399)

## Peer Reviewers

Many thanks to [Tim Deschryver](https://twitter.com/tim_deschryver) and [Brandon Roberts](https://twitter.com/brandontroberts) for reviewing this article!