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
▓ Node: 6.4.0
▓ Angular CLI 1.0.0-beta-webpack.2
▌VSCODE
Some
useful hotkeys in VsCode
▌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
|
▌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,
▓ 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.
|
▋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
<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.
Steps:
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>
|
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 “**********”.
▓ 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>
|
▋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;
deletedCustomer: any;
constructor(private dataService:
DataService){
}
//Skip ....
}
//
|
▓ 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