How to reduce duplicated code by using dependency injection in Angular

Using dependency injection to replace redundant logic code

Pham Huu Hien
2 min readDec 11, 2020
Photo by Max Duzij on Unsplash

In my current project, there’s a block of code that is used in many places. The code looks like this

@Component({
...
})
export class SomeComponent implements OnInit {
constructor(
private route: ActivatedRoute
) {}
ngOnInit(): void {
this.route.params.pipe(
takeUntil(this.destroy$),
map(params => params.id),
filter(v => !!v),
// do something with this id
);
}
}

The above code gets a stream of id from the activated route. In other places, it would be getting the passengerId from activated route, or getting whatever id and do something with it.

So the pattern is that using the ActivatedRoute service to get something from the route parameters.

In fact, we can reduce this duplicated logic by using dependency injection. Here’s 3 steps to do that.

Declare injection token and factory function

First, we create a file named route-param.token.ts in a folder, for example core/tokens

export const ROUTE_PARAM_TOKEN = new InjectionToken<Observable<string>>(
'Stream of route param from activated route'
);
// if you want to get :id from route, declare this token
export const PARAM_KEY_ID = new InjectionToken<string>(
'static string for :id param key',
// this is the second argument of InjectionToken constructor
// to produce the value, like when you use useFactory
{
factory: () => {
return 'id';
}
}
);
// if you want to get :someId from the route, the token should look like this
export const PARAM_KEY_SOME_ID = new InjectionToken<string>(
'static string for :someId param key', {
factory: () => {
return 'someId';
}
}
);
export function routeParamFactory(
route: ActivatedRoute,
paramKey: string
): Observable<string> {
// should use paramMap because route.params will be deprecated soon
return route.paramMap.pipe(map((param) => param.get(paramKey)))
}

Declare the token in the provider list of your component

Next is to declare the token you want to use in the providers list in your component.

@Component({
providers: [
{
provide: ROUTE_PARAM_TOKEN,
useFactory: routeParamFactory,
deps: [ActivatedRoute, PARAM_KEY_ID]
// if you want to get someId, the deps will be
// deps: [ActivatedRoute, PARAM_KEY_SOME_ID]
}
]
})
export class SomeComponent {
constructor() {}
}

Inject the token in constructor and use it in your component

@Component({
providers: [
{
provide: ROUTE_PARAM_TOKEN,
useFactory: routeParamFactory,
deps: [ActivatedRoute, PARAM_KEY_ID]
}
]
})
export class SomeComponent implements Oninit {
constructor(
@Inject(ROUTE_PARAM_TOKEN)
private readonly id$: Observable<string> // should use readonly as much as possible
) {}
ngOnInit(): void {
this.id$.pipe(
// do whatever with this id
);
}
}

There are many benefits when you use this approach

  • It makes your code cleaner and easy to understand and maintain
  • Easier to test you component by mocking the dependency injection token
  • It helps reduce duplicated logic in your code

Thanks for reading.

--

--