Support us .Net Basics C# SQL ASP.NET Aarvi MVC Slides C# Programs Subscribe Download

Angular content projection

Suggested Videos
Part 59 - Edit form in angular | Text | Slides
Part 60 - Angular delete form | Text | Slides
Part 61 - Angular accordion example | Text | Slides

In this video we will discuss content projection in Angular with an example. This is continuation to Part 61. Please watch Part 61 from Angular CRUD tutorial before proceeding.


Content projection helps us create reusable components. In Angular 1, this is called transclusion. Let us understand content projection, with an example. We want to create a reusable accordion type of control. The panel heading must be clickable, and when clicked the panel body and footer must be collapsed. Clicking on the panel heading again, must expand the collapsed panel body and footer. 


Another important requirement is, this accordion panel must be reusable with any other component in our application. The component that uses this accordion panel component, must be able to specify what content it wants in the accordion panel body and footer.

angular 2 ng-content select multiple

For example, if we use this accordion panel, with a ProductComponent that displays a product, then in the accordion panel body, the ProductComponent may want to project and display product image, price, weight etc. In the footer, the ProductComponent may want to project and display buttons to customise the product or buy. 

In our case we want to use this accordion panel, with DisplayEmployeeComponent. So in the panel body we want to project and display, employee photo, gender, date of birth, email etc. In the footer, we want to project and display buttons to View, Edit and Delete employee as shown below.

angular content projection

So the important question that we need to answer is, how will the components that use this accordion component be able to inject variable content into the acoordion panel body and footer. 
By using <ng-content> tag

As you can see in the image below, you can think of this <ng-content> as a place holder for the variable content. In a bit we will understand, how a component that uses this accordion component can project variable content depending on the requirements of your application. 

angular 2 transclusion example

First, let's create our reusable accordion component. This is a reusable component and can be used by another component in our application. So, let's place this component in the "Shared" folder. Use the following Angular CLI command to create the component.
ng g c shared/accordion --flat

accordion.component.ts : Notice, in the component class we have introduced 3 properties. The code is commented and self-explanatory.

export class AccordionComponent implements OnInit {
  // We use this property to set a different CSS class on the employee
  // panel if we have just viewed his details
  @Input() hasJustViewed: boolean;
  // Sets the panel title, in our case the name of the employee
  @Input() title: string;
  // Controls hiding and showing panel body and footer
  @Input() isHidden = false;

  constructor() { }

  ngOnInit() {
  }
}

accordion.component.css : The "pointerCursor" class makes the cursor a pointer when hovered over panel title, so the end user knows, it is clickable.

.pointerCursor {
    cursor: pointer;
}

accordion.component.html : As you can see, we have defined the shell for the accordion panel i.e accordion panel header, body and footer. We also have encapsulated the logic in this component to show and hide the panel body and footer. But the content that goes in the panel body and footer will be decided by the component that consumes this accordion component. The consuming component will also need to bind and pass data for the 3 input properties (title, isHidden and hasJustViewed).

<!-- Add panel-success class only if hasJustViewed property is true -->
<div class="panel panel-primary" [class.panel-success]="hasJustViewed">
    <!-- pointerCursor class changes the cursor style to pointer when hovered
             over the employee panel title. When clicked on the title, isHidden
             boolean property is toggled from true to false & vice-versa. We use
             this property to toggle the visibility of panel body & footer -->
    <div class="panel-heading pointerCursor" (click)="isHidden = !isHidden">
        <h3 class="panel-title">{{title | uppercase}}</h3>
    </div>
    <div class="panel-body" [hidden]="isHidden">
        <!-- ng-content specifies the slot into which the content will be projected
               by the component that consumes this accordion component -->
        <ng-content select=".myPanelBody"></ng-content>
    </div>
    <div class="panel-footer" [hidden]="isHidden">
        <!-- Another slot into which the content can be projected. Since we have more
               than one slot into which the content can be projected, this is called
               multi-slot content projection-->
        <ng-content select=".myPanelFooter"></ng-content>
    </div>
</div>

Changes in display-employee.component.html : The changes are commented and self-explanatory.

<!-- Pass employee name as the value for title input property. Also set
     isHidden input propety to false if you want the panel body and footer
     to be collapsed on the initial page load.-->
<app-accordion [title]="employee.name" [isHidden]="true"
[hasJustViewed]="selectedEmployeeId === employee.id">
<!-- Notice myPanelBody css class is present on this <div>. This CCS class is
used as the selector on the <ng-content> tag in accordion component. So all this
content in this DIV will be projected at the location where we have <ng-content>
tag with css class selector .myPanelBody -->
<div class="col-xs-10 myPanelBody">
  <div class="row vertical-align">
    <div class="col-xs-4">
      <img class="imageClass" [src]="employee.photoPath" />
    </div>
    <div class="col-xs-8">

      <div class="row">
        <div class="col-xs-6">
          Gender
        </div>
        <div class="col-xs-6">
          : {{employee.gender}}
        </div>
      </div>
      <div class="row">
        <div class="col-xs-6">
          Date of Birth
        </div>
        <div class="col-xs-6">
          : {{employee.dateOfBirth | date}}
        </div>
      </div>
      <div class="row">
        <div class="col-xs-6">
          Contact Preference
        </div>
        <div class="col-xs-6">
          : {{employee.contactPreference}}
        </div>
      </div>
      <div class="row">
        <div class="col-xs-6">
          Phone
        </div>
        <div class="col-xs-6">
          : {{employee.phoneNumber}}
        </div>
      </div>
      <div class="row">
        <div class="col-xs-6">
          Email
        </div>
        <div class="col-xs-6">
          : {{employee.email}}
        </div>
      </div>
      <div class="row">
        <div class="col-xs-6">
          Department
        </div>
        <div class="col-xs-6" [ngSwitch]="employee.department">
          :
          <span *ngSwitchCase="1">Help Desk</span>
          <span *ngSwitchCase="2">HR</span>
          <span *ngSwitchCase="3">IT</span>
          <span *ngSwitchCase="4">Payroll</span>
          <span *ngSwitchDefault>N/A</span>
        </div>
      </div>
      <div class="row">
        <div class="col-xs-6">
          Is Active
        </div>
        <div class="col-xs-6">
          : {{employee.isActive}}
        </div>
      </div>

    </div>
  </div>
