DEV Community

KrishnaSai Polanki
KrishnaSai Polanki

Posted on

Component Inheritance in Angular

Sometimes we might get a requirement like we need to create a component such that initially, we need to show few details, and then if the user is interested to read more then once the user clicks we might need to show complete details.

let's take an example, consider we need to show employee details with just name and role and we have a button to show more details about the employee. In this case in both the scenarios the data source will be the same, only the data representation is different. In this case, we can leverage the inheritance concept in angular without duplicating the code which is related to the pulling of data into those components.

Demo

Step1:

Create a base employee component.

import { Component, OnInit, Input } from '@angular/core';
import { Employee } from 'src/app/shared/api/employee';

@Component({
  selector: 'app-employee-base',
  template: ``,
  styleUrls: ['./employee-base.component.scss'],
})
export class EmployeeBaseComponent implements OnInit {
  @Input() employeeInfo: Employee;

  ngOnInit() {}

  constructor() {}
}
Enter fullscreen mode Exit fullscreen mode

Notice here that this template property is empty since we don't really use this component to render any data.

Step2:

Create a minified version of the employee details component to display minimum details like name and job role.

import { Component, OnInit } from '@angular/core';
import { EmployeeBaseComponent } from '../employee-base/employee-base.component';

@Component({
  selector: 'app-employee-details-mini',
  templateUrl: './employee-details-mini.component.html',
  styleUrls: ['./employee-details-mini.component.scss'],
})
export class EmployeeDetailsMiniComponent extends EmployeeBaseComponent {
  constructor() {
    super();
  }
}
Enter fullscreen mode Exit fullscreen mode

There are two things to note here

  1. We are extending the EmployeeDetailsMiniComponent from EmployeeBaseComponent
  2. In the constructor, we are calling the super() which is required when we are inheriting from the base component

corresponding HTML code for this component will look like below

<div>
  <span>
    First Name :
  </span>
  <span>{{ employeeInfo.firstName }}</span>
</div>
<div>
  <span>
    JobTitle :
  </span>
  <span>{{ employeeInfo.jobTitle }}</span>
</div>
Enter fullscreen mode Exit fullscreen mode

So here we can see the property employeeInfo is not part of the EmployeeDetailsMiniComponent but it is part of the EmployeeBaseComponent. So we no need to write additional code to assign data this variable that is the responsibility of EmployeeBaseComponent, we can just use that property to render as per the child component scope. Here we are just using the firstName and jobTitle properties.

Step3:

Now let's create a details component where we will display all the employee details.

import { Component, OnInit } from '@angular/core';
import { EmployeeBaseComponent } from '../employee-base/employee-base.component';

@Component({
  selector: 'app-employee-details',
  templateUrl: './employee-details.component.html',
  styleUrls: ['./employee-details.component.scss'],
})
export class EmployeeDetailsComponent extends EmployeeBaseComponent implements OnInit {
  displayedColumns: string[] = ['id', 'firstName', 'lastName', 'jobTitle'];
  dataSource: any[] = [];
  constructor() {
    super();
  }

  ngOnInit() {
    this.dataSource.push(this.employeeInfo);
  }
}
Enter fullscreen mode Exit fullscreen mode

The corresponding HTML code will look like below

<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
  <ng-container matColumnDef="id">
    <th mat-header-cell *matHeaderCellDef>No.</th>
    <td mat-cell *matCellDef="let element">{{ element.id }}</td>
  </ng-container>

  <ng-container matColumnDef="firstName">
    <th mat-header-cell *matHeaderCellDef>FirstName</th>
    <td mat-cell *matCellDef="let element">{{ element.firstName }}</td>
  </ng-container>

  <ng-container matColumnDef="lastName">
    <th mat-header-cell *matHeaderCellDef>LastName</th>
    <td mat-cell *matCellDef="let element">{{ element.lastName }}</td>
  </ng-container>

  <ng-container matColumnDef="jobTitle">
    <th mat-header-cell *matHeaderCellDef>JobTitle</th>
    <td mat-cell *matCellDef="let element">{{ element.jobTitle }}</td>
  </ng-container>

  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
