2016年8月24日 星期三

[Angular] ng2 beginner tutorial in vscode

 Angular2    Angular-CLI    Visucal Studio Code    VSCODE  





Environment

   Visual Studio Code 1.4.0
  Node: 6.4.0
   Angular CLI 1.0.0-beta-webpack.2



VSCODE


Some useful hotkeys in VsCode

Hot keys
Description
Example
[Shift]+[ALT]+[f]
Format the codes

[Ctrl]+[p]
or
[Ctrl]+[e]
   Search any file
   Execute command (press ? to see details)
$ ext install Angular2
Will search ‘Angular2’ related extensions or packages.
[Ctrl]+[Shift]+[p]
Open Editor commands

[Ctrl]+[Shift]+[f]
Or
[Ctrl]+[f]
Search or replace words









Create a web application architecture


Use the following command to create a web application architecture by Angular CLI.

$ ng new [Your Application(Project) Name]


 

After all the packages and settings are done by CLI. The web application’s architecture would looks like the above picture.

Name
Type
Description
public
Folder
Put the global CSS or elements(json data…) in this folder.
app
Folder
Where we are going to implement our typescripts, js and HTML.
Index.html
HTML
The homepage of our web application, everything starts here.
package.json
File
The web application’s information, or use
$ npm init
to create a new one. (See reference)






Implement : install 3rd packages


bootstrap

$ npm intall bootstrap –save

Copy the necessary css under /public/assets/styles/
and add the following codes on /src/index.html

<head>
    <link href="/assets/styles/bootstrap.min.css" rel="stylesheet">
</head>



Implement : Create header component


We’re going to put a header on the index page by creating a Header Component.
Use the following command to create it.

$ ng generate component [new_component_name]

Or simply the command as

$ ng g c [new_component_name]


header.component

After it’s done, you will find a new folder : “/src/app/header/” in the project, which at least contains
header.component.css
header.component.html
header.component.ts

Notice that the header.component.css could only be used in header.component.html
.

Furthermore, you can take a look at /src/app/app.module.ts
CLI had imported the new component for us.

Modify the header component like the following sample.

   header.component.html
<div class="form-group" class="nav-title">
    <div class="container">
        <div>
            <ul class="nav navbar-nav">
                <li><a href="#" class="nav-title"><h3>Home</h3></a></li>
                <li><a href="#" class="nav-title"><h3>About</h3></a></li>
            </ul>
        </div>
    </div>
</div>


   header.component.css
div.nav-title {
   background-color: lightgreen;
}
a.nav-title {
   color: darkblue;
}


app.component


   app.component.html

Now we can put the header selector name on the app.component.html.
In other words, the header selector is the directive’s name, you can find the name on *.component.ts

<app-header></app-header>
<div class="container body-content">
    <h1>
        {{title}}
    </h1>
</div>



Implement : Show data list


Since we learned how to create component and integrate it with the exist component.
We now want to create another component in order to show a list of data on the index page.

Use $ ng g c data to create data.component.*

We will make a scenario that the parent (app.component) provides the data, and child (data.component) displays the data on it’s HTML. For this case, we must learn how to pass the data from parent to child.


app.component : making sample data

On app.component.ts, add a const array : customers,
And use ngOnInit() to initialize the property: data

   app.component.ts

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

@Component({
    selector: 'app-root',
    templateUrl: 'app.component.html',
    styleUrls: ['app.component.css']
})
export class AppComponent implements OnInit {
    title:string;
    data:any[];

    ngOnInit() {
        this.title = "Customers";
        this.data = customers;
    };
}


const customers: any[] =
  [{ "Name": "<b>JB</b>", "Phone": "0933XXXXXX", "My-age": 35 }, { "Name": "<b>Lily</b>", "Phone": "0910XXXXXX", "My-age": 18 }, { "Name": "<b>Leia</b>", "Phone": "N/A", "My-age": 3 }]





