Using a factory method to control Angular dependency injection

Similar to the use of the useClass attribute in the previous post, we can also use the useFactory attribute to supply a factory method to create our dependent value.

To start off, here’s a simple illustration of using the useFactory attribute.

We need to create a factory method which will use some logic to determine which service should be created. In this case checking if the Date.now() is even. This is of course a rather contrived example.

import { FirstService } from './first.service';
import { SecondService } from './second.service';

export let serviceFactory = () => {
  if (Date.now() % 2 === 0) {
    return new FirstService();
  } else {
    return new SecondService();
  }
};

In the first service we add the useFactory attribute to the service’s decorator:

import { Injectable } from '@angular/core';
import { serviceFactory } from './service.provider';

@Injectable({
  providedIn: 'root',
  useFactory: serviceFactory
})
export class FirstService {

  constructor() { }

  output() {
    return 'first';
  }
}

Here is the code for SecondService:

@Injectable()
export class SecondService implements FirstService {
 
  constructor() { }
 
  output() {
    return 'second';
  }
}

Now any component that injects a FirstService through it’s constructor will use the serviceFactory method. This factory could read the service information from a configuration file in the project, allowing us to inject different services based on which configuration file the project was built with.

Control which class will get instantiated for your Angular dependencies

Using Angular dependency injection, it’s possible to control which class will be injected for a specific provider. Let’s start with a simple scenario.

Given a simple component:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'dependency-injection';

  constructor(public injectedService: FirstService) {

  }
}

And two services:

@Injectable({
  providedIn: 'root'
})
export class FirstService {

  constructor() { }

  output() {
    return 'first';
  }
}

Where the second one implements the first:

@Injectable({
  providedIn: 'root'
})
export class SecondService implements FirstService {

  constructor() { }

  output() {
    return 'second';
  }
}

We can control which service will be injected using the useClass attribute in the base class declaration:

@Injectable({
  providedIn: 'root',
  useClass: SecondService
})
export class FirstService {

  constructor() { }

  output() {
    return 'first';
  }
}

We need to remove the providedIn attribute in the second service:

@Injectable()
export class SecondService implements FirstService {

  constructor() { }

  output() {
    return 'second';
  }
}

Here it’s important to use the implements keyword rather than the extends keyword as it treats the base class as an interface and does not link to it directly through super which would create a circular reference.

Now our AppComponent will receive a SecondService instance in it’s constructor instead of FirstService.

While this can be used to easily switch which classes are deployed by modifying the attribute in one place, it can also be used to construct more advanced injection scenarios.

For example we could use the useClass attribute in the component’s provider allowing certain components to specify which specific service they require while letting other components receive the default service.

Here is an example of this. First we remove the useClass in FirstService

@Injectable({
  providedIn: 'root'
})
export class FirstService

And then we add a providers attribute in the component decorator:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [
    { provide: FirstService, useClass: SecondService }
  ]
})
export class AppComponent {
  title = 'dependency-injection';

  constructor(public injectedService: FirstService) {

  }
}

This component would receive a SecondService while another component lacking this providers declaration would receive a FirstService.

Other more advanced scenarios are also possible and I will cover one such scenario in a subsequent post.

Using the new Angular dependency injection API

The way to configure dependency injection with the old API was to configure the providers on the module definition.

First we defined a service:

import { Injectable } from '@angular/core';

@Injectable()
export class ExampleService {
}

Then added it to the providers collections on AppModule:

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [ExampleService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Using the new API we can forego changing the AppModule, there is no need to change the providers collection:

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Instead in our service we use the providedIn attribute in the Injectable decorator:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ExampleService {
}

This has two benefits, first we don’t need to modify the module file when we add a new service and more importantly, when using the new API if a service isn’t referenced it will be removed from the bundle using tree shaking. This can potentially result in smaller bundle sizes.

Services included with the old bundle can’t be tree shaken because the AppModule has a dependency on them.

New services created by running ng generate service will use the new API by default.

Updating a rebased commit’s timestamp

When I work on a feature branch with Git, I like to do smaller commits in the branch. This allows me to rollback certain changes and to easily switch to another computer.

One thing that I don’t like about this approach is that when I do my final

git rebase -i

to squash all my commits into one, the timestamp of the final commit is the same as the first commit in the chain.

When I’ve been working for 3 days on something, I’d rather the timestamp be the same as if I had done one big commit in my branch, at the end, rather than one commit at the start and then nothing.

To fix this, I’ve found that I simply need to do:

git commit --amend --reset-author

and leave the information as is. This will update the last rebased commit to the current date and time.