Angular Input and Output decorators are very important while establishing a relationship between parent and child components in our applications.

While developing our project, sometimes our components may become large and hard to read. So, it is always a good choice to split that big component into a few smaller ones. Furthermore, smaller components can be reused into other components, therefore creating the parent-child relationship is a very good idea. The child component depends on the parent component, and because of that, they make one coherent part.

Creating child components by using Angular Input and Output decorators is going to be our goal in this post.

For the complete navigation and all the basic instructions of the Angular series, check out: Introduction of the Angular series.

To download the source code for this article, you can visit our GitHub repository.

Let’s start

Overview

In the situations where we want to send some content from a parent to a child component, we need to use the @Input decorator in a child component to provide a property binding between those components. Moreover, we can have some events in a child component that reflect its behavior back to a parent component. For that purpose, we are going to use @Output decorator with the EventEmitter.

To handle success messages and error messages (other than 500 or 404 messages), we are going to create a modal window child components. We are going to reuse them in every component that requires displaying these types of messages. When we want to register our reusable component it is a good practice to create a shared module and to register and export our components inside that module. Then, we can use those reusable components in any higher-level component we want by registering the shared module inside a module responsible for that higher-order component.

Split OwnerDetails with Angular Input and Output Decorators

Currently, we have our OwnerDetails component that shows both the owner and accounts data on the page. We can split that up thus making our component cleaner and easier to maintain.

To do that, let’s start with the creation of a new component:

ng g component owner/owner-details/owner-accounts --skip-tests

We place the files from this component inside the owner-details folder because we are only going to use it to extract some content from the OwnerDetails component.

 Now, let’s modify the owner-accounts.component.ts file:

import { Component, Input, OnInit } from '@angular/core';

import { Account } from './../../../_interfaces/account.model';

@Component({
  selector: 'app-owner-accounts',
  templateUrl: './owner-accounts.component.html',
  styleUrls: ['./owner-accounts.component.css']
})
export class OwnerAccountsComponent implements OnInit {
  @Input() accounts: Account[];

  constructor() { }

  ngOnInit(): void {
  }

}

We add a single Account array property and decorate it with the @Input decorator.

Next, we are going to cut the table that shows accounts from the owner-details.component.html file and replace it with the selector from the OwnerAccountsComponent:

//previous code
  
<ng-template #advancedUser>
    <div class="row">
      <div class="col-md-3">
        <strong>Type of user:</strong>
      </div>
      <div class="col-md-3">
        <span class="text-info">Advanced user.</span>
      </div>
    </div>
  </ng-template>
</div>

<app-owner-accounts [accounts]="owner?.accounts"></app-owner-accounts>

Here, we use the app-owner-accounts selector to inject the child component into the parent component. Also, we use the property binding to pass all the accounts from the owner object to the @Input accounts property in the child component.

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

After that, we can paste the code we cut from the parent component inside the owner-details.component.html file:

<div class="row">
  <div class="col-md-12">
    <div class="table-responsive">
      <table class="table table-striped">
        <thead>
          <tr>
            <th>Account type</th>
            <th>Date created</th>
          </tr>
        </thead>
        <tbody>
          <tr *ngFor="let account of accounts">
            <td>{{account?.accountType}}</td>
            <td>{{account?.dateCreated | date: 'dd/MM/yyyy'}}</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</div>

We can also notice that we are not using the owner?.accounts anymore inside the *ngFor directive but just the accounts property.

With that in place, we can start our app and navigate to the OwnerDetails component, and we are going to see the same result as before. Just this time, we’ve split the logic into two components.

Emmit Events from Child Component Using @Output Decorator

Let’s use the simplest example to show how we can emit events from the child component to the parent component.

To start, we are going to add some modifications inside OwnerAccountsComponent:

import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';

import { Account } from './../../../_interfaces/account.model';

@Component({
  selector: 'app-owner-accounts',
  templateUrl: './owner-accounts.component.html',
  styleUrls: ['./owner-accounts.component.css']
})
export class OwnerAccountsComponent implements OnInit {
  @Input() accounts: Account[];
  @Output() onAccountClick: EventEmitter<Account> = new EventEmitter();

  constructor() { }

  ngOnInit(): void {
  }

  onAccountClicked = (account: Account) => {
    this.onAccountClick.emit(account);
  }

}

We use the @Ouput decorator with the EventEmitter to emit an event from a child to a parent component. Also, by providing a type for the EventEmitter, we state that it can emit only the Account data type. Furthermore, we create a new onAccountClicked function, where we accept an account and call the emit function to emit this event to the parent component.

