img

Some History

In Web Components there used to be a property on components called <content> which was used inside of Shadow DOM as an insertion point. This has since been deprecated in favor of the <slot> element; see reference on MDN. They are very similar in content, both allowing for the projection of DOM into a certain point.

ng-content specification

The spec for <content> calls for the web component to actually initialize even if the content is not projected into the DOM. This is important to note because Angular follows this standard. That means when you use <ng-content> even if you *ngIf the content tag to only show when something is activated, the nested component will still initialize. The reason is <ng-content> does not actually produce content, it simply projects existing content. To learn more about this, see the Github Issue on the Angular project.

You can see an example of this in this plunkr.

Use Case

If you build something like a tab's component, something like this:

tabs.component.ts

@Component({
  selector: 'my-tabs',
  template: `
    <section>
      <ul>
        <li
          *ngFor="let tab of tabs">
          <button (click)="tabClicked(tab)">
            {{tab.title}}
          </button>
        </li>
      </ul>
      <div class="tab-content">
        <ng-content></ng-content>
      </div>
    </section>
  `
})
export class TabsComponent implements AfterContentInit {

  @ContentChildren(TabComponent) tabs: QueryList<TabComponent>;

  tabClicked(activeTab): void {
    const tabs = this.tabs.toArray();
    tabs.forEach(tab => tab.active = false);
    activeTab.active = true;
  }

}

tab.component.ts

@Component({
  selector: 'tab',
  template: `
    <div *ngIf="!active">
      <ng-content></ng-content>
    </div>
  `
})
export class TabComponent {  
  @Input() active = false;
}

You can see in this example, the <ng-content> is toggle off by *ngIf when the tab is not active. Even though its behind that *ngIf, the components nested in my tab will still initialize but not be inserted into the DOM. This can cause a few different problems:

  • Components relying on DOM calculations such as sizing/animations will not be measured correctly since there is no DOM yet.
  • In situations where you have large amounts of nested components inside <ng-content> it can be a performance issue.

Solution

Now that we understand the problem and the why, how do we fix this? Its actually pretty simple, inside your content you are projecting, you just need to add a *ngIf. So in the situation of the tabs, you could do something like:

<my-tabs>  
  <tab title="Foo" #fooTab>
    <div *ngIf="footTab.active">
      ...You Content...
    </div>
  </tab>
</my-tabs>  

Wrapping Up

While this is can be unexpected, Angular is actually following the specification for web components ( which I think is ideal ). The solution is relatively simple to implement but you need to know about it.

© 2017. All Rights Reserved.