ng-content select bound variable

28,424

Solution 1

Since you are binding to a variable, please use the one way binding syntax like:

<ng-content [select]="f.customid"></ng-content>

Correction
ng-content is for static projection only. It's meant to be fast "transcluding". Please check this issue for more info

Solution 2

I know this is an old question, but this is one of the first places I landed when searching for this functionality so I'll add how I was able to solve it.

ngContent is only for static projection, so you can't use it to do any bindings. If you need bindings in your projected content you can use ngTemplateOutlet and ngOutletContext.

Usage Example:

<my-component>
    <template let-item="item">
        <h1>{{item?.label}}</h1> - <span>{{item?.id}}</span>
    </template>
</my-component>

Inside MyComponent you can access that template using ContentChild:

@ContentChild(TemplateRef) templateVariable: TemplateRef<any>;

Then inside your component's template you pass that to ngTemplateOutlet like this:

<div *ngFor="let item of list">
    <template [ngTemplateOutlet]="templateVariable" [ngOutletContext]="{item: item}"></template>
</div>

The ngOutletContext is optional but it allows you to create the object that you will be binding to in the template. Notice that I created a property item in the context object. That matches the name I put on the template here: let-item="item"

Now the consumer of my-component can pass in the template to be used for each item in the list.

Credit: This answer led me in the right direction.

Solution 3

You can do this already if you wrap the content with a <template> element.

// renders the template
// `item` is just an example how to bind properties of the host component to the content passed as  template
@Directive({
  selector: '[templateWrapper]'
})
export class TemplateWrapper implements OnChanges {

  private embeddedViewRef:EmbeddedViewRef<any>;

  @Input()
  private item:any;

  constructor(private viewContainer:ViewContainerRef) {
    console.log('TemplateWrapper');
  }

  @Input() templateWrapper:TemplateRef<any>;

  ngOnChanges(changes:{[key:string]:SimpleChange}) {
    if (changes['templateWrapper']) {
      if (this.embeddedViewRef) {
        this.embeddedViewRef.destroy();
      }
      console.log('changes', changes);
      this.embeddedViewRef = this.viewContainer.createEmbeddedView(this.templateWrapper, {item: this.item});
    }

    if (this.embeddedViewRef) {
      console.log('context', this.embeddedViewRef.context);
      this.embeddedViewRef.context.item = this.item;
    }
  }
}
// just some component that is used in the passed template
@Component({
  selector: 'test-component',
  styles: [':host { display: block; border: solid 2px red;}'],
  directives: [TemplateWrapper],
  template: `
<div>test-comp</div>
<div>prop: {{prop | json}}</div>
  `
})
export class TestComponent {
  @Input() prop;

  constructor() {
    console.log('TestComponent');
  }
}
// the component the `<template>` is passed to to render it
@Component({
  selector: 'some-comp',
  directives: [TemplateWrapper],
  template: `
<div>some-comp</div>
<div *ngFor="let f of fields">
  <div [ngSwitch]="f.type">
    <span *ngSwitchCase="'custom'">         
      <template [templateWrapper]="template" [item]="f" ></template>
    </span>
  </div>
</div>  
  `
})
export class SomeComponent {

  constructor() {
    console.log('SomeComponent');
  }

  @ContentChild(TemplateRef) template;

  fields = [
    {name: 'a', type: 'custom'},
    {name: 'b', type: 'other'},
    {name: 'c', type: 'custom'}];
}
// the component where the `<template>` is passed to another component
@Component({
  selector: 'my-app',
  directives: [SomeComponent, TestComponent],
  template: `
<some-comp>
  <template let-item="item">
    <div>some content</div>
    <div>item: {{item | json}}</div>
    <test-component [prop]="item"></test-component>
  </template>
</some-comp>
  `,
})
export class App {
  constructor() {
    console.log('AppComponent');
  }
}

Plunker example

Share:
28,424

Related videos on Youtube

gatapia
Author by

gatapia

Over the last 2 years Guido has been involved in building 2 predictive analytics libraries for both the .Net platform and the Python language. These libraries and Guido's machine learning experience have placed PicNet at the forefront of predictive analytics services in Australia. For the last 10 years Guido has been the Software and Data Science Manager at PicNet and in that time Guido has delivered hundreds of successful software and data projects. An experience Architect and all round 'software guy' Guido has been responsible for giving PicNet its 'quality software provider' reputation. Prior to PicNet, Guido was in the gaming space working on advanced graphics engines, sound (digital signal processing) engines, AI players and other great technologies. Interesting Links: Profile: http://www.picnet.com.au/aboutus/GuidoTapia.aspx Blog: http://www.picnet.com.au/blogs/guido/ Kaggle Profile: https://www.kaggle.com/users/78083/guido-tapia GitHub: https://github.com/gatapia Speaking Engagements and Conferences: http://www.picnet.com.au/AboutUs/News.aspx

