33

I have a component like:

@Component({
  selector: 'app-item',
  template: '<p>{{title}}</p>'
})
export class TitleComponent {
  @Input() title:string;
}

@Component({
  selector: 'app-foo',
  template: '<ng-container *ngComponentOutlet="outlet"></ng-container>'
})
export class FooComponent {
  outlet = TitleComponent;
}

How do you pass input title value on the ng-container for the TitleComponent or how can I set this value?

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
Fran b
  • 3,016
  • 6
  • 38
  • 65
  • 1
    I'm about to start looking into using this to replace using ViewContainerRef.createComponent(). From what I read you'll need to get the ComponentRef that ngComponentOutlet creates and then the situation is the same as ViewContainerRef.createComponent. You use componentOutlet.instance and the you manually set the properties that are your @Input()s. It sucks since all sorts of stuff doesn't work automatically (ngOnChange, OnPush, @Input("PublicName") _privateName). I hoped ngComponentOutlet would improve this, but it doesn't seem so. – Aardvark Feb 16 '17 at 19:46
  • 1
    Possible duplicate of [angular 4+ assign @Input for ngComponentOutlet dynamically created component](http://stackoverflow.com/questions/42522633/angular-4-assign-input-for-ngcomponentoutlet-dynamically-created-component) – Greg Dan Mar 10 '17 at 10:48
  • 2
    A workaround is to add a Service that initialize your component, and with ngComponentOutletInjector inject a custom Injector. One that you can doit from the parent. – titusfx Jul 26 '17 at 12:02
  • 1
    I'm even afraid to ask about @Output... – silvio Aug 24 '18 at 13:14

4 Answers4

23

As Reno has already mentioned, you can use an injector to inject values.

For the sake of completeness, here is an example with a dynamic "title" value:

export const TITLE = new InjectionToken<string>('app.title'); 


@Component({
  selector: 'app-item',
  template: '<p>{{title}}</p>'
})
export class TitleComponent implements OnInit {
  @Input() title:string;

  constructor(@Inject(TITLE) private titleInjected: string){

  } 

  ngOnInit() {
    this.title = this.title || this.titleInjected;
  }

}


@Component({
  selector: 'app-foo',
  template: '<ng-container *ngComponentOutlet="outlet; injector: myInjector"></ng-container>'
})
export class FooComponent {
  outlet = TitleComponent;
  myInjector: Injector;

  constructor(injector: Injector){
    let title = 'My dynamic title works!';
    this.myInjector = ReflectiveInjector.resolveAndCreate([{ provide: TITLE, useValue: title }], injector);
  }
}


@NgModule({
  providers: [
    { provide: TITLE, useValue: '' }
  ]
})
export class AppModule { }
Pierre Chavaroche
  • 1,253
  • 16
  • 12
  • 2
    could you please update your answer that it will work for dynamically changing `title` from parent component? I did it like so - https://stackblitz.com/edit/angular-ngcomponentoutlet-input?file=src/app/app.component.ts but maybe there is better solution – Stepan Suvorov Jul 26 '18 at 08:42
  • 2
    Good answer, although it is deprecated now. Check the **complex example** here guys for more info: https://angular.io/api/common/NgComponentOutlet – Charis Moutafidis Jun 16 '20 at 14:28
12

An example solution is shown in the ngComponentOutlet documentation, specifically the second example with the @Injectable, as titusfx also mentioned.

Here is what it would look like with your use case:

@Injectable()
class Info {
  title = 'a little hacky';
}

@Component({
  selector: 'app-item',
  template: '<p>{{info.title}}</p>'
})
export class TitleComponent {
  // @Input() title:string;
  constructor(public info: Info){ }
}

@Component({
  selector: 'app-foo',
  template: '<ng-container *ngComponentOutlet="outlet; injector: myInjector"></ng-container>'
})
export class FooComponent {
  outlet = TitleComponent;
  myInjector: Injector;
  constructor(injector: Injector){
    this.myInjector = ReflectiveInjector.resolveAndCreate([Info], injector);
  }
}
Renaud
  • 4,569
  • 7
  • 41
  • 72
  • 1
    Thank you for your example. It's even possible to inject dynamic values with "useValue" provider: ReflectiveInjector.resolveAndCreate([{ provide: DATA, useValue: myData }], injector). "TitleComponent" needs to inject "DATA" to get "myData" then. – Pierre Chavaroche Jan 29 '18 at 09:25
4

Consider using ng-dynamic-component library to work with dynamic components.

  • 5
    Please add example of usage – Jan Černý Nov 12 '18 at 11:01
  • Nice! Took me 5 minutes to get it working! Is it possible to use entrycomponents instead of the DynamicModule.withComponents(....) import? – Jelmer Jellema Dec 03 '19 at 10:10
  • Started to get excited for this and then found that there is no Angular 12 support and most of the latest issues in the repo are asking "Is this project abandoned?" with no response. :( – coppereyecat Oct 12 '21 at 17:42
  • FYI--support has been added through the the latest angular version, which is version 14 as of this comment. – dlporter98 Jul 30 '22 at 20:31
0

As of Angular 16.2 there is a new way to handle inputs with ngComponentOutlet :

@Component({
 selector: 'app-item',
 template: '<p>{{title}}</p>'
})
export class TitleComponent {
 @Input() title:string;
}

@Component({
 selector: 'app-foo',
 template: '<ng-container *ngComponentOutlet="outlet; inputs: inputs"></ng-container>'
})
export class FooComponent {
 outlet = TitleComponent;
 inputs = { title: 'myTitle' }
}
Sam
  • 300
  • 1
  • 5