In this article, I want to talk about a feature which Angular provides, that isn’t particularly well known or used by many developers. When using Angular InjectionToken, we can specify a factory function which returns a default value of the parameterized type T. For example:

const WINDOW = new InjectionToken<Window>('A reference to the window object', {
  factory: () => window,
});

This sets up the InjectionToken using this factory as a provider, as if it was defined explicitly in the application’s root injector. Now we can use it anywhere in our application:

@Component({
  selector: 'my-app'
})
export class AppComponent {
  constructor(@Inject(WINDOW) window: Window) {}
}

But that’s not all. We can use the inject function to obtain a reference to other providers inside our factory function. Let’s see another real-world example:

import { inject, InjectionToken } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

export type TimespanProvider = Observable<string>;

export const TIMESPAN = new InjectionToken('Subscribe to timespan query param', {
  factory() {
    const activatedRoute = inject(ActivatedRoute);

    return activatedRoute.queryParams.pipe(
      pluck('timespan'),
      filterNil(),
      distinctUntilChanged()
    );
  },
});

In the above example, we inject the ActivatedRoute provider and return an observable for the timespan query param. Now we can use it in our components:

@Component({
  selector: 'app-home'
})
export class HomeComponent implements OnInit {
  constructor(@Inject(TIMESPAN) private timespan$: TimespanProvider) {}

  ngOnInit() {
    this.timespan$.pipe(untilDestroyed(this)).subscribe(console.log);
  }
}

We can also pass InjectionFlags to the inject function. For example, we can say that the requested provider is optional:

import { inject, InjectionToken, InjectFlags } from '@angular/core';

const MY_PROVIDER = new InjectionToken('', {
  factory: () => {
    const optional = inject(SomeProvider, InjectFlags.Optional);
    
    return optional ?? fallback;
  },
});

Here’s another real-world example — let’s say you have a theme service, which exposes the current user’s theme:

@Injectable({ providedIn: 'root' })
export class ThemeService {
  private theme = new Subject<string>();
  theme$ = this.theme.asObservable();

  setTheme(theme: string) {
    this.theme.next(theme);
  }
}

The only data that most components need is the current theme. So instead of doing the following in each component:

@Component({
  selector: 'app-hello',
  template: `<h1>{{ theme$ | async }}</h1>`
})
export class HelloComponent {
  theme$: Observable<string>;

  constructor(private themeService: ThemeService) {}

  ngOnInit() {
    this.theme$ = this.themeService.theme$;
  } 

}

We can create a provider with the sole purpose of providing the current user’s theme:

export type ActiveThemeProvider = Observable<string>;
export const ACTIVE_THEME = new InjectionToken<ActiveThemeProvider>('Active theme', {
  factory() {
    return inject(ThemeService).theme$;
  }
});
@Component({
  template: `<h1>{{ theme$ | async }}</h1>`
})
export class HelloComponent {
  constructor(@Inject(ACTIVE_THEME) public theme$: ActiveThemeProvider) {}
}

In summary, the benefits of using the InjectionToken factory function are:

  • The provider is tree-shakeable, since we don’t need to inject it in our app module as we’d do with the useFactory provider.
  • Using inject() to request a provider is faster and more type-safe than providing an additional array of dependencies (which is the common usage of useFactory providers).
  • The provider has a single responsibility, and our components are injected only with the data they need.
  • It makes testing more straightforward because we don’t need to mock everything. We can return a mock value from the factory, and that’s all.

 

Leave a comment

Your email address will not be published.