Updated on July 09, 2022

Comments

  • gatapia
    gatapia almost 2 years

    I'm trying to create a form builder using angular 2. An very basic example is as follows:

    this.fields = [{name: 'Name', type: 'text'}, {name: 'Age', type: 'number'}];
    

    But I also want to support custom elements like:

    this.fields = [
      {name: 'Name', type: text}, 
      {name: 'Age', type: 'custom', customid: 'Ctl1'},
      {name: 'Whatever', type: 'custom', customid: 'Ctl2'}
    ];
    // template:
    <super-form [fields]="fields">
      <Ctl1><input type="number" ...><Ctl1>
      <Ctl2><whaterver-control ...><Ctl2>
    </super-form>
    

    In my form builder component I then have something like:

    <div *ngFor="let f of fields">
      <div [ngSwitch]="f.type">
        <span *ngSwitchWhen="'custom'">          
          <ng-content select="f.customid"></ng-content>
        </span>
      </div>
    </div>
    

    But given that I'm here this obviously does not work. Is this an ng2 limitation? If so, I guess I could hard code say 5 optional content elements and check if they are specified and not have dynamic selects but this is a hack.

    Cheers

  • Abdulrahman Alsoghayer
    Abdulrahman Alsoghayer almost 8 years
    @gatapia Sorry, it seems ng-content is for static projection only. Check this issue
  • gatapia
    gatapia almost 8 years
    thanks I just found that also. Modify your answer and I'll mark it as correct.
  • gatapia
    gatapia almost 8 years
    I am trying, really am but I cannot make heads or tails of this. Looks like you create a template (could be empty I guess) and pass it to test-compoenent. What is test component, is that supposed to be the "dynamic element" I am passing into my form builder? Does this mean that for every element I may want to inject into my form builder I need to give it a [prop] input?
  • Günter Zöchbauer
    Günter Zöchbauer almost 8 years
    TestComponent is just some component I use in the template that is passed to SomeComponent (could be anything). I just added it to demonstrate how content in the passed template can bind to properties in SomeComponent. Hope this helps. If you have further questions I won't reply today anymore. Here it's time for bed. The prop stuff is also just some example how to bind from the template (as before) you don't need to add it for the simplest case.
  • Sebastian
    Sebastian over 7 years
    @GünterZöchbauer Is this still the correct way to do it using Angular 2.4.1?
  • Günter Zöchbauer
    Günter Zöchbauer over 7 years
    Looks like it should still work this way. directives` was moved tto @NgModule({ declarations: [...]. <template [ngTemplateOutlet]="..." might work as well instead of TemplateWrapper
  • Sebastian
    Sebastian over 7 years
    @GünterZöchbauer The plunker is broken at least. Could you update it please?
  • Günter Zöchbauer
    Günter Zöchbauer over 7 years
    Plunker doesn't work for me since a while. I might have a look when Plunker is working for me again.
  • naomi
    naomi about 7 years
    Is there a way to place the Outlet template (the one with the let-item) in another component? - not directly inside my-component?
  • Justin
    Justin about 7 years
    @naomi. I'm not sure if I understand what you're after. Are you talking about the html template code or the controller code? Once you have a handle on the template, you can pass it around like any other variable, so you just need to get ahold of it somehow. For example, in my use case I actually had to pass the template down another level to the child of my component, so I just passed it as an input parameter to the child component and then I had the template use it there.
  • Justin
    Justin about 7 years
    @naomi. If you didn't want your item template inside the <my-component> tag, you could possibly use a ViewChild to get it from somewhere else, but I haven't tested that.
  • naomi
    naomi about 7 years
    I've tried using ViewChild - but it did't work so I asked a question stackoverflow.com/q/42688946/4610489
  • Naveed Ahmed
    Naveed Ahmed about 7 years
    What if I have multiple templates inside <my-component>, I know we can use @ContentChildren(TemplateRef) templates: QueryList<TemplateRef>; But how do I update this portions for multiple templates <template [ngTemplateOutlet]="templateVariable" [ngOutletContext]="{item: item}"></template>, can you please guide?
  • Justin
    Justin about 7 years
    I'm not sure what you mean. If you have multiple templates but only need to display one of them based on some condition, then you could just search you templates list and store the correct template in templateVariable. If you actually want to display all the templates, then maybe you could do an *ngFor on the <template> tag to loop through all your templates and display them all. I'm not really sure what you are trying to do, but hopefully that helps. If not, you may want to ask a new question.
  • phil294
    phil294 over 6 years
    so this is just like <ng-content>, just with passing variables from child to parent. I love it. My understanding is that one could include multiple templates like <template let-item1="..">, <template let-item2="..."> etc.
  • Greg
    Greg about 6 years
    this is not really a correct answer since there is no solution
  • Lal Krishna
    Lal Krishna over 3 years
    ngOutletContext was renamed to ngTemplateOutletContext