To be able to call this onAccountClicked function, we have to modify the owner-accounts.component.html file:

<tbody>
  <tr *ngFor="let account of accounts" (click)="onAccountClicked(account)">
    <td>{{account?.accountType}}</td>
    <td>{{account?.dateCreated | date: 'dd/MM/yyyy'}}</td>
  </tr>
</tbody>

We just add the click event to the row inside the table.

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

Finally, in order to have a pointer cursor when we hover over our account rows, we are going to modify the owner-accounts.component.css file:

tbody tr:hover{
  cursor: pointer;
}

For our emitter to work, we have to subscribe to it from our parent component.

The first thing we are going to do is to add a single function inside the owner-details.component.ts file:

printToConsole= (param: Account) => {
  console.log('Account parameter from the child component', param)
}

And then, we are just going to connect the dots in the owner-details.component.html file:

<app-owner-accounts [accounts]="owner?.accounts" 
  (onAccountClick)="printToConsole($event)"></app-owner-accounts>

Here, we create the event with the same name as our @Output property in the child component and assign a local printToConsole function to it. Also, we use $event as a parameter to accept the emitted object from the child component.

At this point, we can start our app, and once we navigate to the OwnerDetails component (by clicking the Details button), we can see that as soon as we click on any account, we are going to log a message in a console.

Creating the Shared Module

As we said, for the shared components, it is always a good practice to create a shared module. So, let’s start by creating that shared module in the shared folder:

ng g module shared --module owner 

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

This command will also include the SharedModule inside the owner.module.ts file:

CREATE src/app/shared/shared.module.ts (192 bytes)
UPDATE src/app/owner/owner.module.ts (556 bytes)

Then let’s modify the shared.module.ts file:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

@NgModule({
  declarations: [],
  imports: [
    CommonModule
  ], 
  exports: []
})
export class SharedModule { }

Error Modal Component

Let’s execute AngularCLI command to create an error modal component:

ng g component shared/modals/error-modal --skip-tests

Besides creating component files, this command is going to import a new component into the shared module. But we need to export it manually:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ModalModule } from 'ngx-bootstrap/modal';

import { ErrorModalComponent } from './modals/error-modal/error-modal.component';

@NgModule({
  declarations: [
    ErrorModalComponent
  ],
  imports: [
    CommonModule,
    ModalModule.forRoot()
  ], 
  exports: [
    ErrorModalComponent
  ]
})
export class SharedModule { }

In the exports array, we export our component. Additionally, since we are going to use the modal component from ngx-bootstrap, we inject it inside the imports array using the forRoot function.

Now, let’s modify the error-modal.component.ts file:

import { Component, OnInit } from '@angular/core';
import { BsModalRef } from 'ngx-bootstrap/modal';

@Component({
  selector: 'app-error-modal',
  templateUrl: './error-modal.component.html',
  styleUrls: ['./error-modal.component.css']
})
export class ErrorModalComponent implements OnInit {
  modalHeaderText: string;
  modalBodyText: string;
  okButtonText: string;

  constructor(public bsModalRef: BsModalRef) { }

  ngOnInit(): void {
  }

}

Here we have three properties that we are going to use in the HTML part of this component. Also, we inject the BsModalRef class inside the constructor. We will use it in the HTML file as well, thus making it public.

Let’s continue by modifying the error.modal.component.html file:

<div class="modal-header">
  <h4 class="modal-title pull-left">{{modalHeaderText}}</h4>
  <button type="button" class="btn-close close pull-right" 
  aria-label="Close" (click)="bsModalRef.hide()">
    <span aria-hidden="true" class="visually-hidden">&times;</span>
  </button>
</div>
<div class="modal-body">
  {{modalBodyText}}
</div>
<div class="modal-footer">
  <button type="button" class="btn btn-danger" (click)="bsModalRef.hide()">{{okButtonText}}</button>
</div>

Just a simple HTML file that uses string interpolation to add the text for the header, body, and button. Additionally, we can see how we use bsModalRef, which we injected inside the constructor, to call the hide function, to hide the modal.

Creating Success Modal Component

To continue let’s create a success modal in the same way as we did with the error modal:

ng g component shared/modals/success-modal --skip-tests

We have to export the success-modal component from the shared.module.ts file:

exports: [
  ErrorModalComponent,
  SuccessModalComponent
]

Then, let’s modify our success-modal.component.ts file:

import { BsModalRef } from 'ngx-bootstrap/modal';
import { Component, EventEmitter, OnInit } from '@angular/core';

