davidmr
davidmr

Reputation: 115

extending classes with dependency injection in angular 4

I'm using angular cli and angular 4. I've made some classes from which my components inherit behavivour! However during development their constructores grow and in the current phase I need to be able to allow other to use them whithout the need of knowing their constructors!

So after seeing Petr sugestion in dependency injection I tried, but had to make some adjustments due to errors the compiler was giving, I suppose due to diferences betwween angular 4 and angular 5!

This is my code:

Service Locator:

import {ReflectiveInjector} from "@angular/core";

export class ServiceLocator {
  static injector: ReflectiveInjector;
}

module which imports the serviceLocator:

ServiceLocator.injector = ReflectiveInjector.resolveAndCreate(
      Object.keys(services).map(key => ({
        provide: services[key].provide,
        useClass: services[key].provide,
        deps: services[key].deps
      }))
    );

serviceList:

import {SysFwDatesService} from '../sysDates/sysFwDates.service';
import {MatSnackBar} from '@angular/material';
import {SysFwFormSpecBuilder} from '../uiValidation/SysFwFormSpecBuilder';
import {SysFwHttpApi} from '../http/SysFwHttpApi';


export const services: {[key: string]: {provide: any, deps: any[], useClass?: any}} = {
 'SysFwDatesService': {
    provide: SysFwDatesService,
    deps: []
  },
  'MatSnackBar': {
    provide: MatSnackBar,
    deps: []
  },
  'SysFwFormSpecBuilder': {
    provide: SysFwFormSpecBuilder,
    deps: []
  },
  'SysFwHttpApi': {
    provide: SysFwHttpApi,
    deps: []
  }
}

It seems to be working, however it seems to have lost the other providers and to be expecting all providers to be passed this way!

This is the error I'm getting:

No provider for HttpClient! (SysFwDatesService -> SysFwHttpApi -> HttpClient)

Do I need to put everything in the servicesList? What am I doing wrong?

Before I use the injector it all worked fine!

Thanks for your help!

Upvotes: 4

Views: 1807

Answers (2)

Estus Flask
Estus Flask

Reputation: 222369

HttpClient is defined in HttpClientModule and has a hierarchy of dependencies that is not very easy to list by hand as single providers array. ReflectiveInjector and Injector don't support Angular modules, and it's impractical to parse a module to get a hierarchy providers manually - this is what Angular already does internally.

The module that initializes service locator should import dependency modules, too, and resulting injector should inherit from module injector:

@NgModule({ imports: [HttpClientModule], ... })
export class AppModule {
  constructor(injector: Injector) {
    ServiceLocator.injector = ReflectiveInjector.resolveAndCreate(
      Object.keys(services).map(key => ({
        provide: services[key].provide,
        useClass: services[key].provide,
        deps: services[key].deps
      })),
      injector
    );
  }
}

By the way, services object keys aren't used for anything and are redundant. services likely should be an array.

I would strongly advise against using this home-grown service locator in real-world application. It is a proof of concept that does what it does, but also a hack that isn't idiomatic to the framework and may have a lot of negative consequences that may not be obvious at this moment, e.g. lazy loaded modules.

The recommendation is to use the means offered by the framework wherever possible, this way the application has most chances to be trouble-free in future.

It is:

@Injectable() // needed for JIT compilation
abstract class Injective {
  constructor(protected injector: Injector) {}
}

@Injectable() // potentially needed for AOT compilation
class FooService extends Injective {
  baz = this.injector.get(Baz);
}

@Component(...)
class BarComponent extends Injective {
  foo = this.injector.get(FooService);

  // just passes injector to parent constructor if there's a need for own constructor
  constructor(injector: Injector, public baz: Baz) {
    super(injector);
    // ...
  }
}

Notice that to be on safe side, @Injectable() is needed on both parent and child classes (this may vary between Angular and Angular CLI versions).

Upvotes: 1

erosb
erosb

Reputation: 3141

have you tried explicitly specifying HttpClient as a dependency if SysFwHttpApi ? Like

'SysFwHttpApi': { provide: SysFwHttpApi, deps: [HttpClient] }

Upvotes: 0

Related Questions