Daniel Kobler
Daniel Kobler

Reputation: 533

angular 2 access ng-content within component

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.

Upvotes: 53

Views: 63755

Answers (6)

Chen Peleg
Chen Peleg

Reputation: 2034

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.

Upvotes: 0

ObjectJosh
ObjectJosh

Reputation: 641

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

Upvotes: 1

IvanAllue
IvanAllue

Reputation: 545

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

Upvotes: 2

alansiqueira27
alansiqueira27

Reputation: 8506

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.

Upvotes: 2

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 657118

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);
    }
}

Upvotes: 59

Thierry Templier
Thierry Templier

Reputation: 202148

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

Upvotes: 37

Related Questions