HI WELCOME TO SIRIS

Angular component output properties

Leave a Comment

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



angular2 eventemitter between components



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

User ActionWhat should happen
All(6) radio button is clickedDisplay all the employees in the table
Male(4) radio button is clickedDisplay the 4 Male employees in the table
Female(2) radio button is clickedDisplay 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.

0 comments:

Post a Comment

Note: only a member of this blog may post a comment.