Search code examples
javascriptangularviewchild

How do I not render ViewChild element but access it in the parent component instead?


I have a component that I'm using like this in a different component:

<inline-help>
            <help-title>{{::'lang.whatIsThis'|i18n}}</help-title>
            <help-body i18n="lang.helpBody"></help-body>
</inline-help>

In inline-help I'm using @ViewChild and ng-content to display the help-title and help-body.

@Component({
    selector: 'inline-help',
    template: `
<div class="inline-help">
    <div class="help-icon">
        <i class="en-icon-help"></i>
    </div>
    <div #helptext class="inline-help-text text-left">
<ng-content select="help-title"></ng-content>       
<ng-content select="help-body"></ng-content>
    </div>
</div>`
})

export class InlineHelpComponent implements AfterViewInit {

    @Input() fixed: boolean;
    @ViewChild('helptext', {static: false}) text: ElementRef;
......

What I want to do is create a new component like <inline-help-content> that I can use as such inside :

@Component({
    selector: 'inline-help',
    template: `
<div class="inline-help">
    <div class="help-icon">
        <i class="en-icon-help"></i>
    </div>
    <inline-help-content [helpTitle]="something" [helpBody]="something"></inline-help-content>
</div>`
})

But I DONT want to change all the instances of

<inline-help>
            <help-title>{{::'lang.whatIsThis'|i18n}}</help-title>
            <help-body i18n="lang.helpBody"></help-body>
</inline-help>

that I use in other parts of the codebase, since that's a lot. Is it possible to parse the ViewChild and then get the text inside it and call another component to with the texts as inputs?


Solution

  • In your InlineHelpComponent template, you can wrap the <ng-content>s in elements with template reference variables assigned to them:

    <div #helpTitle><ng-content select="help-title"></ng-content></div>
    <div #helpBody><ng-content select="help-body"></ng-content></div>
    

    Those template reference variables give you access to the native elements, so you can then pass the innerText properties of those elements into your new InlineHelpContentComponent (just be sure to hide the projected content in InlineHelpComponent so it doesn't display twice). Here's what the InlineHelpComponent template would look like:

    <div class="inline-help">
      <div style="display:none;">
        <div #helpTitle><ng-content select="help-title"></ng-content></div>
        <div #helpBody><ng-content select="help-body"></ng-content></div>
      </div>
      <inline-help-content
        *ngIf="showInlineHelpContent"
        [helpTitle]="helpTitle.innerText"
        [helpBody]="helpBody.innerText">
      </inline-help-content>
    </div>
    

    Here's a StackBlitz demonstrating this approach.

    I found I had to include that *ngIf="showInlineHelpContent" on the inline-help-content component, and set the flag in ngAfterContentInit after a setTimeout, otherwise I was getting an ExpressionChangedAfterItHasBeenCheckedError error.