Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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]
        }
    ]
})
like image 891
Dominic Avatar asked Mar 04 '17 07:03

Dominic


2 Answers

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

like image 149
yurzui Avatar answered Oct 03 '22 02:10

yurzui


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 ]
    },
like image 41
Simon_Weaver Avatar answered Oct 03 '22 00:10

Simon_Weaver