arjacsoh
arjacsoh

Reputation: 9242

How to use factory provider?

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?

Upvotes: 17

Views: 60925

Answers (5)

Rajamohan Anguchamy
Rajamohan Anguchamy

Reputation: 1736

I had a same issues to inject on app_initalizer. Inject your service in providers like solution below. Try to modify tour code like below.

@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);
};

Reference angular.dev

Upvotes: 21

Jun
Jun

Reputation: 3044

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) { } 
}

Upvotes: 2

Richard Matsen
Richard Matsen

Reputation: 23533

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.

Upvotes: 2

Anton Nikiforov
Anton Nikiforov

Reputation: 3485

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".

Upvotes: 1

mgmg
mgmg

Reputation: 26

Today I faced with the same question and found some solution.

1) I found the exact code in angular.io, see following: https://github.com/angular/angular.io/blob/master/public/docs/_examples/dependency-injection/ts/src/app/heroes/hero.service.provider.ts (link updated 15 Mar 2017).
From this code, heroServiceProvider is no need to be a class.

2) In app.module.ts, there is providers property in @NgModule. And you can add the heroServiceProvider in this like following:

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

...

@NgModule({

  ...

  providers: [
    heroServiceProvider
  ],
  bootstrap: [ AppComponent ]
})

By providing service in @NgModule like that, you can use the service in application scope.

Upvotes: 0

Related Questions