angular 2 access ng-content within component
Asked Answered
L

6

53

How can I access the "content" of a component from within the component class itself?

I would like to do something like this:

<upper>my text to transform to upper case</upper>

How can I get the content or the upper tag within my component like I would use @Input for attributes?

@Component({
    selector: 'upper',
    template: `<ng-content></ng-content>`
})
export class UpperComponent {
    @Input 
    content: String;
}

PS: I know I could use pipes for the upper case transformation, this is only an example, I don't want to create an upper component, just know how to access the component's content from with the component class.

Lynn answered 11/4, 2016 at 10:0 Comment(1)
Do you want the HTML string or a reference to a specific component, ...?Foliose
R
37

You need to leverage the @ContentChild decorator for this.

@Component({
  selector: 'upper',
  template: `<ng-content></ng-content>`
})
export class UpperComponent {
  @Input 
  content: String;

  @ContentChild(...)
  element: any;
}

Edit

I investigated a bit more your issue and it's not possible to use @ContentChild here since you don't have a root inner DOM element.

You need to leverage the DOM directly. Here is a working solution:

@Component({
  selector: 'upper',
  template: `<ng-content></ng-content>`
})
export class UpperComponent {
  constructor(private elt:ElementRef, private renderer:Renderer) {
  }

  ngAfterViewInit() {
    var textNode = this.elt.nativeElement.childNodes[0];
    var textInput = textNode.nodeValue;
    this.renderer.setText(textNode, textInput.toUpperCase());
  }
}

See this plunkr for more details: https://plnkr.co/edit/KBxWOnyvLovboGWDGfat?p=preview

Rorie answered 11/4, 2016 at 10:2 Comment(1)
What if the content being transcluded isn't an another component? What if you just want all the transcluded content within ng-content?Eradis
K
60

If you want to get a reference to a component of the transcluded content, you can use:

@Component({
    selector: 'upper',
    template: `<ng-content></ng-content>`
})
export class UpperComponent {
    @ContentChild(SomeComponent) content: SomeComponent;
}

If you wrap <ng-content> then you can access access to the transcluded content like

@Component({
    selector: 'upper',
    template: `
  <div #contentWrapper>
    <ng-content></ng-content>
  </div>`
})
export class UpperComponent {
    @ViewChild('contentWrapper') content: ElementRef;

    ngAfterViewInit() {
      console.debug(this.content.nativeElement);
    }
}
Kg answered 11/4, 2016 at 10:4 Comment(2)
If I wrap it I should access it using \@ViewChild, not \@ContentChild ?Lynn
@Günter Zöchbauer Very good idea!!! It helped me to refactor the Angular Material dialogNylons
R
37

You need to leverage the @ContentChild decorator for this.

@Component({
  selector: 'upper',
  template: `<ng-content></ng-content>`
})
export class UpperComponent {
  @Input 
  content: String;

  @ContentChild(...)
  element: any;
}

Edit

I investigated a bit more your issue and it's not possible to use @ContentChild here since you don't have a root inner DOM element.

You need to leverage the DOM directly. Here is a working solution:

@Component({
  selector: 'upper',
  template: `<ng-content></ng-content>`
})
export class UpperComponent {
  constructor(private elt:ElementRef, private renderer:Renderer) {
  }

  ngAfterViewInit() {
    var textNode = this.elt.nativeElement.childNodes[0];
    var textInput = textNode.nodeValue;
    this.renderer.setText(textNode, textInput.toUpperCase());
  }
}

See this plunkr for more details: https://plnkr.co/edit/KBxWOnyvLovboGWDGfat?p=preview

Rorie answered 11/4, 2016 at 10:2 Comment(1)
What if the content being transcluded isn't an another component? What if you just want all the transcluded content within ng-content?Eradis
P
2

https://angular.io/api/core/ContentChildren

class SomeDir implements AfterContentInit {

  @ContentChildren(ChildDirective) contentChildren : QueryList<ChildDirective>;

  ngAfterContentInit() {
    // contentChildren is set
  }
}

Note that if you do console.log(contentChildren), it will only work on ngAfterContentInit or a later event.

Playlet answered 28/11, 2018 at 20:7 Comment(1)
Very weird I'm doing exactly this even with forwardref and descendants: true, checking in the AfterContentInit() and it still comes back as an empty list.Wirehaired
L
2

Good morning,

I've been doing a little research on this topic because something similar has happened to me in my project. What I have discovered is that there are two decorators that can help you for this solution: ViewChildren and ContentChildren. The difference mainly according to what I have found in the network is that ViewChildren accesses the interior of the component and ContentChildren accesses the DOM or the component itself.

To access the upper element from the ng-content you must change the upper element leaving it like this:

<upper #upper>my text to transform to upper case</upper>

And then simply to access the interior (In the component that has the ng-content):

  @ViewChildren('upper') upper: QueryList<ElementRef>

And to access the component in general (In the component that has the ng-content):

  @ContentChildren('upper') upper: QueryList<ElementRef>

Where did you get the information from: https://netbasal.com/understanding-viewchildren-contentchildren-and-querylist-in-angular-896b0c689f6e

Libretto answered 24/7, 2020 at 12:12 Comment(0)
S
0

You can also use TemplateRef, which was what worked me in the end because I was using a service to initialize my component.

In your-component.component.html

<!-- Modal Component & Content -->
<ng-template #modal>
  ... modal content here ...
</ng-template>

In your-component.component.ts

@ViewChild('modal') modalContentRef!: TemplateRef<any>;
... pass your modalContentRef into your child ... 

... For me here, the service was the middleman - [Not shown] ...

In modal.component.html

<div class="modal-container">
    <!-- Content will be rendered here in the ng-container -->
    <ng-container *ngTemplateOutlet="modalContentRef">
    </ng-container>
</div>

In modal.component.ts

@Input() modalContentRef: TemplateRef<any> | null = null;

This how I ended up solving the problem with a TemplateRef, since ng-content could not be used since a service was creating the modal

Solvent answered 8/2, 2023 at 18:18 Comment(0)
G
0

Another approach, although hacky, I'll admit:

@Component({
  selector: "upper",
  template: `<span style="display:none;" #contentWrapper>
               <ng-content></ng-content>
             </span>
             <div class="some-class"> {{ myText }} </div> `,
  })
  export class UpperComponent implements      AfterContentInit { 
    public myText: string;
    @ViewChild("contentWrapper")
    public contentWrapper: ElementRef;

    constructor() {}
    public ngAfterContentChecked() {
      const newText = 
      this.contentWrapper.nativeElement.innerHTML;
      if (newText !== this.myText) {
        // do some extra stuff with the text you got
        this.myText = newText;
      }
    } 
    
  }
  

What this allows you to do is to have more complex Html contetn, styles and all.

Then use it like this:

<upper> make me upper please </upper>

Pros:

  1. You can use custom classes and more Things inside the component (I just got here to make a text trimmer and a popup on hover if the text is too long, so I needed more logic and tags)
  2. Looks "cleaner" for the consumer of the component (no extra tags or attributes.

Cons:

  1. Hacky - getting dirty to the native element with it's innerHTML
  2. Could cause trouble if passed somthing that is not plain text.
Gonfalonier answered 5/6 at 8:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.