38

Suppose i have simple Bootstrap panel component with multiple transclusion slots. Template example:

<div class="panel panel-default">
  <div class="panel-heading">
    <ng-content select="my-panel-heading"></ng-content>
  </div>
  <div class="panel-body">
    <ng-content select="my-panel-content"></ng-content>
  </div>
</div>

I want to make panel-heading optional. How do i hide <div class="panel-heading"> element, if there is no content provided for <ng-content select="my-panel-heading"></ng-content>

hendrix
  • 3,364
  • 8
  • 31
  • 46
  • `ngIf` should be preferred over `[hidden]`. Is there a reason you prefer `hidden`? – Günter Zöchbauer Aug 01 '16 at 07:40
  • 2
    @GünterZöchbauer if variable showHeading is initialized to false, element is removed from template by the time ngAfterContentInit is called, making reference to viewChild undefined. If ngIf is prefered, you could change default value for showHeading to true and it should work. – hendrix Aug 01 '16 at 07:45
  • Actually, i just tested it and it didnt work well with *ngIf even after changing default value to true. Reference to #panelHeading was still undefined cause *ngIf removed it. – hendrix Aug 01 '16 at 07:47
  • Didn't think of that. Good catch. – Günter Zöchbauer Aug 01 '16 at 07:49

3 Answers3

30

If you have a parent element of <ng-content> with a template variable (#panelHeading)

<div class="panel panel-default">
  <div class="panel-heading" #panelHeading [hidden]="!showHeading">
    <ng-content select="my-panel-heading"></ng-content>
  </div>
  <div class="panel-body">
    <ng-content select="my-panel-content"></ng-content>
  </div>
</div>

then you can query for it like

@ViewChild('panelHeading') panelHeading;

and set a property depending on whether there are children or not

constructor(private cdRef:ChangeDetectorRef) {}

showHeading:boolean = false;

ngAfterViewInit() {
  this.showHeading = this.panelHeading.nativeElement && this.panelHeading.nativeElement.children.length > 0;
  this.cdRef.detectChanges();
}

If <my-panel-heading> is an Angular2 component, then you can also use

@ContentChild(MyPanelHeading) panelHeading:MyPanelHeading;

constructor(private cdRef:ChangeDetectorRef) {}

showHeading:boolean = false;

ngAfterViewInit() {
  this.showHeading = this.panelHeading != null;
  this.cdRef.detectChanges();
}
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • By setting showHeading default value to 'false', this.panelHeading is 'undefined'. By setting showHeading default value to 'true', it works correctly but I get 'ExpressionChangedAfterItHasBeenCheckedError' in console. Moreover, I have to use 'ngAfterViewInit' instead of 'ngAfterContentInit', otherwise showHeading is always undefined. Any ideas how to solve? Thx – Luca Perico Oct 17 '17 at 08:13
  • 1
    @Luca, thanks for the feedback. I updated my answer. I think now `ngAfterViewInit` is correct. I updated a few more things that should address the issues you mentioned. – Günter Zöchbauer Oct 17 '17 at 08:18
  • thank you! For my solution I preferred the use of `*ngIf` instead of `hidden` by setting `showHeading` default value to true. Now all works correctly :) – Luca Perico Oct 17 '17 at 08:32
  • Ok, I assume that's the reason why you got `undefined`. `*ngIf` actually removes the content from the DOM, while `hidden` only hides it and `@ContentChild()` still can find it. – Günter Zöchbauer Oct 17 '17 at 08:34
  • @GünterZöchbauer, what is the purpose of the ChangeDetectorRef in this situation? – Cristiano Feb 21 '18 at 10:20
  • 2
    There are some lifecycle callbacks where it results in an exception ("The model has changed after it was checked" or similar) when the model is changed in this callback. Calling `detectChanges` fixes this exception. – Günter Zöchbauer Feb 21 '18 at 10:23
  • 1
    Ok thanks. I tested it now and received that error. And adding ChangeDetectorRef fixed it. Also, I had to set showHeading initially to true in order to make my component to work properly. – Cristiano Feb 21 '18 at 10:52
30

You'd have to remove all your spaces but you could do this with CSS if you really cared about it (ng-content doesn't take up space):

.panel-heading:empty { display: none }

<div class="panel-heading"><ng-content select="my-panel-heading"></ng-content></div>
William Neely
  • 1,923
  • 1
  • 20
  • 23
4

Do:

<div class="panel panel-default">
  <div class="panel-heading" [hidden]="!panelHeading.hasChildNodes()" #panelHeading>
    <ng-content select="my-panel-heading"></ng-content>
  </div>
  <div class="panel-body">
    <ng-content select="my-panel-content"></ng-content>
  </div>
</div>

Note you can't use *ngIf in this case, for catch-22 situation.

hendrix
  • 3,364
  • 8
  • 31
  • 46
Deb
  • 5,163
  • 7
  • 30
  • 45