DotNetCurry Logo

Angular 2: Passing Data in Master Details application using @Input binding

Posted by: Mahesh Sabnis , on 12/1/2016, in Category AngularJS
Views: 9159
Abstract: In an Angular 2 application, a parent component can pass data to a child component. This article will demonstrate how to do it using @input binding.

Components are building blocks of an Angular 2 application. In Angular, everything is a component. A component exposes properties and functions to the HTML UI for DataBinding.

In an Angular 2 application, a component can contain child components, and can pass data to it. Typically, this scenario is used for representing master-details relationships. The parent component can pass data to the child component using @Input binding. This binding is defined in the child component to accept the value passed from the parent component.

 

This article explains the use of @Input binding for implementing master-detail components. The application is implemented using Webpack, a JavaScript module bundler.

Note that this article does not cover the configuration of Webpack. To understand the steps for Webpack configuration, please read Angular 2 with Webpack.

Step 1: Create a folder named NG2ParentChildCtrl. Open this folder using Visual Studio Code (VSCode). In this folder, add subfolders of the name app and deps.

Step 2: On the root of the folder, add a package.json file. (Alternatively this file can be created using npm init command from the VS Code command prompt.) Add the following dependencies in it:

{
  "name": "ng2-master-details",
  "version": "1.0.0",
  "description": "The Application explains use of @Input ",
  "main": "boot.js",
  "scripts": {
    "start": "webpack-dev-server --inline --progress --port 9091"
  },
  "keywords": [
    "Input"
  ],
  "author": "MS",
  "license": "ISC",
  "devDependencies": {
    "typescript": "2.0.9",
    "webpack": "1.13.3",
    "webpack-dev-server": "1.16.2",
    "webpack-merge": "0.15.0",
    "@types/core-js": "0.9.34",
    "@types/node": "6.0.46",
    "angular2-template-loader": "0.6.0",
    "awesome-typescript-loader": "2.2.4",
    "bootstrap-loader": "1.3.0",
    "bootstrap-sass": "3.3.7",
    "bootstrap-webpack": "0.0.5",
    "css-loader": "0.25.0",
    "extract-text-webpack-plugin": "1.0.1",
    "file-loader": "0.9.0",
    "html-loader": "0.4.4",
    "html-webpack-plugin": "2.24.1",
    "jquery": "3.1.1",
    "node-sass": "3.11.2",
    "raw-loader": "0.5.1",
    "resolve-url-loader": "1.6.0",
    "rimraf": "2.5.4",
    "sass-loader": "4.0.2",
    "style-loader": "0.13.1",
    "url-loader": "0.5.7"
  },
  "dependencies": {
    "@angular/common": "2.1.2",
    "@angular/compiler": "2.1.2",
    "@angular/core": "2.1.2",
    "@angular/forms": "2.1.2",
    "@angular/platform-browser": "2.1.2",
    "@angular/platform-browser-dynamic": "2.1.2",
    "bootstrap": "3.3.7",
    "bootstrap-loader": "1.3.0",
    "bootstrap-sass": "3.3.7",
    "core-js": "2.4.1",
    "css-loader": "0.25.0",
    "install": "0.8.2",
    "jquery": "3.1.1",
    "node-sass": "3.11.2",
    "resolve-url-loader": "1.6.0",
    "rxjs": "5.0.0-rc.1",
    "sass-loader": "4.0.2",
    "style-loader": "0.13.1",
    "url-loader": "0.5.7",
    "zone.js": "0.6.26"
  }
}

Step 3: Add a new file named index.html. Right-Click on the file and select Open in Command Prompt. Run the following command from the command prompt

Npm install

This will install all required dependencies in the application.

Step 4: In the app folder add the following files

Category.model.ts

This file contains the category model class and array and will represent the master model class:

export class Category{
    constructor(
        public categoryId:number,
        public categoryName:string
    ){}
}

export const Categories = [
    {categoryId:101,categoryName:'Electrical'},
    {categoryId:102,categoryName:'Electronics'},
    {categoryId:103,categoryName:'Household'},
    {categoryId:104,categoryName:'Food'}
];

Product.model.ts

This file contains product model class and will represent the child class.

