--- title: InjectionToken as a Service description: (hot take) I want to use InjectionToken for almost all my services publishedAt: 2023-03-27 tags: ["Angular"] slug: injection-token-service --- Up until recently, [Angular](https://angular.io) has been a hard-core class-based framework: Component, Directive, Pipe, Guard, Interceptor, Service, etc... Everything has been a [TypeScript](https://typescriptlang.org) class. Nowadays, Angular provides more **functional** APIs: Functional Guard, Functional Interceptor, and Functional Resolver. These functional APIs are great and if you haven't tried them out, I'd highly recommend that you do because they do **improve the authoring experience**. Now, let's take a quick look at Component, Directive, and Pipe ```ts= @Component({ /*...*/ }) export class UserComponent {} @Directive({ /*...*/ }) export class UserDirective {} @Pipe({ /*...*/ }) export class UserPipe {} ``` All three of these building blocks have their dedicated [Decorators](https://www.typescriptlang.org/docs/handbook/decorators.html), which means that we do not have much choice than to use classes for Component, Directive, and Pipe. What's left? ## Service Yes, we have **Service**, _the lord of general usage_. If you haven't noticed, Guard, Interceptor, and Resolver are all Services. What Angular developers are so fixtated on is that Service has to be a TypeScript class. Functional Guard/Resolver/Interceptor prove that to be not the case. So, for general-usage Service, what can we do today? Let's say we have a `GithubUserService`, that is `providedIn: 'root'`, with the following requirements: - It needs to inject `HttpClient` to make network calls - It needs to expose a method `searchUser(query?: string)` to search for Github users based on a query Any Angular developer should be able to write this Service in a heartbeat ```ts= @Injectable({ providedIn: "root" }) export class GithubUserService { private readonly http = inject(HttpClient); // or using true-private field // readonly #http = inject(HttpClient); searchUser(query = "") { return this.http.get<GithubUser[]>(`path/to/github-api?q=${query}`); } } ``` There are two things I do not particularly like about this: - Using `inject` becomes more and more popular. But if I use `inject` in my Service, then I can't test my Service as simple as `new GithubUserService()` anymore. Additionally, I don't want to use Constructor-DI for my Services and Inject-DI for my other building blocks. The inconsistency haunts me - `GithubUserService` is both a **type** and an **implementation**. It is nice that I only need one symbol for both type and implementation, but I don't like that I expose the implementation details. We all can argue about using Abstract Class or Interface to hide the implementation details but that approach introduces enough boilerplate that the trade-off isn't worth it for me. Next, I'd like to introduce an alternative to writing Services in Angular. ## Injection Token Many Angular developers, including Senior ones, are not familiar with [InjectionToken](https://angular.io/api/core/InjectionToken). Even if they know `InjectionToken`, a lot of developers do not utilize them _enough_. For this blog post, I'll be using `InjectionToken` to write a concise and easy to test **Service** > We'll skip the fact that class-based Service is familiar to Angular developers. Since we're exploring a new approach, it's easier for me to leave that out of the argument. Let's rewrite `GithubUserService` using `InjectionToken`. For this, we'll have two separate units: a Factory Function and an Injection Token ```ts= // github-user.factory.ts export function githubUserServiceFactory(http: HttpClient) { return { searchUser: (query = "") => http.get<GithubUser[]>(`path/to/github-api?q=${query}`), }; } // utilize TypeScript Utility Types export type GithubUserServiceApi = ReturnType<typeof githubUserServiceFactory>; ``` ```ts= // github-user.token.ts export const GITHUB_USER_SERVICE = new InjectionToken<GithubUserServiceApi>( "Github User Service", { factory: () => githubUserServiceFactory(inject(HttpClient)) } ); ``` What's nice about this is we separate the Dependency interface (token) from the implementation (factory). Imagine our file structure is as follow: ``` . └── github-user/ ├── github-user.factory.ts ├── github-user.token.ts └── index.ts ``` We can **only** expose the Injection Token for consumers and keep the implementation (Factory Function) as private API to `github-user` library. > This argument makes more sense for folks who work in a monorepo setup where each specific piece of features is a **library** that expose its Public API for the rest of the monorepo. #### Testing As far as testing goes, we only care about testing our implementation details, which is `githubUserServiceFactory`. ```ts= describe(githubUserServiceFactory.name, () => { let mockedHttpClient: jasmine.SpyObj<HttpClient>; let service: GithubUserServiceApi; beforeEach(() => { mockedHttpClient = jasmine.createSpyObj(/*...*/); service = githubUserServiceFactory(mockedHttpClient); }); it("should search user", () => { /* test searchUser */ }); }); ``` Pretty cool right? We can still use `inject()` and keep tests as simple as possible. ### Atomic Token Previously, we implemented `GithubUserService` as an `InjectionToken` with a factory function. The return value of our factory is to mimic that of a class-based service. What if we **only** ever need `searchUser()` method? Luckily, using `InjectionToken` provides us this flexibility to return what we **actually** need. ```ts export function githubUserSearchFactory(http: HttpClient) { return (query = '') => http.get<GithubUser[]>(`path/to/github-api?q=${query}`); export type GithubUserServiceApi = ReturnType<typeof githubUserSearchFactory>; ``` > Our `github-user.token.ts` stays mostly the same, with some name changes because we change our factory function name When we use this in a Component, our code looks like the following: ```ts @Component(/*...*/) export class UserComponent { private readonly searchGithubUser = inject(GITHUB_USER_SEARCH); readonly users$ = this.query$.pipe( switchMap((query) => this.searchGithubUser(query)) ); } ``` This approach also allows us to separate tests, separate implementation details with different arguments. All functions do not **necessarily** share all the Dependencies ### Life-cycle hooks As of this moment, the only life-cycle hook that we care about for Services is `ngOnDestroy`. With the latest version of Angular 16.0.0-next.5, we will have a new token, [DestroyRef](https://netbasal.com/getting-to-know-the-destroyref-provider-in-angular-9791aa096d77?gi=eb7e68b4c77a), as a way to implement a clean-up mechanism for our Service. Let's rewrite our factory to also expose more than just a `searchUser` function ```ts= export function githubUserServiceFactory( http: HttpClient, destroyRef: DestroyRef ) { const query$ = new BehaviorSubject(""); destroyRef.onDestroy(() => { query$.complete(); }); return { setQuery: (query: string) => void query.next(query), users$: query$.pipe( switchMap((query) => http.get(`path/to/github-api?q=${query}`)) ), }; } export type GithubUserServiceApi = ReturnType<typeof githubUserServiceFactory>; ``` With this, let's assume that our Service is no longer a Root Service so we need to rewrite the Token ```ts export const GITHUB_USER_SERVICE = new InjectionToken<GithubUserServiceApi>( "Github User Service" ); export function provideGithubUserService() { return { provide: GITHUB_USER_SERVICE, useFactory: githubUserServiceFactory, // let Angular DI knows what our Factory needs deps: [HttpClient, DestroyRef], }; } ``` We're ready to use this in **ANY** Component that needs `GithubUserServiceApi` ```ts= @Component({ template: ` <input (input)="githubUserService.setQuery($event.target.value)" /> <ul> <li *ngFor="let user of githubUserService.users$ | async"></li> </ul> `, providers: [provideGithubUserService()], }) export class UserComponent { readonly githubUserService = inject(GITHUB_USER_SERVICE); } ``` ### Dealing with ComponentStore/RxState-like API One limitation with this approach is how we can leverage APIs like [ComponentStore](https://ngrx.io/guide/component-store) or [RxState](https://www.rx-angular.io/docs/state). Usually, the way to consume these APIs is to `extends ComponentStore<>` or `extends RxState<>`. However, we cannot `extends` because we have no class. The only thing that we care about when `extends ComponentStore<>` is for the Component Store to automatically run its destroy logic. Once again, `DestroyRef` to the rescue ```ts= export function userStoreFactory(destroyRef: DestroyRef) { const store = new ComponentStore(initialUserState); destroyRef.onDestroy(() => { // let's call ngOnDestroy manually here store.ngOnDestroy(); }); // now we can work with ComponentStore API and return exactly what we need } export type UserStoreApi = ReturnType<typeof userStoreFactory>; ``` ```ts= export const USER_STORE = new InjectionToken<UserStoreApi>('UserStore'); export function provideUserStore() { return { provide: USER_STORE, useFactory: userStoreFactory, deps: [DestroyRef] } } ``` > The same can be applied to `RxState` #### Using `provider` Alternatively, we can provide then inject `ComponentStore` instead of `new ComponentStore()` since some library author might expose their API as an Abstract Class, or you simply do not like calling `new` or `ngOnDestroy()` manually. ```ts= export function useStoreFactory(store: ComponentStore<UserState>) { // work with ComponentStore API and return exactly what we need } ``` ```ts= export const USER_STORE = new InjectionToken<UserStoreApi>("UserStore"); export function provideUserStore() { return [ ComponentStore, { provide: INITIAL_STATE_TOKEN, useValue: initialUserState }, { provide: USER_STORE, useFactory: userStoreFactory, deps: [ComponentStore], }, ]; } ``` > `INITIAL_STATE_TOKEN` is imported from `@ngrx/component-store` #### Possibilities This approach opens up new possibility: configurable component store. Let's say we want to reuse our `UserStoreApi` but we want the consumers to have the ability to configure its initial state. We definitely can expose `setState` on `UserStoreApi` and call `setState` where we use `UserStoreApi` to set the initial state. The limitation to this is we cannot call `setState` if we are to provide `UserStoreApi` on the Route-provider level. On the other hand, we can turn our `userStoreFactory` into a higher-order function to accept some initial state. ```ts= export function userStoreFactory(initialState: UserState) { return (destroyRef: DestroyRef) => { const store = new ComponentStore(initialState); /* ... */ } } export type UserStoreApi = ReturnType<ReturnType<typeof userStoreFactory>>; ``` ```ts= export const USER_STORE = new InjectionToken<UserStoreApi>('User Store'); export function provideUserStore(initialState: UserState) { return { provide: USER_STORE, useFactory: userStoreFactory(initialState), deps: [DestroyRef] } } ``` Now, we can provide different initial `UserState` when we call `provideUserStore()`; on Component-provider level, or on Route-provider level. > The same can be applied when use `provider` ### Conclusion We briefly went over the current APIs on Angular's building blocks and learned that some of the Angular APIs have gone away from Class-based approach. We also explored a new approach to writing Services using `InjectionToken`. Hopefully, I'm able to express my thoughts on this new approach and you learn something from this post. Thank you for reading. ### FAQs **1. What is the practicality of this approach?** Good question and I'll be honest. Nothing presented here have made it to any enterprise applications that I'm a part of. That said, I do use the approach in this blog post in my side projects. **2. I like it, but is it too verbose to write?** Yes, it is a bit verbose. We can always abstract the creation of the Injection Token and the Factory Function to a utility function. Something like the following: ```ts= export const [factoryFn, TOKEN] = createInjectionToken( "token descripton", () => {} ); ``` **3. What can we use if we don't have `DestroyRef`?** It is a unfortunate because `DestroyRef` really does help. In older versions, you can _maybe_ try the following hack: ```ts= const viewRef = inject(ChangeDetectorRef) as ViewRef; queueMicrotask(() => { viewRef.onDestroy(() => {}); }); ``` **4. Is `InjectionToken` not a Singleton ?** Yes, by default, `new InjectionToken('description')` is just a token that you need to **provide** something for it before you can **inject** it. However, `new InjectionToken('description', {factory: () => {}})` is `providedIn: 'root'` by default. Hence, the approach introduced here does give you a Singleton. However, this is all **configurable** per situation.