Angular 2 DI: pass input binding to deps of factory provider
Asked Answered
C

2

8

Is there an easy way to inject an input binding into the deps array of a provider factory? Below obviously does not work.

const myServiceFactory = (object: any) => {
   //...
};

@Component({
    // ...
    inputs: ['object'],
    providers: [
        {
            provide: Object,
            useValue: object,
        },
        {
            provide: MyService,
            useFactory: myServiceFactory,
            deps: [Object]
        }
    ]
})
Cogwheel answered 4/3, 2017 at 7:22 Comment(5)
What is the purpose?Estipulate
I want to get a specific implementation of MyService based on the input bindingCogwheel
ngOnInit() { this.myService.setParam(this.myInput); }Estipulate
That is what I thought but instead of configuring MyService onInit, I would prefer to have a factory methodCogwheel
i solved a similar issue with a route Resolve that provides resolved data to a component via ActivatedRoute. I was able to inject the ActivatedRoute (via deps) into a provider factory, grab the data corresponding to the resolved router parameters - and then ‘provide’ my new object.Modestia
E
14

As a possible solution you can try to do it something like this:

const myServiceFactory = (self: Child) => {
  return new MyService(self.param);
};

class MyService {
  constructor(private param: string) {}
}
@Component({
  selector: 'child',
  template: `{{ param }}`,
  providers: [
    {
      provide: MyService,
      useFactory: myServiceFactory,
      deps: [Child]
    }
  ]
})
export class Child {
  @Input() param: any;

  constructor(private inj: Injector) { }

  ngOnInit() { // or ngOnChanges
    let service = this.inj.get(MyService);
  }
}

Plunker Example

Estipulate answered 4/3, 2017 at 8:19 Comment(2)
surely the input binding of the component isn't available at the time of the provider creation?Ministry
Disregard... I now see that the provider is created at ngOnInit instead of constructor which is why it works.Ministry
M
1

The accepted answer works well, but if you have several dependencies 'provided' by your component that depend on each other than things get a lot more complicated.

Another approach that may work if you're already heavily using observables is to provide a LAZY_ID token which is actually a ReplaySubject<number> (or whatever type you need it to be).

In your ngOnInit() you simply call this.lazyID.next(this.id) to update the ReplaySubject with the value passed in via @Input.

In addition you would then use this LAZY_ID with a provider factory to create whatever the primary dependency was.

Disclaimer: I don't think this is a good general solution to this issue. It can get clumsy but sometimes it may work!

Here's a simplified example - would welcome improvements:

export const LAZY_ID = new InjectionToken<ReplaySubject<number>>('LAZY_ID');

export const LazyIDFactory = () => 
{
    return new ReplaySubject<number>(1);
}


export const productDataFromLazyIDFactory = (productService: ProductService, id$: ReplaySubject<number>) =>
{
    // Create your 'ProductData' from your service and id$
    // So yes - the catch here is your ProductData needs to take an observable
    // as an input - which only really works if you're extensively using observables
    // everywhere. You can't 'wait' for the result here since the factory must return
    // immediately
}

Then in your @Component

providers: [ 

    // creates our ReplaySubject to hold the ID
    {
        provide: LAZY_ID,
        useFactory: LazyIDFactory
    },
    { 
        provide: ProductData,
        useFactory: productDataFromLazyIDFactory,
        deps: [ ProductService, LAZY_ID ]
    },
Modestia answered 13/8, 2019 at 20:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.