PS.
The lifecycle hook of ngOnInit and Constructor is different.
Constructor : called when the component is being created.
ngonInit : called after the component is created.
See more details on official ng2 doc : lifecycle hooks


data.component

We now have the sample data in app.component, it’s time to pass the data from app.component to data.component and use ngFor directive to show the data on data.component.


   data.component.ts

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

@Component({
    selector: 'app-data',
    templateUrl: 'data.component.html',
    styleUrls: ['data.component.css']
})
export class DataComponent implements OnInit {

    @Input('local-data') data: any[];
    constructor() { }

    ngOnInit() {
    }

}

In the export class, use @Input([optional name]) to define the property: data, is an input parameter.  We will pass the data in app.component through this input parameter later.

   data.component.html

Use ngFor directive to display the data.

<div>
    <table class="table table-bordered table-hover">
        <thead>
            <tr>
                <td class="text-center">Name</td>
                <td class="text-center">Phone</td>
                <td class="text-center">Age</td>
            </tr>
        </thead>
        <tbody>
            <tr *ngFor="let item of data">
                <td>{{item.Name}}</td>
                <td>{{item.Phone}}</td>
                <td>{{item.My-age}}</td>
            </tr>
        </tbody>
    </table>
</div>

Furthermore, if you would like to get $index (like ng1) in ng2.
Use the codes like this :

<tr *ngFor="let item of data; let sn=index">
   <
td>{{Sn}}</td>
</tr>



Add the data.component directive to app.component.html

   app.component.html

<app-header></app-header>
<div class="container body-content">
    <h3>
        {{title}}
    </h3>
    <app-data [local-data]="data"></app-data>
</div>


We use property binding “[]” to set the value of “data” to data.component’s input parameter: local-data!



Okay, let’s see the result…



There are 2 incorrect issue on our first run:
1.  HTML tag is blocked by interpolation: {{}}
2.  {{item.My-age}} is not correct binding.

For safety issue, the HTML tag will be blocked with interpolation. So if you want to bind HTML tag, try using property binding: [innerHtml].

The second problem could be solved by changing the binding pattern:
{{item['My-age']}}


The new content of data.component.html:

<tr *ngFor="let item of data; let sn=index;">
    <td>{{sn+1}}</td>
    <td [innerHtml]="item.Name"></td>
    <td>{{item.Phone}}</td>
    <td>{{item['My-age']}}</td>
</tr>


 


Implement : Delete data


We will implement “delete button” on the customer data in this chapter.
Here we will learn Event binding: ()


Remove data

   data.component.ts

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

@Component({
    selector: 'app-data',
    templateUrl: 'data.component.html',
    styleUrls: ['data.component.css']
})
export class DataComponent implements OnInit {

    @Input('local-data') data: any[];

    constructor() { }

    ngOnInit() {
       
    }

    deleteCustomer(item:any){
        //Remove item
        var index = this.data.indexOf(item);
        this.data.splice(index, 1);
    }

}




   data.component.html

<tr *ngFor="let item of data; let sn=index;">
    <td>{{sn+1}}</td>
    <td [innerHtml]="item.Name"></td>
    <td>{{item.Phone}}</td>
    <td>{{item['My-age']}}</td>
    <td class="text-center">
        <input type="button" class="btn btn-warning" value="Remove" (click)="deleteCustomer(item)" />
    </td>
</tr>


Passing the deleted information from child to parent

We want to have a function that we can see which customer is removed in data.component.
That means the child component must pass information to parent component.
We will use EventEmit to achieve this goal.

Steps:
1.  Import Output & EventEmit from angular/core.
2.  Declare an output EventEmitter.
3.  Set the trigger(emit) for the EventEmitter in data.component.
4.  Set the receiver for the EventEmitter in app.component.


   data.component.ts

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

@Component({
    selector: 'app-data',
    templateUrl: 'data.component.html',
    styleUrls: ['data.component.css']
})
export class DataComponent implements OnInit {

