Best Practice: injection functions for repeating initializations

You can use injection functions for repeating initializations. Let's see how it works with NGRX

Lets give a simple use case of injection functions, and from that example try to understand a general best practice tip for using injection functions.

DI

Usually we use the DI by injecting using the constructor.

import { Component } from '@angular/core';
import { Store } from '@ngrx/store';

@Component({...})
export class AppComponent {
	counter$ = this._store.select((state) => state.counter);
	
	constructor(private _store: Store) {}
}
Read-only

In this example we inject the Store using the constructor, and then use the Store service to select data from the State.

Problem

What if we want to read that counter in many components, what if we want to do some repeating manipulation on that data?
Do we have to duplicate this logic along all the components?

Sure we can use NGRX Selectors and RXJS pipe to make a custom RXJS operator to extract that data, but we would still have to DI the Store service and call the store.pipe.

In general with NGRX we have a lot of those initializers that subscribe to some section of the state.

Injection Functions

When you have repeating initializations it's a good sign for using injection functions.
Another way you can ask the DI to give you the Store service is by using the inject function:

import { Component, inject } from '@angular/core';
import { Store } from '@ngrx/store';

@Component({...})
export class AppComponent {
	private _store: Store;
	counter$ = this._store.select((state) => state.counter);
	
	constructor() {
		this._store = inject(Store);
	}
}
Read-only

In fact you can call the inject function from an injection context which includes those class props like counter$.

With injection function the code above can turn to:

import { Component, ChangeDetectionStrategy } from "@angular/core";
import { useCounter } from './counter.selector';


@Component({
	changeDetection: ChangeDetectionStrategy.OnPush,
	selector: "app-root",
	template: `<h1>{{ counter$ | async }}</h1>`
})
export class AppComponent {
	counter$ = useCounter();	
}
Read-only

The code is not only shorter, simpler, and cleaner, but also it makes your component easier to test, and easier to extend.
You can use more logic to the useCounter if you need to manipulate the data in some way, and you can also use the same technique if for example you need initialization of server query for a component.

Best Practice

So to summarize, if you have a repeating prop initializers in your class like:

  • server calls
  • service usage
  • grab of data from ngrx or elsewhere

instead of duplicating the logic you can use a reusable function that is using the inject function to inject the services.