export class Product{
    constructor(
        public productId:number,
        public productName:string,
        public productPrice:number,
        public categoryName:string
    ){}
}

export const Products = [
    {productId:10001,productName:'Iron',productPrice:2000,categoryName:'Electrical'},
    {productId:10002,productName:'Laptop',productPrice:80000,categoryName:'Electronics'},
    {productId:10003,productName:'TV',productPrice:22000,categoryName:'Household'},
    {productId:10004,productName:'Rice',productPrice:2000,categoryName:'Food'},
    {productId:10005,productName:'Heater',productPrice:900,categoryName:'Electrical'},
    {productId:10006,productName:'Router',productPrice:3000,categoryName:'Electronics'},
    {productId:10007,productName:'Fridge',productPrice:25000,categoryName:'Household'},
    {productId:10008,productName:'Veg. Oil',productPrice:1000,categoryName:'Food'}
];

Category.component.ts

This file contains angular component:

import { Component, OnInit } from '@angular/core';
import {Category,Categories} from './category.model';

@Component({
    selector: 'category-data',
    templateUrl: './category.html'
})
export class CategoryComponent implements OnInit {
    categories=Categories;
    categotyName:string;
    constructor() {
        this.categotyName = "";
     }
     selectCategory(c){
         this.categotyName = c.categoryName
     }

    ngOnInit() { }
}        

The above code imports the Category model object, and the Categories array. The category-data selector declared by the component will be used for rendering category.html. The CategoryComponent class defines categories member which is used to contain Categories array values. The CategoryName member is used to store the CategoryName which is set by invoking the selectCategory() function.

Product.component.ts

import { Component, OnInit,Input } from '@angular/core';
import {Product,Products} from './product.model'; 
@Component({
    selector: 'product-data',
    templateUrl: './product.html'
})
export class ProductComponent implements OnInit {
    products = Products
    _filterProduct:Array< Product >;
    _categoryName:string;
    
  @Input()
  set categoryName(cname: string) {
    this._categoryName = (cname && cname.trim()) || 'No Category Selected';
  }
  get categoryName() { 
      return this._categoryName; 
  }    

    constructor() { 
         this._filterProduct = new Array< Product >();
         console.log('Product');
    } 

  get filterProducts() { 
           this._filterProduct = new Array< Product >();
          for(let e of Products){
            if(e.categoryName==this._categoryName){
                this._filterProduct.push(e);
            }
        } 
      return this._filterProduct; 
    }

    ngOnInit() { }
}

The above file imports Product and Products models.

· The Input module is imported from @angular/core so that it can be used to define @Input binding in the ProductComponent.

· The product-data selector is used to render product.html. The ProductComponent class declares products member to store data from Products array.

· The _filterProduct member is of the type Array< Product >, which is used to filter products based on the CategoryName.

· The _categoryName member is used to store categoryname passed from the CategoryComponent.

· The CategoryName property is decorated using @Input binding which has getter and setter methods for using CategoryName used for filtration.

· The filterProducts() getter method contains logic for filtering products based on the CategoryName.

Category.html

This file contains markup displaying category data:

<table class="table table-striped table-bordered">
     <thead>
        <tr>
            <td>Category Id</td>
            <td>Category Name</td>
        </tr>
     </thead>
     <tbody>
         <tr *ngFor="let cat of categories" 
         (click)="selectCategory(cat)">
             <td>{{cat.categoryId}}</td>
             <td>{{cat.categoryName}}</td>
         </tr>
     </tbody>
</table>

<product-data [categoryName]='categoryName'></product-data>

The above markup contains table which generates rows based on the categories. The table row is bound with the click event with selectCategory() function of the CategoryComponent class. The above markup contains tag which is defined using ProductComponent.

Product.html

<h3>Product in Category : {{categoryName}}</h3>
<table class="table table-striped table-bordered">
     <thead>
        <tr>
            <td>Product Id</td>
            <td>Product Name</td>
            <td>Product Price</td>
            <td>Cateogry</td>
        </tr>
     </thead>
     <tbody>
         <tr *ngFor="let prd of filterProducts">
             <td>{{prd.productId}}</td>
             <td>{{prd.productName}}</td>
             <td>{{prd.productPrice}}</td>
             <td>{{prd.categoryName}}</td>
         </tr>
     </tbody>
</table>

The above markup generates table rows based on the filterProducts defined in the ProductComponent.

Boot.ts

This file defines the bootstrapping for the application by importing components

import { NgModule } from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {FormsModule} from '@angular/forms';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import { CategoryComponent }   from './category.component';
import { ProductComponent }   from './product.component';

@NgModule({
    imports: [BrowserModule,FormsModule],
    declarations: [CategoryComponent,ProductComponent],
    bootstrap:[CategoryComponent]
})
export class AppModule { }
platformBrowserDynamic().bootstrapModule(AppModule);

Step 5: In the deps folder, add polyfills.ts and libraries.ts. These files will contain standard import for the Angular 2 packages and dependencies:

Libraries.ts

import 'rxjs';
import '@angular/core';
import '@angular/common';
import '@angular/compiler';
import '@angular/forms';
import '@angular/platform-browser';
import '@angular/platform-browser-dynamic';
import 'jquery';
import 'bootstrap-loader';

Polyfills.ts

import 'core-js/es6';
import 'core-js/es7/reflect';
require('zone.js/dist/zone');
Error['stackTraceLimit'] = Infinity;
require('zone.js/dist/long-stack-trace-zone');

Step 6: In the root of the application, add a webpack.confg.js. This will hold the required configuration for the application.

var webPack = require('webpack');
var htmlWebPack = require('html-webpack-plugin');
var extractTextWebPackPlugin = require('extract-text-webpack-plugin');
var path = require('path');
var config = {
  entry:{
    'app':'./app/boot.ts',
    'libs':'./deps/libraries.ts',
    'polyfills':'./deps/polyfills.ts' 
  },
  resolve:{
      extensions:['','.ts','.js','css']
  },
  module:{
      loaders:[{
           test:/\.ts$/,
           loaders:['awesome-typescript-loader','angular2-template-loader']
      },
      {
        test: /\.html$/,
        loader: 'html'
      },
      {
        test: /\.css$/,
        exclude: path.resolve('deps', 'app'),
       // loader: extractTextWebPackPlugin.extract('style', 'css?sourceMap')
       loader:"style-loader!css-loader?root=."
      },
       { test: /\.scss$/, loaders: ['style', 'css', 'postcss', 'sass'] },
      { test: /\.(woff2?|ttf|eot|svg)$/, loader: 'url?limit=10000' },
      { test: /bootstrap\/dist\/js\/umd\//, loader: 'imports?jQuery=jquery' }
      ]
  },
 plugins: [
    new webPack.optimize.CommonsChunkPlugin({
      name: ['app', 'libs', 'polyfills']
    }),

    new htmlWebPack({
      template: './index.html'
    }),
    new webPack.ProvidePlugin({
        jQuery: 'jquery',
        $: 'jquery',
        jquery: 'jquery'
    })
  ],
  output: {
    publicPath: 'http://localhost:9091/',
    filename: '[name].js',
  },
};
module.exports = config;

(Note: Please read the Using Angular 2 with WebPack article for understanding the above configuration).

Step 7: From the command prompt run the following command

Tsc  - -init

This command will add tsconfig.json in the project as shown in the following code

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "noImplicitAny": false,
        "sourceMap": false,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true
    }
}

Note that emitDecoratorMetadata and experimentalDecorators properties needs to be added explicitly.

Running the following command from the command prompt..

Npm start

..will build the app. To view the result, enter the following URL in the browser:

http://localhost:9091

to see the following result..

angular2-master-details

Click the Category table row and the following result will be displayed

filter-res

This shows the products for the selected category.

Conclusion: As we saw, using @Input binding, data can be easily passed from parent to child component to create a master-detail Angular 2 application.

Download the entire source code of this article (Github)

Was this article worth reading? Share it with fellow developers too. Thanks!
Share on Google+
Further Reading - Articles You May Like!
Author
Mahesh Sabnis is a DotNetCurry author and Microsoft MVP having over 17 years of experience in IT education and development. He is a Microsoft Certified Trainer (MCT) since 2005 and has conducted various Corporate Training programs for .NET Technologies (all versions). Follow him on twitter @maheshdotnet


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!