    @Input('local-data') data: any[];
    @Output('delete-item') emitDeleteCust = new EventEmitter<any>();

    constructor() { }

    ngOnInit() {
       
    }

    deleteCustomer(item:any){
        //emit data to parent
        this.emitDeleteCust.emit(item);

        //Remove item
        var index = this.data.indexOf(item);
        this.data.splice(index, 1);
    }

}


   app.component.html

<app-header></app-header>
<div class="container body-content">
    <h3>
        {{title}}
    </h3>
    <div *ngIf="deletedCustomer" class="alert alert-warning" role="alert">
        <strong>Warning!</strong>
        <span [innerHtml]="deletedCustomer.Name + ' was removed!!'"></span>
    </div>
    <app-data [local-data]="data" (delete-item)="showDeletedCust($event)">
</app-data>
</div>


In app.component.html, we use *ngIf  to decide whether the warning message will be shown or not.
Besides, using Event binding: (delete-item), to locate the output EventEmitter and to bind a received callback: showDeletedCust($event).

Notice that the showDeletedCust($event) is passing “$event” parameter. However, we emit the “deleted item” in data.component, so the $event will be “deleted item”.


   app.component.ts

Simply add the received callback inside app.component.ts.

deletedCustomer: any;

//Display the deleted customer
showDeletedCust(item: any) {
    this.deletedCustomer = item;
}



Demo


 


Implement : Search data


We’re going to put a textbox and a search button to filter our customer data on “Name”.
We will learn
   How to get DOM in component



Search after [Enter] is pressed

Add the following html to app.component.html

   app.component.html

<div class="form-group">
    <div class="col-md-5">
        <input type="text" class="form-control" (keyup)="keySearch($event)" />
    </div>
    <div class="col-md-2">
        <input type="button" class="btn btn-success" value="Search">
    </div>
</div>


There is an event binding for “keyup”, so what will be pass thru the input parameter: $event?


   app.component.ts

We will first find out what $event will be.
Put the following experimental codes on app.component.ts

export class AppComponent implements OnInit {

    //skip the original codes ...
 
    keySearch($event) {
        console.log($event);
    }
}

And the log will be like this after you key in anything




Okay, we know the $event is actually a KeyboardEvent, so fortunately we can use the KeyboardEvent to get the keyCode and HTMLInputElement of the DOM.

We modify keySearch($event) in this way to ensure that searching will executed after “Enter”(ASCII dec:13) is keyed in.  

export class AppComponent implements OnInit {

    //skip the original codes ...

 
    //Search when [Enter] is keyup
    keySearch($event: KeyboardEvent) {
    let input = $event.target as HTMLInputElement;
    if (keyWord.length > 0) {
            this.data = this.defaultData.filter(value => {
               return value.Name.toLowerCase().indexOf(keyWord.toLowerCase()) > -1;});
            }
            else {
                this.data = customers;
            }
    }
}


Search after click [Search] button

How about search after click [Search] button? How can we get the value of the input textbox for [Search] button?
Template syntax will do this favor for us.


   app.component.html

<div class="form-group">
    <div class="col-md-5">
        <input type="text" class="form-control" #searchKeyWord (keyup)="keySearch($event)" />
    </div>
    <div class="col-md-2">
        <input type="button" class="btn btn-success" value="Search" (click)="btnSearch(searchKeyWord.value)">
    </div>
</div>

We defined a template syntax: #searchKeyWord on the input textbox, and we can get the input value by searchKeyWord.value.

PS. You can write #searchKeyWord as ref-searchKeyWord.



   app.component.ts

export class AppComponent implements OnInit {

    //skip the original codes ...

 
    //Search when [Search] is clicked
    btnSearch(keyWord: string) {
        if (keyWord.length > 0) {
            this.data = searchData(this.defaultData,keyWord);
            console.log(this.data);
        }
        else{
            this.data=customers;
        }
    }

}

