Skip to content

APP_INITIALIZER not injecting using Injector class #14459

Closed

Description

@m-sawicki-tt

I'm submitting a ... (check one with "x")

[x] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior
I create 3 services:

@Injectable()
export class Dog {
  constructor() {
    console.log("Dog constructor");
  }
}

@Injectable()
export class Child {
  constructor(injector: Injector) {
    let dog = injector.get(Dog);
    console.log("Child constructor | injector: %o | dog: %o", injector, dog);
  }
}

@Injectable()
export class Parent {
  constructor(child: Child,
              dog: Dog) {
    console.log("Parent constructor | child: %o | dog: %o", child, dog);
  }
}

Now I create the APP_INITIALIZER factory:

export function configFactory(
  parent: Parent) {

  return () => {
    console.log(parent);
  }
}
export const config: any = {
  provide: APP_INITIALIZER,
  deps: [Parent],
  useFactory: configFactory,
  multi: true
};

Finally I add everything to the module

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [
    Parent,
    Child,
    Dog,
    config
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

After I run It I get the following console messages:

Child constructor | injector: AppModuleInjector | dog: undefined
Dog constructor
Parent constructor | child: Child | dog: Dog

It looks like a Child constructor tries to inject a Dog, which is also queued to inject in a Parent constructor, but at that time somehow can't create a Dog, thus returning undefined.

Expected behavior
The console should return:

Dog constructor
Child constructor | injector: AppModuleInjector | dog: Dog
Parent constructor | child: Child | dog: Dog

There are several ways to fix that:

  1. Change the parameter order in a Parent constructor, so that Dog is injected before Child:
@Injectable()
export class Parent {
  constructor(dog: Dog,
              child: Child) {
    console.log("Parent constructor | child: %o | dog: %o", child, dog);
  }
}
  1. Inject Dog in a Child constructor instead of using Injector:
@Injectable()
export class Child {
  constructor(dog: Dog) {
    console.log("Child constructor | dog: %o", dog);
  }
}
  1. Injecting Parent in a Component instead of APP_INITIALIZER

Minimal reproduction of the problem with instructions
http://plnkr.co/edit/OhqJKLD8zuZOVn9RF0UJ

What is the motivation / use case for changing the behavior?

Please tell us about your environment:
Windows 7 Enterprise, Webstorm 2016.3.3

  • Angular version: 2.0.X
    Angular 2.4.7
    @angular-cli 1.0.0-beta.30
  • Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]
    all
  • Language: [all | TypeScript X.X | ES6/7 | ES5]
    TypeScript 2.0.10

  • Node (for AoT issues): node --version =

Activity

  1. JoniJnm commented on Feb 15, 2017

    @JoniJnm
    Contributor

    I think it's not a bug.

    You should use the second fix.

    Injector might not be completely ready while you are injecting providers:

    deps: [Parent],
    

    What angular does:

    1. Create Parent (dependences: Dog, Child)
    
    1.1 Create Dog (dependences: anything)
    1.2 Dog created and injected
    
    1.3 Create Child (dependences: Injector)
    1.4 Dog created (and injected) with current injector (only Dog is injected, not Child). Here is the problem.
    
    2. Parent created and injected
    
    3. Call configFactory
    

    So, you have to tell angular that Child needs Dog and angular will wait for it.

    Other way to fix it:

    deps: [Dog, Parent],
    

    Dog will be injected in injector so will be ready later.

  2. m-sawicki-tt commented on Feb 15, 2017

    @m-sawicki-tt
    Author

    @JoniJnm Ok, but why the app behaves "normally" when I remove a Dog dependency from a Parent class?

    Take a look at this (unchanged):

    constructor(child: Child,
                dog: Dog) {

    A Child is first and wants to inject a Dog, then the Injector inside a Child constructor somehow doesn't create a new Dog instance.

    And now this (removed a Dog parameter)

    constructor(child: Child) {

    Now a Child is again first in a Parent constructor, but this time the Injector properly injects a Dog instance.

  3. JoniJnm commented on Feb 16, 2017

    @JoniJnm
    Contributor

    Maybe because angular "creates" the objects that are not needed in APP_INITIALIZER first.

    With Parent: constructor(child: Child) { the code generated by angular is:

    // The following code creates a reference
    // When Child does injector.get(Dog) the Dog provide is not created yet but angular will know how get it
    Object.defineProperty(AppModuleInjector.prototype, '_Dog_61', { get: function() {
      var self = this;
      if ((self.__Dog_61 == null)) { (self.__Dog_61 = new jit_Dog56()); }
      return self.__Dog_61;
    }});
    
    AppModuleInjector.prototype.createInternal = function() {
      //...
      self._Child_13 = new jit_Child69(self); //Dog will be created in the constructor of Child
      self._Parent_14 = new jit_Parent70(self._Child_13);
      //....
    }

    With Parent: constructor(child: Child, dog: Dog) { the code is:

    AppModuleInjector.prototype.createInternal = function() {
      //...
      self._Child_13 = new jit_Child68(self); //dog won't be ready because is created in the next line
      self._Dog_14 = new jit_Dog69(); //why isn't it in the previous line? Because angular don't know that will be needed in child constructor
      self._Parent_15 = new jit_Parent70(self._Child_13,self._Dog_14);
      //....
    }

    So, if you do not tell to angular the real dependencies, angular can not create a real graph and some of them might not be ready.

    With Child: constructor(dog: Dog) { the code is:

    AppModuleInjector.prototype.createInternal = function() {
      //...
      self._Dog_13 = new jit_Dog68();
      self._Child_14 = new jit_Child69(self._Dog_13); //all is ok :)
      self._Parent_15 = new jit_Parent70(self._Child_14,self._Dog_13);
      //....
    }

    Be careful using Injector, and more in APP_INITIALIZER step. It should be used only if you don't have other way.

    I don't know if angular can change some logic and reference the objects first. But I think that you are doing it wrong.

    At least you should include the real dependences in configFactory with deps: [Dog, Parent],

  4. added this to the Backlog milestone on Jan 23, 2018
  5. trotyl commented on Feb 20, 2018

    @trotyl
    Contributor

    Has been fixed by #19558, you can try have another look at the plunker:

    image

  6. angular-automatic-lock-bot commented on Sep 14, 2019

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  7. locked and limited conversation to collaborators on Sep 14, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions