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

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

14 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
    3. I too get that error instead of (change)--->change it to ngModelchange so that as soon as selectedRadioButtonValue changes it emit in ngModelchange

      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
  3. Hi
    input change event is not working,Please help me , i am stuck here.

    ReplyDelete
  4. great sir i try I tried to run this code, but it only show the employeeCount.component (table heade) and not show me the emp.list.component (the table data)

    ReplyDelete
  5. Radiobutton Selection Event not getting fired & list not getting updated.

    ReplyDelete
  6. I have less knowledge about emit method. Can you pls explain more to understand it.

    ReplyDelete
  7. HI Sir, i am able to see only header not the table data and after clicking the radio button it is not modifying the table data. please help me.

    ReplyDelete
  8. import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { FormsModule } from '@angular/forms';
    import { AppComponent } from './app.component';
    import { EmployeeComponent } from './employee/employee.component';
    import { EmployeeListComponent } from './employee/employeeList.component';
    import { EmployeeTitlePipe } from './employee/employeeTitle.pipe';
    import { EmployeeCountComponent } from './employee/employeeCount.component';

    @NgModule({
    imports: [BrowserModule, FormsModule],
    declarations: [AppComponent, EmployeeComponent, EmployeeListComponent, EmployeeTitlePipe, EmployeeCountComponent],
    bootstrap: [AppComponent]
    })
    export class AppModule { }

    Please copy all the content to app.module.ts

    ReplyDelete
  9. great sir, I run this code It's working fine for me. Very nice explanation.

    ReplyDelete
  10. sir
    all table data is showing but change method not working please help

    ReplyDelete
  11. not convinced how that rows are filtering based on keywords

    ReplyDelete

It would be great if you can help share these free resources