11

I created a core library for my project containing some components and services. I built the library with ng-packagr. In the consuming project which references the library I built my webapp containing components provided by the library. Nothing special so far. But sometimes I want a component (coming from my lib) calling a method from a Service outside of the lib. Is this possible? Can I somehow inject a service to a component which is defined inside a library?

Cheers

  • 1
    Are you asking if a consumer of your library can provide a different service for your library to use? – Daniel W Strimpel Apr 21 '18 at 19:38
  • Yes, as we know Angular is all built out of modules. To have your component speak to another service/component declared in another module (library) that module needs to be imported in your module (that contains the component you wish to speak to the external service/module). That is exactly how you are using the standard Angular services like httpClient and so on. –  Apr 21 '18 at 19:39
  • @Daniel W Strimpel. Basically yes. I want to outsource core components and services in a lib. I reference this lib and therefore the components in another project. Sometimes I want the referenced component to use a customized service instead of the original referenced service (also part of the lib). –  Apr 21 '18 at 19:58
  • 1
    Research the use of `forRoot` methods on your module to provide config information. – Daniel W Strimpel Apr 21 '18 at 19:59

1 Answers1

24

I've achieved this before with something like this:

Your library's service(s) should be defined as an interface rather than as a concrete implementation (as is done in OO languages quite often). If your implementing application will only sometimes want to pass in its own version of the service then you should create a Default service in your library, and use it as so:

import { Component, NgModule, ModuleWithProviders, Type, InjectionToken, Inject, Injectable } from '@angular/core';

export interface ILibService {
  aFunction(): string;
}

export const LIB_SERVICE = new InjectionToken<ILibService>('LIB_SERVICE');

export interface MyLibConfig {
  myService: Type<ILibService>;
}

@Injectable()
export class DefaultLibService implements ILibService {
  aFunction() {
    return 'default';
  }
}

@Component({
  // whatever
})
export class MyLibComponent {
  constructor(@Inject(LIB_SERVICE) libService: ILibService) {
    console.log(libService.aFunction());
  }
}

@NgModule({
  declarations: [MyLibComponent],
  exports: [MyLibComponent]
})
export class LibModule {
  static forRoot(config?: MyLibConfig): ModuleWithProviders {
    return {
      ngModule: LibModule,
      providers: [
        { provide: LIB_SERVICE, useClass: config && config.myService || DefaultLibService }
      ]
    };
  }
}

Then in your implementing application you have the ability to pass in the optional config via your library's forRoot method (note that forRoot should only be called once per application and at the highest level possible). Note that I've marked the config parameter as optional, so you should call forRoot even if you have no config to pass.

import { NgModule, Injectable } from '@angular/core';
import { LibModule, ILibService } from 'my-lib';

@Injectable()
export class OverridingService implements ILibService {
  aFunction() {
    return 'overridden!';
  }
}

@NgModule({
  imports: [LibModule.forRoot({ myService: OverridingService })]
})
export class ImplementingModule {

}

This was from memory as I don't have the code to hand at the moment so if it doesn't work for any reason let me know.

UncleDave
  • 6,872
  • 1
  • 26
  • 44
  • Hey I tried it and it works like a charm. I only had one issue: the default service had no provider :) I just added it to the provides list of the library module. The rest worked and therefore I set this answer as solution to my question. Thanks again. –  Apr 22 '18 at 16:01
  • 1
    You shouldn't add it to the providers list, `forRoot` takes care of that. I missed the `@Injectable()` decorator on the services - that should be the issue. – UncleDave Apr 22 '18 at 16:06
  • Unfortunately I receive the same issue, even with @Injectable() on both services :( –  Apr 22 '18 at 16:23
  • 1
    Could you show me where you import the module and the exact error message? – UncleDave Apr 22 '18 at 16:25
  • Sure. This is the library repo: https://github.com/baerree/ng-package-pos-shell and this is the consuming project: https://github.com/baerree/ng-package-pos-shell-consumer the error message is:NullInjectorError: No provider for PosShellService! –  Apr 22 '18 at 16:27
  • 1
    Check out `MyLibComponent` in my answer - see that it expects `ILibService` to be injected, NOT the concrete class `DefaultLibService` That's your problem in `CustomFlowAComponent` – UncleDave Apr 22 '18 at 16:30
  • I see, it was referenced in the app.component.ts :) I removed the old direct references to PosShellService, and now it is working as you described it. Thanks again for taking time to help me out! –  Apr 22 '18 at 16:54
  • this is crazy and works excelente, thank you. – Michi-2142 Jan 15 '21 at 14:35