简体   繁体   中英

Angular 7: injection with dependencies for ngComponentOutlet

I need to use some kind of @Input() decorator for my ngComponentOutlet .

But seems that Angular hasn't this feature. Instead, all things that I want to pass inside my outlet components should be provided via Injector .

And it's fine if I want to initiate the thing I want provide to inside injectable class. But I need to dive deeper and provide some kind of observable variable ( Observeble<number> type for example) at creation Injector step. But I can't to get observable variable inside outlet component. Getting following error: NullInjectorError: No provider for [object Object]! .

Here is an example of code, I got this pattern from angular docs ( angular.io/api/common/NgComponentOutlet ) and modified a little:

@Injectable()
export class Greeter {
    suffix$: Observable<number> = null;
    constructor(private _suffix$: Observable<number>) {
        this.suffix$ = this._suffix$;
    }
}

@Component({
    selector: 'complete-component',
    template: `Complete: {{ greeter.suffix$ | async }}`
})
export class CompleteComponent {
    constructor(public greeter: Greeter) {
        this.greeter.suffix$.subscribe(data => console.log('data', data));
        // not working
    }
}

@Component({
    selector: 'ng-component-outlet-complete-example',
    template: `
        <ng-container *ngComponentOutlet="CompleteComponent; 
                                        injector: myInjector;"
    })
export class NgTemplateOutletCompleteExample {
    CompleteComponent = CompleteComponent;
    myInjector: Injector;

    suffix$: Observable<number> = of(3);

    constructor(injector: Injector) {
        this.myInjector =
            Injector.create({providers: [{provide: Greeter, deps: [this.suffix$]}], parent: injector});
    }
}

So how can I get and subscribe into this $suffix variable inside outlet component.

PS If I provide NgTemplateOutletCompleteExample into the deps array and get NgTemplateOutletCompleteExample.suffix$ inside injectable component Greeter - it will work. But the thing is I have a lot of NgTemplateOutletCompleteExample components and this thing invalid in my case.

I had a similar problem for dynamic injection of components taking Inputs and returning Outputs. Natively, Angular does not support @Input() and @Output on dynamic components.

You can use the package NgDynamicComponent: https://www.npmjs.com/package/ng-dynamic-component .

NgComponentOutlet syntax support

The package supports the NgComponentOutlet syntax as you can see here: https://www.npmjs.com/package/ng-dynamic-component#ngcomponentoutlet-support

From what I see from your code, it's what you are asking for: NgComponentOutlet + Inputs support.

Custom dynamic injector for more advanced stuff

You can also create a custom injector: https://www.npmjs.com/package/ng-dynamic-component#extra

The main thing is that Injector - is a static injector. So, to provide any data inside component oultet (or get out any callbacks) I must use useValue instead deps .

Contructor in NgTemplateOutletCompleteExample should be like this:

constructor(injector: Injector) {
    this.myInjector =
        Injector.create({providers: [{provide: Greeter, deps: [], useValue: {suffix$: this.suffix$}], parent: injector});
}

In order to understand why you're getting that error, I'd recommend having at look at Dependency Providers from the docs.

The error comes from here

Injector.create({providers: [{provide: Greeter, deps: [this.suffix$]}], parent: injector});

To put it briefly, the deps array must be a list of valid DI tokens and there is no token configured for this.suffix$ .

Quoted from the sources(Angular 8.0.0)

export interface FactorySansProvider {
  /**
   * A function to invoke to create a value for this `token`. The function is invoked with
   * resolved values of `token`s in the `deps` field.
   */
  useFactory: Function;

  /**
   * A list of `token`s which need to be resolved by the injector. The list of values is then
   * used as arguments to the `useFactory` function.
   */
  deps?: any[];
}

Here is one way to solve it:

const OBS_TOKEN = new InjectionToken('obs');

@Injectable()
export class Greeter {
  greeterProp = 'hello!';

   constructor (@Inject(OBS_TOKEN) public suffix$: Observable<any>) {
    console.log('[GREETER]', this.suffix$)
  }
}

@Component({ /* ... */ })
export class NgTemplateOutletCompleteExample {
 /* ... */

 myInjector = Injector.create({
    providers: [
      { provide: Greeter, deps: [OBS_TOKEN], },
      { provide: OBS_TOKEN, useValue: this.dummyObs$ },
    ],
    parent: this.inj,
  })

 /* ... */
}

StackBlitz

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM