Support us .Net Basics C# SQL ASP.NET ADO.NET MVC Slides C# Programs Subscribe Buy DVD

Angular component output properties

Suggested Videos
Part 19 - Angular custom pipe | Text | Slides
Part 20 - Angular 2 container and nested components | Text | Slides
Part 21 - Angular component input properties | Text | Slides

In this video we will discuss 
  • How to pass user actions or user entered values or selections from the child component to the parent component using output properties.
  • Along the way we will discuss creating custom events using angular EventEmitter class
  • Finally what is ng-container directive and it's use


This is continuation to Part 21, so please watch Part 21 from Angular 2 tutorial before proceeding. We will be working with the same example, we started in Part 20.

angular2 eventemitter between components



At the moment when we click the radio buttons, nothing happens. Here is what we want to do.

User Action What should happen
All(6) radio button is clicked Display all the employees in the table
Male(4) radio button is clicked Display the 4 Male employees in the table
Female(2) radio button is clicked Display the 2 Female employees in the table

To achieve this we are going to make use of component output properties. First let's look at the changes required in the nested component i.e EmployeeCountComponent.

The changes required in employeeCount.component.ts are commented and self-explanatory

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

@Component({
    selector: 'employee-count',
    templateUrl: 'app/employee/employeeCount.component.html',
    styleUrls: ['app/employee/employeeCount.component.css']
})
export class EmployeeCountComponent {
    @Input()
    all: number;

    @Input()
    male: number;

    @Input()
    female: number;

    // Holds the selected value of the radio button
    selectedRadioButtonValue: string = 'All';

    // The Output decorator makes the property an Output property
    // EventEmitter class is used to create the custom event
    // When the radio button selection changes, the selected
    // radio button value which is a string gets passed to the
    // event handler method. Hence, the event payload is string.
    @Output()
    countRadioButtonSelectionChanged: EventEmitter<string> =
                                        new EventEmitter<string>();

    // This method raises the custom event. We will bind this
    // method to the change event of all the 3 radio buttons
    onRadioButtonSelectionChange() {
        this.countRadioButtonSelectionChanged
            .emit(this.selectedRadioButtonValue);
    }
}

The following are the changes required in the view template of EmployeeCountComponent i.e employeeCount.component.html. Notice we have made 3 changes on each radio button
  1. value attribute is set to (All, Male or Female)
  2. Implemented 2 way data-binding using the ngModel directive. Notice ngModel is bound to selectedRadioButtonValue property in the component class. This 2 way data-binding ensures whenever the radio button selection changes, the selectedRadioButtonValue property is updated with the value of the selected radio button.
  3. onRadioButtonSelectionChange() method is binded to "change" event of the radio button. So this means whenever, the selection of the radio button changes, onRadioButtonSelectionChange() method raises the custom event "countRadioButtonSelectionChanged". We defined this custom event using Angular EventEmitter class.
<span class="radioClass">Show : </span>

<input name='options' type='radio' value="All"
       [(ngModel)]="selectedRadioButtonValue"
       (change)="onRadioButtonSelectionChange()">
<span class="radioClass">{{'All(' + all + ')'}}</span>

<input name="options" type="radio" value="Male"
       [(ngModel)]="selectedRadioButtonValue"
       (change)="onRadioButtonSelectionChange()">
<span class="radioClass">{{"Male(" + male + ")"}}</span>

<input name="options" type="radio" value="Female"
       [(ngModel)]="selectedRadioButtonValue"
       (change)="onRadioButtonSelectionChange()">

<span class="radioClass">{{"Female(" + female + ")"}}</span>

Now let's look at the changes required in the parent component i.e EmployeeListComponent

The following are the changes required in the EmployeeListComponent class i.e employeeList.component.ts. The changes are commented and self-explanatory

import { Component } from '@angular/core';

@Component({
    selector: 'list-employee',
    templateUrl: 'app/employee/employeeList.component.html',
    styleUrls: ['app/employee/employeeList.component.css']
})

export class EmployeeListComponent {
    employees: any[];

    // This property keeps track of which radio button is selected
    // We have set the default value to All, so all the employees
    // are displayed in the table by default
    selectedEmployeeCountRadioButton: string = 'All';

    constructor() {
        this.employees = [
            {
                code: 'emp101', name: 'Tom', gender: 'Male',
                annualSalary: 5500, dateOfBirth: '6/25/1988'
            },
            {
                code: 'emp102', name: 'Alex', gender: 'Male',
                annualSalary: 5700.95, dateOfBirth: '9/6/1982'
            },
            {
                code: 'emp103', name: 'Mike', gender: 'Male',
                annualSalary: 5900, dateOfBirth: '12/8/1979'
            },
            {
                code: 'emp104', name: 'Mary', gender: 'Female',
                annualSalary: 6500.826, dateOfBirth: '10/14/1980'
            },
            {
                code: 'emp105', name: 'Nancy', gender: 'Female',
                annualSalary: 6700.826, dateOfBirth: '12/15/1982'
            },
            {
                code: 'emp106', name: 'Steve', gender: 'Male',
                annualSalary: 7700.481, dateOfBirth: '11/18/1979'
            },
        ];
    }

    getTotalEmployeesCount(): number {
        return this.employees.length;
    }

    getMaleEmployeesCount(): number {
        return this.employees.filter(e => e.gender === 'Male').length;
    }

    getFemaleEmployeesCount(): number {
        return this.employees.filter(e => e.gender === 'Female').length;
    }

    // Depending on which radio button is selected, this method updates
    // selectedEmployeeCountRadioButton property declared above
    // This method is called when the child component (EmployeeCountComponent)
    // raises the custom event - countRadioButtonSelectionChanged
    // The event binding is specified in employeeList.component.html
    onEmployeeCountRadioButtonChange(selectedRadioButtonValue: string): void {
        this.selectedEmployeeCountRadioButton = selectedRadioButtonValue;
    }
}

The following are the changes required in the view template of EmployeeListComponent i.e employeeList.component.html. 

1. onEmployeeCountRadioButtonChange($event) method is bound to the custom event - countRadioButtonSelectionChanged. The $event object will have the selected radio button value as that is what is passed as the event payload from the nested component. The event handler method (onEmployeeCountRadioButtonChange()) in the component class updates the property "selectedEmployeeCountRadioButton". This property is then used along with *ngIf structural directive to decide which employee objects to display in the table.

2. On the <tr> element, we are using "ngIf" directive along with selectedEmployeeCountRadioButton property which controls the employee objects to display. Notice, just above the <tr> element, we have introduced <ng-container> element and the "ngFor" directive is placed on this element. If you are wondering why we have done this, Angular does not allow multiple structural directives to be placed on one element as shown below. 

<tr *ngFor="let employee of employees;"
    *ngIf="selectedEmployeeCountRadioButton=='All'
    || selectedEmployeeCountRadioButton==employee.gender">

The above line of code raises the following error
Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with *.

employeeList.component.html

<employee-count [all]="getTotalEmployeesCount()"
                [male]="getMaleEmployeesCount()"
                [female]="getFemaleEmployeesCount()"
                (countRadioButtonSelectionChanged)="onEmployeeCountRadioButtonChange($event)">
</employee-count>
<br /><br />
<table>
    <thead>
        <tr>
            <th>Code</th>
            <th>Name</th>
            <th>Gender</th>
            <th>Annual Salary</th>
            <th>Date of Birth</th>
        </tr>
    </thead>
    <tbody>
        <ng-container *ngFor="let employee of employees;">
            <tr *ngIf="selectedEmployeeCountRadioButton=='All' ||
                       selectedEmployeeCountRadioButton==employee.gender">
                <td>{{employee.code | uppercase}}</td>
                <td>{{employee.name | employeeTitle:employee.gender }}</td>
                <td>{{employee.gender}}</td>
                <td>{{employee.annualSalary | currency:'USD':true:'1.3-3'}}</td>
                <td>{{employee.dateOfBirth | date:'dd/MM/y'}}</td>
            </tr>
        </ng-container>
        <tr *ngIf="!employees || employees.length==0">
            <td colspan="5">
                No employees to display
            </td>
        </tr>
    </tbody>
</table>

At this point, run the application and test. Notice, the correct set of employees are displayed based on the selection of the radio button.

Angular 2 tutorial for beginners

4 comments:

  1. Very nice explained. I tried to run this code, but there is an error. Any one can help on this error!!

    Unhandled Promise rejection: Template parse errors:
    Can't bind to 'ngModel' since it isn't a known property of 'input'. ("

    input name='options' type='radio' value="All"
    [ERROR ->][(ngModel)]="selectedRadioButtonValue"
    (change)="onRadioButtonSelectionChange()
    span clas"): ng:///app/employee/employeeCount.component.html@18:7
    Can't bind to 'ngModel' since it isn't a known property of 'input'. ("

    ReplyDelete
    Replies
    1. Hello Kumar,
      It seems to be you didn't import FormsModule. Please follow below steps and give a try
      1) import { FormsModule } from '@angular/forms'; in module and
      2) include it in the imports array.
      e.g --> imports: [
      BrowserModule,
      routes,
      FormsModule
      ],
      please let me know if it didn't work.

      Delete
    2. Please Add FormsModule in app.madule.ts

      Delete
  2. Please go through Part-15. "Two way data binding in angular 2" before proceeding to this lesson. Your error will be solved.

    ReplyDelete

If you like this website, please share with your friends on facebook and Google+ and recommend us on google using the g+1 button on the top right hand corner.