Codementor Events

Using dependency injection functions instead of constructors

Published Apr 11, 2023

Introduction

Starting in Angular 14, we can use the new inject function in components, directives and pipes. There are several benefits in using this approach as it significantly improves the readability and reusability of our code.

For example, instead of:

export class MyComponent { constructor(private apiService: APIService) {}
}

we can just write:

export class MyComponent {
  private apiService = inject(APIService);
}

This approach is really useful for components with a large number of dependencies as it's far more readable to pass in dependencies in the definition of our class, rather than in the constructor.

So, we can change code like this:

export class MyComponent { constructor(
    private apiService: APIService,
    private router: Router,
    private activatedRoute: ActivatedRoute
  ) {}
}

to this:

export class MyComponent {
  private apiService = inject(APIService);
  private router = inject(Router);
  private activatedRoute = inject(ActivatedRoute);
}

Reusable functions

One of the main benefits of using the inject function is the ability to create reusable and modular functions.

An example of this is the code required to unsubscribe from observables when the component is destroyed.

Instead of writing code like this:

export class MyComponent { private destroy$ = new Subject(); ngOnInit() { this.apiService.getData().pipe( takeUntil(this.destroy$) ).subscribe(data => console.log(data)); } ngOnDestroy(): void { this.destroy$.next(true); this.destroy$.complete();
  }
}

we can simply write:

export class MyComponent { private destroy$ = untilDestroyed(); ngOnInit() { this.apiService.getData().pipe( takeUntil(this.destroy$) ).subscribe(data => console.log(data));
  }
}

Instead of repeating the logic to unsubscribe in every component, we can simply create a reusable function to handle the unsubscribe logic:

export function untilDestroyed() { const subject = new Subject<void>(); const viewRef = inject(ChangeDetectorRef) as ViewRef; viewRef.onDestroy(() => { subject.next(); subject.complete() }); return takeUntil(subject.asObservable());
}

Some other use cases include writing logic to retrieve a specific query parameter.

So, rather than writing:

export class MyComponent { id$ = this.activatedRoute.queryParams.pipe( map(params => params.id) ); constructor(private activatedRoute: ActivatedRoute) {}
}

we can simply rewrite our component as:

export class MyComponent { id$ = getParam$('id');
}

The logic to determine the current query parameter can then be rewritten as a common function:

export function getParam$(key: string) { const activatedRoute = inject(ActivatedRoute); return activatedRoute.queryParams.pipe( map(params => params[key])
  );
}

This can prevent having to duplicate logic in multiple components.

Discover and read more posts from Alex Routledge
get started
post comments1Reply
David Warner
2 years ago

Using dependency injection functions instead of constructors is a common practice in software development that can help improve the maintainability, testability, and flexibility of your code.

Instead of creating an object and passing its dependencies through the constructor, you can use a function that takes in the dependencies as parameters and returns the object. This function is known as a factory function or a dependency injection function.

Here’s an example:

class MyClass:
def init(self, dependency1, dependency2):
self.dependency1 = dependency1
self.dependency2 = dependency2

def create_my_class(dependency1, dependency2):
return MyClass(dependency1, dependency2)
In this example, we have a MyClass that depends on dependency1 and dependency2. Instead of passing the dependencies through the constructor, we define a create_my_class function that takes in the dependencies as parameters and returns the MyClass object.

Using dependency injection functions instead of constructors has several advantages:

It makes it easier to change the dependencies of an object without having to modify its constructor.
It allows you to use different implementations of the same dependency in different contexts.
It simplifies testing by allowing you to easily inject mock dependencies.
Overall, using dependency injection functions can help make your code more modular, flexible, and easier to maintain over time.