How to use factory provider?

43,110

Solution 1

I faced same issues to inject on app_initalizer, after long search i found the solution below. May be this is help for your scenario.

@NgModule({
  imports: [ BrowserModule],
  ...
  providers: [
    {
      provide: HeroService,
      useFactory: heroServiceFactory,
      deps: [Logger, UserService],
      multi: true
    }
    ]
})
export class AppModule {}


export function heroServiceFactory = (logger: Logger, userService: UserService) => {
    return new HeroService(logger, userService.user.isAuthorized);
};

Solution 2

Question 1:
How should the class generally look like? Where should one add the above code segments?

Answer:
You can create a file to contain code for hero service provider and its factory function. This file can be named hero.service.provider.ts.
And, write code for hero service in another file named hero.service.ts.

Check out this article about how to use Angular Service Providers to see more examples.

hero.service.provider.ts file:

import { HeroService } from './hero.service';
import { Logger } from './logger.service';
import { UserService } from './user.service';

let heroServiceFactory = (logger: Logger, userService: UserService) => {
  return new HeroService(logger, userService.user.isAuthorized);
};

export let heroServiceProvider =
  { provide: HeroService,
    useFactory: heroServiceFactory,
    deps: [Logger, UserService]
  };

Question 2: How should/could one use this factory?

Answer:
Following the example code that you provided, a factory could be configured for a service using providers field in @Component decorator and the service can be injected through the class constructor or using Angular injector object.

However, tree shaking is not working when a factory provider configured this way. Check out this Angular tree shakable service example if you need tree shaking to be working.

import { heroServiceProvider } from './hero.service.provider';
import { HeroService } from './hero.service';

@Component({
  selector: 'my-selector',
  template: ``,
  providers: [heroServiceProvider]
})
export class HeroComponent {
  constructor(private heroService: HeroService) { } 
}

Solution 3

I don't have enough points to comment against @mgmg's answer, but here is some useful info...

I used the provider factory pattern given in docs (and subject of this question) in my own app, but kept getting an error in compilation

ERROR in Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function...

Essentially, when a factory dependency is used in a root module, all it's dependencies have to be provided with it.

That means the code block given in @mgmg's answer should strictly have the dependent services

import { heroServiceProvider } from './hero.service.provider';
import { UserService } from './user.service';
import { Logger }      from './logger.service';

...

@NgModule({

  ...

  providers: [
    Logger,
    UserService,
    heroServiceProvider
    // Wrapper for:
    //{ provide: HeroService,
    //  useFactory: (logger: Logger, userService: UserService) => {
    //    return new HeroService(logger, userService.user.isAuthorized)
    //  },
    //  deps: [Logger, UserService]
    //};

  ],
  bootstrap: [ AppComponent ]
})

Note, in angular docs here, heroServiceProvider is provided in heroes.component not in app.module, and the references to Logger and UserService are not needed there. I presume these two dependencies are being picked up from higher up the injector tree.

Solution 4

1) The first part of your question is easy: you just keep the snippet you provided in a separate file and import it in the component as shown in your question:

import { heroServiceProvider } from './hero.service.provider';

2) As for the actual usage, you don't really need to change service-related code in the component. Just keep using it if the original service was injected. You don't even need to modify your component constructor. The idea of the service provider is that you can customize your service on a per-component basis by having custom service provider for each component, and you would do component-specific initialization in the factory function. Just don't forget to list your service provider in the decorator of your component, Angular is taking care of the rest "automagically".

Share:
43,110
arjacsoh
Author by

arjacsoh

Updated on August 25, 2020

Comments

  • arjacsoh
    arjacsoh over 3 years

    I need to inject a service into another service in an Angular 2 application.

    After reading the docs I deduced, that the best approach is to use a Factory Provider. However, two questions have arisen:

    1) The docs recommend the creation of a HeroServiceProvider class with two "code segments":

    let heroServiceFactory = (logger: Logger, userService: UserService) => {
      return new HeroService(logger, userService.user.isAuthorized);
    };
    
    export let heroServiceProvider =
      { provide: HeroService,
        useFactory: heroServiceFactory,
        deps: [Logger, UserService]
      };
    

    My question is how should the class generally look like? Where should one add the above code segments?

    2) How should/could one use this factory? I see, it should be imported as:

    import { heroServiceProvider } from './hero.service.provider';
    
    @Component({
      selector: 'my-selector',
      template: `
      `,
      providers: [heroServiceProvider]
    })
    

    How could then the desired parametrized service retrieved and accessed?

  • Reinhard
    Reinhard about 7 years
    I agree: I got the same Error: "ERROR in Error encountered resolving symbol...." And I found a solution: useFactory: (heroServiceFactory) . just add ( ) around the factory.
  • Andrei Gătej
    Andrei Gătej over 4 years
    Thanks a bunch! I've read some articles from your blog and I've found some interesting stuff. I didn't know you could use @Injectable({ providedIn: 'root', useFactory: () => {}, deps:[] }) in order to make decisions at runtime and also get tree-shakable dependency. That's so cool!
  • weilah
    weilah almost 4 years
    the OP say at the beginning of its question: "I need to inject a service into another service" and this example is about injecting it in a Component, not a Service. Do you know how could one achieve this behaviour?
  • Jun711
    Jun711 almost 4 years
    @weilah yea, that is the reply to first question. Read that OP says "After reading the docs I deduced, that the best approach is to use a Factory Provider. However, two questions have arisen:" That means Factory Provider is the way to do it.