Angular 14 dependency injection unlocked
Michael Muscat
Posted on June 13, 2022
Quietly released with Angular 14 is a fix for this handy little inject
utility that allows us to create and retrieve dependencies from the injector tree without relying on parameterized class constructors (read docs)
A small change with big ramifications?
Until now it's only been possible to use it in places such as an InjectionToken
or Injectable
constructor context. Angular 14 now allows inject
to be used in Directive
and Component
constructor contexts as well.
This makes it a little nicer when injecting non class-based tokens.
const API = new InjectionToken<Api>("API")
// Angular <=13
@Directive()
export class MyDirective {
constructor(@Inject(API) private api: Api) {}
}
// Angular <=13
@Component()
export class MyDirective {
constructor(@Inject(API) private api: Api) {}
}
// Angular 14+
@Directive()
export class MyDirective {
private api = inject(API) // type inferred
}
// Angular 14+
@Component()
export class MyDirective {
private api = inject(API) // type inferred
}
Higher Order Services
Another change in Angular 14 loosens the rules around abstract class constructors so we can safely use them without running afoul of the Angular compiler with strictInjectionParameters
.
Thanks to this change it's now much easier to compose higher order services using mixins. For example, we can replace the ResourceManager
in this example with service composition.
// generate higher order service, mixing plain value
// parameters with dependency injection
export function createResource<T extends Fetchable>(
fetchable: Type<T>
): Type<Resource<T>> {
@Injectable()
class ResourceImpl extends Resource<T> {
constructor() {
super(inject(fetchable));
}
}
return ResourceImpl;
}
@Injectable()
export abstract class Resource<T extends Fetchable> {
// this value is injected
private changeDetectorRef = inject(ChangeDetectorRef);
private subscription = Subscription.EMPTY
...
// Angular behaviors require the `Injectable` decorator
ngOnDestroy() {
this.subscription.unsubscribe()
}
// this value is passed in through `super()`
constructor(private fetchable: Fetchable) {}
}
This example shows how we can now easily mix dependency injection with explicit parameter constructors while preserving Angular behaviors like ngOnDestroy
.
Let's see it in action.
const endpoint = 'https://jsonplaceholder.typicode.com/todos'
@Injectable({ providedIn: 'root' })
export class FetchTodosByUserId implements Fetchable<Todo[]> {
private http = inject(HttpClient);
fetch(userId: string) {
return this.http.get<Todo[]>(endpoint, {
params: {
userId,
},
});
}
}
<!-- todos.component.html -->
<div *ngFor="let todo of todos.value">
<div>id: {{ todo.id }}</div>
<div>title: {{ todo.title }}</div>
<input disabled type="checkbox" [checked]="todo.completed" />
</div>
const TodosByUserId = createResource(FetchTodosByUserId);
@Component({
selector: 'app-todos',
templateUrl: './todos.component.html',
providers: [TodosByUserId],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TodosComponent {
protected todos = inject(TodosByUserId);
@Input()
userId: string;
ngOnChanges() {
this.todos.fetch(this.userId);
}
}
A working example can be seen here 👉 view on Stackblitz
A small step forward
Angular 14 has many long needed and welcomed improvements to the core developer experience. Be sure to check the change log so you don't miss anything.
Happy Coding!
Posted on June 13, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.