</div>
<!-- The content in the following DIV will be projected at the location
where we have <ng-content> tag with css selector .myPanelFooter -->
<div class="myPanelFooter">
  <button class="btn btn-primary" (click)="viewEmployee()">View</button>
  <button class="btn btn-primary" (click)="editEmployee()">Edit</button>
  <span *ngIf="confirmDelete">
    <span>Are you sure you want to delete ?</span>
    <button class="btn btn-danger" (click)="deleteEmployee()">Yes</button>
    <button class="btn btn-primary" (click)="confirmDelete=false">No</button>
  </span>
  <span *ngIf="!confirmDelete">
    <button class="btn btn-danger" (click)="confirmDelete=true">Delete</button>
  </span>
</div>
</app-accordion>

At the moment we are using class selector to match the projection content with the ng-content slot. We can use any of the CSS selectors (class selector, element selector, attribute selector etc)

angular crud tutorial

Angular content projection - Slides






Angular accordion example

Suggested Videos
Part 58 - Passing data between components in angular | Text | Slides
Part 59 - Edit form in angular | Text | Slides
Part 60 - Angular delete form | Text | Slides

In this video we will discuss 
  • How to implement simple accordion type of functionality in Angular
  • Difference between ngIf directive and hidden property in Angular 2 and later versions

Implementing accordion type of functionality in Angular 2 and later versions : When the panel title is clicked, the panel body and the footer must be collapsed. Clicking on the panel title again, must expand the collapsed panel body and footer.


Angular accordion example

There are several ways to do this. One way is to implement this accordion functionality in the component itself where we need it. For example, in our case the display logic for the employee is present in DisplayEmployeeComponent. So we include accordion functionality also in the DisplayEmployeeComponent. 

The benefit of this approach is, it is very easy to implement. The downside is, we cannot reuse this accordion functionality in another component if we need it there. We have to re-implement this same functionality again in that component. 

In our next video we will discuss how to extract common accordion functionality into a separate component using content projection, so it can be reused anywhere in the application where we need that accordion type of functionality.

In this video let's implement accordion functionality in the DisplayEmployeeComponent itself.

Changes in display-employee.component.ts : Include the following panelExpanded boolean property in the component class. Notice we have initialised it to true, so the employee panel is expanded by default when the page first loads.

panelExpanded = true;

Changes in display-employee.component.css : Include the following "pointerCursor" class to make the cursor, a pointer when hovered over panel title, so the end user knows, it is clickable.

.pointerCursor {
    cursor: pointer;
}

Changes in display-employee.component.html : To show and hide panel body and footer, we are using ngIf structural directive. ngIf removes the element from the DOM completely when the condition is false and adds the element back once the condition becomes true. So every time, we click the panel title, the panel body and footer are either added to the DOM or removed from the DOM depending on whether the condition is true or false. 

<div class="panel panel-primary"
     [class.panel-success]="selectedEmployeeId === employee.id">
    <!-- pointerCursor class changes the cursor style to pointer when hovered over 
          the employee panel title. When clicked on the title, the panelExpanded 
          boolean property is toggled from true to false & vice-versa. We use this 
          property to toggle the visibility of panel body & footer -->
    <div class="panel-heading pointerCursor"
                        (click)="panelExpanded = !panelExpanded">
        <h3 class="panel-title">{{employee.name | uppercase}}</h3>
    </div>
    <!-- Render panel-body <div> only if panelExpanded property is true  -->
    <div class="panel-body" *ngIf="panelExpanded">
        <!-- Rest of the HTML -->
    </div>
    <!-- Render panel-footer <div> only if panelExpanded property is true  -->
    <div class="panel-footer" *ngIf="panelExpanded">
        <!-- Rest of the HTML -->
    </div>
</div>

In our case, the user of the application may toggle the visibility several times. So from a performance standpoint, it is better to show and hide the panel body and footer, rather than removing from the DOM and adding them back again when the condition is true. To show and hide we use the hidden property as shown below.

<div class="panel-body" [hidden]="!panelExpanded">
    <!-- Rest of the HTML -->
</div>
<div class="panel-footer" [hidden]="!panelExpanded">
    <!-- Rest of the HTML -->
</div>

ngIf vs hidden in Angular
  • ngIf adds or removes element from the DOM where as hidden property hides and shows the element by adding and removing display: none style. 
  • If you frequently toggle the visibility of an element, it is better to use hidden property from a performance standpoint
  • If you know you will not need to show an element, then ngIf is better. For example, you are logged in as a NON-Administrator and there is a report component on the page that should be displayed only to the Administrators. Since you are logged in as a NON-Administrator, using ngIf to hide the report component is better from  a performance standpoint. Since ngIf does not add the element to the DOM, it also does not execute the code associated with that report component. If you use hidden property instead, the report component will be constructed, all it's associated code is executed, the component is added to the DOM, and to keep it hidden from the non-administrator it uses display:none style. So in short, if you frequently toggle the visibility of an element, it is better to use hidden property. On the other hand, if you know the element will remain hidden and the user does not have the ability to toggle the visibility, then use ngIf structural directive.
angular crud tutorial