function searchData(data, keyWord) {
    var filteredData = data.filter(value => {
        return value.Name.toLowerCase().indexOf(keyWord.toLowerCase()) > -1;
    });

    return filteredData;
}



Demo

 



Implement : Style it


ngSwitch

There is a new requirement about changing the customer‘s phone number from “N/A” to “**********”.

We will use ngSwitch to complete this requirement.

   data.component.html

<tr *ngFor="let item of data; let sn=index;">
    <!--skip... -->
    <td>
        <div [ngSwitch]="item.Phone">
            <span *ngSwitchCase="'N/A'" style="color:darkgray">***********</span>
            <span *ngSwitchDefault>{{item.Phone}}</span>
        </div>
    </td>
    <!--skip... -->
</tr>

It’s done! Notice that if the value is string, we have to add single quotation on the keyword comparison. For example, *ngSwitchCase=”XXXX


ngClass

We will display the customer’s age in different color. We will first write some css class and use use ngClass to bind the css class under several conditions.

   data.component.css

.age-green {
  color: green !important;
}
.age-blue {
  color: blue !important;
}
.age-orange {
  color: darkorange !important;
}


   data.component.html

Update the html with the following codes.

<span [ngClass]="{'age-green': item['My-age']<18, 'age-blue':item['My-age']>=18, 'age-orange':item['My-age']>60}">
    {{item['My-age']}}
</span>

You can try ngStyle to style the data as well.


Demo




Implement : Service and ajax


In this chapter, we will create Service to get the customers’ data thru ajax, and import the Service into app.component to remove the responsibility of getting customers’ data from app.component.

Create data.service

Change directory to $/src/app/data/
Create “data.service.ts” by the following command:

$ ng generate service [service_name]


Update app.module.ts

Open data.service.ts and copy the name of the class: “DataService”.
We will need to setup /src/app/app.module.ts , to inject the Service as a provider, and import HttpModule for later using.

   app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, ApplicationRef } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { HeaderComponent } from './header/header.component';
import { DataComponent } from './data/data.component';
import { HttpModule } from '@angular/http';
import { DataService } from './data/data.service';

@NgModule({
    declarations: [
      AppComponent,
      HeaderComponent,
      DataComponent
    ],
    imports: [
      BrowserModule,
      CommonModule,
      FormsModule,
      HttpModule
    ],
    providers: [DataService],
    entryComponents: [AppComponent],
    bootstrap: [AppComponent]
})
export class AppModule {

}



Implement DataService


So we want to get(ajax) the customers’ data, and restore it on DataService.

First add a JSON file which contains the customers’ data.

 


   data.service.ts

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';

@Injectable()
export class DataService {

    defaultData: any[];
    data: any[];

    constructor(private http: Http) {
        http.get("/api/customers.json")
          .subscribe(value => {
              this.data= this.defaultData = value.json();
          });
    }
}



The DataService is ready, we now must import it to AppComponent.
Instead of using the “data” and “defaultData” in the AppComponent,  we turn to use the ones in DataService.


   app.component.ts

import { Component, OnInit } from '@angular/core';
import {DataService} from './data/data.service';

@Component({
    selector: 'app-root',
    templateUrl: 'app.component.html',
    styleUrls: ['app.component.css']
})
export class AppComponent implements OnInit {
    title: string;
    // defaultData: any[];
    // data: any[];
    deletedCustomer: any;

    constructor(private dataService: DataService){

    }

    //Skip ....
}

// const customers: any[] = ...;




   app.component.html

Don’t forget to update app.component.html as well.

<app-data [(local-data)]="dataService.data" (delete-item)="deleteCustSrc($event)"></app-data>



Furthermore

However, you will find that there are some conflicts on deleting and searching customers’ data. The reason is that the delete and search functions are meant to be in DataService, but not AppComponent or DataComponent.

I will leave this as your practice.
You can check my GITHUB for the full codes and demo.



Github

View the source code on my Github



Reference