Enter fullscreen mode Exit fullscreen mode

Here we can notice that the data source is the same for both the mini and normal versions of the employee details component. The only thing is in the mini version we are just displaying less information, but in the normal component, we are displaying the complete details.

To make it compatible with matTable data source we are transforming accordingly, but the actual data source is still the same which is employeeInfo which is the property of EmployeeBaseComponent.

Step4:
Now let's see how we can consume these components. To do that we will create another component called employee list component where it will display all the employees and by default, it will display in minified version of employee details.

import { Component, OnInit, Input } from '@angular/core';
import { Employee, EmployeeService } from 'src/app/shared/api/employee';

@Component({
  selector: 'app-employee-list',
  templateUrl: './employee-list.component.html',
  styleUrls: ['./employee-list.component.scss'],
})
export class EmployeeListComponent implements OnInit {
  employeeList: Employee[];
  constructor(private employeeService: EmployeeService) {}

  ngOnInit() {
    this.employeeList = this.employeeService.getEmployees();
  }
}
Enter fullscreen mode Exit fullscreen mode

Corresponding HTML code will look like below

<mat-card class="example-card" *ngFor="let employee of employeeList">
  <mat-card-content>
    <app-employee-details-mini [employeeInfo]="employee" *ngIf="!employee.showFullInfo"></app-employee-details-mini>
    <app-employee-details [employeeInfo]="employee" *ngIf="employee.showFullInfo"></app-employee-details>
  </mat-card-content>
  <mat-card-actions>
    <button mat-button (click)="employee.showFullInfo = !employee.showFullInfo">
      {{ employee.showFullInfo ? 'Show Less' : 'Show More' }}
    </button>
  </mat-card-actions>
</mat-card>
Enter fullscreen mode Exit fullscreen mode

So here we can see two components app-employee-details-mini and app-employee-details. Initially, we will display a mini version of employee details and if the user is interested by clicking Show More button we will display the complete details. But the data source for both the components is the same.

Alt Text

Alt Text

Few things need to consider in Inheritance.

  • Life Cycle hooks are not inherited from the base component. In order to call the base component life hooks, we need to call something like below
  ngOnInit() {
    super.ngOnInit();
  }
Enter fullscreen mode Exit fullscreen mode
  • Properties and methods on base class will be inherited based on the access level. Meaning if the properties/methods are private the child components cannot be inherited
  • MetaData and Decorator will not be inherited from the parent component to child components. However, there is an exception to @Input() and @Output() decorators which can be inherited into the child component.
  • Dependency injection is tricky in Inheritance. If we might need to provide the reference of the instance from child component and the parent component must make the property as public in the constructor. Below is a simple example to give more ideas.

Parent Component

@Component({
  selector: 'app-employee-base',
  template: ``,
  styleUrls: ['./employee-base.component.scss'],
})
export class EmployeeBaseComponent {
  employeeInfo: Employee;
  constructor(public employeeService: EmployeeService) {
    this.employeeInfo = this.employeeService.getEmployees();
  }
}
Enter fullscreen mode Exit fullscreen mode

Child Component

@Component({
  selector: 'app-employee-details-mini',
  templateUrl: './employee-details-mini.component.html',
  styleUrls: ['./employee-details-mini.component.scss'],
})
export class EmployeeDetailsMiniComponent extends EmployeeBaseComponent {
  constructor(public employeeService: EmployeeService) {
    super(employeeService);
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
pix303 profile image
Paolo Carraro • Edited

I'm try now and as far i can see (with my surprise...) super.ngOnInit() is not necessary. Both concrete and base component run the method. Base class implements OnInit, concrete one extends only. Maybe because of ngOnInit is async?

Collapse
 
pix303 profile image
Paolo Carraro

I confirm: async makes onNgInit run anyway for base and concrete class.