2

We are using Angular 7 currently. I've read through Angular's guide on dependency injection and creating tree-shakable providers. I've read a dozen Q&A's and blog posts about defining InjectionTokens or using abstract classes as tokens for providing a concrete class for an interface or abstract class. But they all seem to assume that all the code is in a single Angular project.

In our architecture, I have an Angular library project which contains my interface and InjectionToken:

export const MY_SERVICE = new InjectionToken<MyService>('my-service');

export interface MyService {
    someMethod();
}

In that library project, I have a directive that gets this token injected:

export class MyDirective {
    constructor(@Inject(MY_SERVICE) myService: MyService) { }

    @HostListener('click')
    private clicked() {
        this.myService.someMethod();
    }
}

I want to provide the actual implementation for this service at the application-level, which is another Angular project that imports the library project.

The Angular application project would have a concrete implementation of MyService:

@Injectable({
    providedIn: 'root'
})
export class ConcreteMyService implements MyService {
    someMethod() {
        console.log('Application specific concrete implementation!');
    }
}

I use providedIn: 'root' because everything I've read says this is required to make this tree-shakable. The problem is that I don't see how to wire this up to Angular's DI system in order for ConcreteMyService to be injected into MyDirective while remaining tree-shakable.

Keep in mind that MyDirective and MyService are in a different project than ConcreteMyService, and have no knowledge of it. This is by design and should not change.

I mention that because I've seen others suggest to do the following when setting up the InjectionToken:

export const MY_SERVICE = new InjectionToken<MyService>('my-service', {
    factory: () => new ConcreteMyService()
});

The problem here is that ConcreteMyService isn't available in the library project, nor should it be...this isn't an option.

The only solution that I've seen that seems to accomplish what I'm asking would be to add a provider to a module like:

@NgModule({
    providers: [
        { provide: MY_SERVICE, useClass: ConcreteMyService }
    ]
})
export class SomeModule { }

But this isn't tree-shakable from what I understand based on everything I've read.


How can I make ConcreteMyService tree-shakable while also providing it to the Angular DI system to be used in cases where my MY_SERVICE token is requested? Please keep in mind the separation of library from application projects.

crush
  • 16,713
  • 9
  • 59
  • 100

0 Answers0