@Component({
  selector: 'app-success-modal',
  templateUrl: './success-modal.component.html',
  styleUrls: ['./success-modal.component.css']
})
export class SuccessModalComponent implements OnInit {
  modalHeaderText: string;
  modalBodyText: string;
  okButtonText: string;
  redirectOnOk: EventEmitter<any> = new EventEmitter();

  constructor(private bsModalRef: BsModalRef) { }

  ngOnInit(): void {
  }

  onOkClicked = () => {
    this.redirectOnOk.emit();
    this.bsModalRef.hide();
  }

}

We are going to use the success component once we execute the create, update, or delete actions successfully, and by pressing the OK button we are going to redirect a user to the owner-list component. That’s why we use that EventEmitter class. Also, this time we use the private bsModalRef, because we use it only inside the .ts file.

Finally, let’s modify the success-modal.component.html file:

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<
<div class="modal-header">
  <h4 class="modal-title pull-left">{{modalHeaderText}}</h4>
  <button type="button" class="btn-close close pull-right" 
  aria-label="Close" (click)="onOkClicked()">
    <span aria-hidden="true" class="visually-hidden">&times;</span>
  </button>
</div>
<div class="modal-body">
  {{modalBodyText}}
</div>
<div class="modal-footer">
  <button type="button" class="btn btn-success" (click)="onOkClicked()">{{okButtonText}}</button>
</div>

We are going to use both of these modal components in the next article.

Directives

In the last part of this article, we are going to show you how to use directives to add additional behavior to elements in our Angular application.

So, let’s create the Append directive in the shared folder:

ng g directive shared/directives/append --skip-tests

This will create a new file with the @Directive decorator and the [appAppend] selector. We are going to use that selector to add the additional behavior to elements inside the OwnerDetails component:

<div class="row" *ngIf='owner?.accounts.length <= 2; else advancedUser'>
  <div class="col-md-3">
    <strong>Type of user:</strong>
  </div>
  <div class="col-md-3">
    <span [appAppend]="owner" class="text-success">Beginner user.</span>
  </div>
</div>
<ng-template #advancedUser>
  <div class="row">
    <div class="col-md-3">
      <strong>Type of user:</strong>
    </div>
    <div class="col-md-3">
      <span [appAppend]="owner" class="text-info">Advanced user.</span>
    </div>
  </div>
</ng-template>

As you can see, we add the [appAppend] selector inside both span elements and also, we pass the owner object as a parameter to our directive.

Now, we have to modify the directive file:

import { Directive, ElementRef, Input, OnChanges, Renderer2, SimpleChanges } from '@angular/core';
import { Owner } from '../../_interfaces/owner.model';

@Directive({
  selector: '[appAppend]'
})
export class AppendDirective implements OnChanges {
  @Input('appAppend') ownerParam: Owner;

  constructor(private element: ElementRef, private renderer: Renderer2) { }

  ngOnChanges(changes: SimpleChanges) {
    if(changes.ownerParam.currentValue){
      const accNum = changes.ownerParam.currentValue.accounts.length;
      const span = this.renderer.createElement('span');
      const text = this.renderer.createText(` (${accNum}) accounts`);

      this.renderer.appendChild(span, text);
      this.renderer.appendChild(this.element.nativeElement, span);
    }
  }
}

Here we have the ownerParam property that we decorate with the @Input decorator. We do this in order to accept the parameter from the parent component. Then, inside the constructor, we inject ElementRef and Renderer2 classes. We use the ElementRef class to reference an element we applied our directive to. Additionally, we use Renderer2 to manipulate elements without touching the DOM directly. So, as you can see, no need for JQuery to manipulate the DOM elements – Agular provides us with all the tools we need.

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

After the constructor modification, we add a new  ngOnChanges lifecycle method. This method is called before the ngOnInit lifecycle hook, and also every time one or more input properties change. Inside it, we check if our input property has the currentValue populated. If it does, we extract the number of accounts, use renderer to create new span, and text elements, and combine them together. 

Finally, we use the same renderer property to combine our nativeElement with the newly created span element.

Now we can start our apps, and once we navigate to the details page of any user, we will see our directive in action:

Directive usage to add number of accounts

We see that we have new information next to the user’s description. For different users, we will get different account numbers.

 

Conclusion

There we go. In this article, we’ve learned what is the purpose of Angular Input-Output decorators and how to share information between parent and child components. Also, we’ve talked about the shared module and using directives to add additional behavior to the elements of the Angular app.

In the next part of the series, we are going to start with the creation of the owner and form validation.

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<