2

I am working on building a Header component in Angular and want to show the navigation at different locations in the DOM structure according to whether an input inputTwoRows has been set to true/false. The nav is added to the Header's template via content projection - ng-content. I have tried to wrap 2 ng-content in ng-templates at different locations in the template and added ngIf to conditionally show them. The templates are, however, attempting to show the same projected content. As you will see in the Stackblitz link demoing the issue only the first ng-content is shown if [twoRows]="true" on c-header. Below is the code for header.component.html:

<header>
  <div>logo</div>
  <ng-template [ngIf]="inputTwoRows">
    <p>Two Rows</p>
    <ng-content select="c-header-nav"></ng-content>
  </ng-template>
  <div>utils</div>
  <ng-template [ngIf]="!inputTwoRows">
    <p>One Row</p>
    <ng-content select="c-header-nav"></ng-content>
  </ng-template>
</header>

This logic works fine if the content inside either ng-template is not ng-content. Is there a way I can achieve my original aim somehow?

https://stackblitz.com/edit/angular-ivy-xfr7hs?file=src/app/components/header/header.component.html

Thanks

James

2 Answers2

2

I ended up defining a single <ng-content> wrapped in an <ng-template>. I then placed <ng-containers> where I needed them in the DOM. The outer <ng-containers> deal with the conditional logic and the inner <ng-containers> reference the <ng-template> if required:

<header>
  <div>logo</div>
  <ng-container *ngIf="!twoRows">
    <ng-container *ngTemplateOutlet="headerNavTemplate"></ng-container>
  </ng-container>
  <div>utils</div>
  <ng-container *ngIf="twoRows">
      <ng-container *ngTemplateOutlet="headerNavTemplate"></ng-container>
  </ng-container>
  <ng-template #headerNavTemplate>
    <ng-content select="c-header-nav"></ng-content>
  </ng-template>
</header>
James Howell
  • 1,392
  • 5
  • 24
  • 42
0

It’s because ng-content happens at the build time. is instantiated when the host component is instantiated and it also means that Angular compiler creates the rule and related JS code when compiling the code. No run-time affects the behavior later. It does not change when the component is updated.

Or Shalmayev
  • 295
  • 1
  • 12
  • Ok thanks, so what can I do to solve the issue? – decodedcreative Jul 27 '22 at 19:21
  • You can use ngProjectAs="selector-name" or give the component a special identifier. the problem was that you named your ng-content selectors with the same name so the first ng-content that was instantiated took place. following is a link to solve the problem: https://stackblitz.com/edit/angular-ivy-zyl15t?file=src%2Fapp%2Fcomponents%2Fheader%2Fheader.component.html,src%2Fapp%2Fapp.component.html,src%2Fapp%2Fapp.component.ts,src%2Fapp%2Fcomponents%2Fheader%2Fheader.component.ts,src%2Fapp%2Fapp.component.css – Or Shalmayev Jul 27 '22 at 19:33
  • Thanks for the help but I don't want multiple instances of the Header Nav component in the application. The component I am building is for an Angular component library and I don't want users to have to define multiple navigations just to get around this issue. I've managed to fix the issue now. Will post up my solution shortly. – decodedcreative Jul 27 '22 at 19:49
  • You can try to do something like portal: https://stackblitz.com/edit/angular-ivy-ysx6kr?file=src/app/app.component.html – Or Shalmayev Jul 27 '22 at 19:54