Cấu trúc
chương trình Angular
Cấu trúc của chương trình Angular
Cấu trúc của 1 component
Các bước làm một project Angular
Các bước làm
Tạo RoutingModule, và component Dashboard
10.
Giả lập HttpClient bằng service InMemoryData component (dùng angular-in-memory-web-api) và tạo component HeroSearch
11.
1. Tạo project
ng new angular-tour-of-heroes
2. Chạy project
cd angular-tour-of-heroes
ng serve --open
3. Code thử
title = 'Tour of Heroes';
1. Thay title
app.component.ts (class title property)
2. Hiện title
app.component.html (template)
<h1>{{title}}</h1>
3. Code thử (3)
/* Application-wide Styles */
h1 {
color: #369;
font-family: Arial, Helvetica, sans-serif;
font-size: 250%;
}
h2, h3 {
color: #444;
font-family: Arial, Helvetica, sans-serif;
font-weight: lighter;
}
body {
margin: 2em;
}
body, input[type="text"], button {
color: #333;
font-family: Cambria, Georgia;
}
/* everywhere else */
* {
font-family: Arial, Helvetica, sans-serif;
}
3. Thêm styles
src/styles.css
4. Tạo component Hero
ng generate component heroes
4. Tạo component Hero
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-heroes',
/* selector— để gọi component ra */
templateUrl: './heroes.component.html',
/* templateUrl— địa chỉ của file template.html */
styleUrls: ['./heroes.component.css']
/* styleUrls— địa chỉ của file css riêng của component */
})
export class HeroesComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
2. HeroesComponent class
app/heroes/heroes.component.ts (initial version)
4. Tạo component Hero
import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
@Component({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
hero: Hero = {
id: 1,
name: 'Windstorm'
};
constructor() { }
ngOnInit() {
}
}
2. HeroesComponent class
app/heroes/heroes.component.ts (updated version)
4. Tạo component Hero
<h2>{{hero.name | uppercase}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<div>
<label>name:
<input [(ngModel)]="hero.name" placeholder="name"/>
</label>
</div>
3. HeroesComponent template file
src/app/heroes/heroes.component.html
4. Tạo component Hero
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; // <-- NgModel lives here
import { AppComponent } from './app.component';
import { HeroesComponent } from './heroes/heroes.component';
@NgModule({
declarations: [
AppComponent,
HeroesComponent
],
imports: [
BrowserModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
4. AppModule (files and libraries the app requires - metadata)
src/app/app.module.ts
4. Tạo component Hero
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Tour of Heroes';
}
5. AppComponent (component tổng - class file không thay đổi)
src/app/app.component.ts
4. Tạo component Hero
<h1>{{title}}</h1>
<app-heroes></app-heroes>
6. AppComponent template (gọi component Hero)
src/app/app.component.html
4. Tạo component Hero
export class Hero {
id: number;
name: string;
}
7. Hero Class (object - A real hero is more than a name.)
src/app/hero.ts
5. Hiển thị danh sách Hero
5. Hiển thị danh sách Hero
import { Hero } from './hero';
export const HEROES: Hero[] = [
{ id: 11, name: 'Dr Nice' },
{ id: 12, name: 'Narco' },
{ id: 13, name: 'Bombasto' },
{ id: 14, name: 'Celeritas' },
{ id: 15, name: 'Magneta' },
{ id: 16, name: 'RubberMan' },
{ id: 17, name: 'Dynama' },
{ id: 18, name: 'Dr IQ' },
{ id: 19, name: 'Magma' },
{ id: 20, name: 'Tornado' }
];
1. Tạo mock heros
src/app/mock-heroes.ts
5. Hiển thị danh sách Hero
// ...
import { Hero } from '../hero';
import { HEROES } from '../mock-heroes';
// ....
export class HeroesComponent implements OnInit {
heroes = HEROES;
selectedHero: Hero;
// ....
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
}
2. Import Heroes
src/app/heroes/heroes.component.ts (import HEROES)
5. Hiển thị danh sách Hero
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
<div *ngIf="selectedHero">
<h2>{{selectedHero.name | uppercase}} Details</h2>
<div><span>id: </span>{{selectedHero.id}}</div>
<div>
<label>name:
<input [(ngModel)]="selectedHero.name" placeholder="name"/>
</label>
</div>
</div>
3. Hiển thị Heroes với *ngFor
heroes.component.html (heroes template)
5. Hiển thị danh sách Hero
/* HeroesComponent's private CSS styles */
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.heroes li:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.heroes li.selected {
background-color: #CFD8DC;
color: white;
}
.heroes li.selected:hover {
background-color: #BBD8DC;
color: white;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color:#405061;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
4. Thêm css cho HeroesComponent
src/app/heroes/heroes.component.css
7. Tạo component Chi tiết Hero
7. Tạo component Chi tiết Hero
ng generate component hero-detail
7. Tạo component Chi tiết Hero
import { Component, OnInit, Input } from '@angular/core';
import { Hero } from '../hero';
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: ['./hero-detail.component.css']
})
export class HeroDetailComponent implements OnInit {
@Input() hero: Hero;
constructor() { }
ngOnInit() {
}
}
1. Tạo @Input trong HeroDetailComponent class
src/app/hero-detail/hero-detail.component.ts (import Hero)
7. Tạo component Chi tiết Hero
<div *ngIf="hero">
<h2>{{hero.name | uppercase}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<div>
<label>name:
<input [(ngModel)]="hero.name" placeholder="name"/>
</label>
</div>
</div>
2. Chuyển code Hero detail vào temple của HeroDetailComponent
src/app/hero-detail/hero-detail.component.html
7. Tạo component Chi tiết Hero
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { HeroesComponent } from './heroes/heroes.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
@NgModule({
declarations: [
AppComponent,
HeroesComponent,
HeroDetailComponent
],
imports: [
BrowserModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
2. Khai báo HeroDetailComponent trong AppModule
src/app/app.module.ts
7. Tạo component Chi tiết Hero
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
3. Gọi component HeroDetail trong HeroesComponent
src/app/heroes/heroes.component.html
8. Tạo service HeroService
8. Tạo service HeroService
ng generate service hero
8. Tạo service HeroService
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
import { MessageService } from './message.service';
// marks the class as one that participates in the dependency injection system
@Injectable({
providedIn: 'root',
// creates a single, shared instance of HeroService and injects into any class that asks for it
})
export class HeroService {
constructor(private messageService: MessageService) { }
getHeroes(): Observable<Hero[]> {
// TODO: send the message _after_ fetching the heroes
this.messageService.add('HeroService: fetched heroes');
// data comes from the future, so return of(data) => Observable.
// The destination then call observable.subscribe(data) to listen for data change
return of(HEROES);
}
}
2. HeroService class
src/app/hero.service.ts (new service)
9. Tạo Message component và gắn vào HeroService
ng generate component messages
9. Tạo Message component
<h1>{{title}}</h1>
<app-heroes></app-heroes>
<app-messages></app-messages>
2. Gọi Message component vào App component
src/app/app.component.html
3. Tạo Message service
ng generate service message
9. Tạo MessageService
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class MessageService {
messages: string[] = [];
add(message: string) {
this.messages.push(message);
}
clear() {
this.messages = [];
}
}
4. MessageService class
src/app/message.service.ts
9. Tạo MessageService
// ...
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
export class HeroesComponent implements OnInit {
constructor(private heroService: HeroService) { }
// ...
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
}
5. HeroesComponent class
src/app/heroes/heroes.component.ts
9. Tạo MessageService
// ...
import { MessageService } from '../message.service';
export class MessagesComponent implements OnInit {
constructor(public messageService: MessageService) {}
ngOnInit() {
}
}
6. Inject MessageService vào MessageComponent
src/app/messages/messages.component.ts
9. Tạo MessageService
<div *ngIf="messageService.messages.length">
<h2>Messages</h2>
<button class="clear"
(click)="messageService.clear()">clear</button>
<div *ngFor='let message of messageService.messages'> {{message}} </div>
</div>
7. Hiện các messages trong MessageComponent template
src/app/messages/messages.component.html
9. Tạo MessageService
/* MessagesComponent's private CSS styles */
h2 {
color: red;
font-family: Arial, Helvetica, sans-serif;
font-weight: lighter;
}
body {
margin: 2em;
}
body, input[text], button {
color: crimson;
font-family: Cambria, Georgia;
}
button.clear {
font-family: Arial;
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
cursor: hand;
}
button:hover {
background-color: #cfd8dc;
}
button:disabled {
background-color: #eee;
color: #aaa;
cursor: auto;
}
button.clear {
color: #333;
margin-bottom: 12px;
}
8. Thêm CSS cho các messages
src/app/messages/messages.component.css
9. Tạo MessageService
// ...
import { MessagesComponent } from './messages/messages.component';
@NgModule({
declarations: [
//...
MessagesComponent
],
imports: [
BrowserModule,
FormsModule
],
providers: [
// no need to place any providers due to the `providedIn` flag...
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
9. Khai báo MessageComponent trong AppModule
src/app/app.module.ts
9. Tạo MessageService
<h1>{{title}}</h1>
<app-heroes></app-heroes>
<app-messages></app-messages>
10. Gọi MessagesComponent trong AppComponent template
src/app/app.component.html
10. Tạo RoutingModule
và component Dashboard
10. Tạo RoutingModule
ng generate module app-routing --flat --module=app
10. Tạo RoutingModule
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
imports: [
CommonModule
],
declarations: []
})
export class AppRoutingModule { }
1. AppRoutingModule
src/app/app-routing.module.ts (generated)
10. Tạo RoutingModule
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DashboardComponent } from './dashboard/dashboard.component';
import { HeroesComponent } from './heroes/heroes.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
const routes: Routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent },
{ path: 'detail/:id', component: HeroDetailComponent },
{ path: 'heroes', component: HeroesComponent }
];
@NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule {}
1. AppRoutingModule updated
src/app/app-routing.module.ts (updated)
Một Angular route sẽ có 2 thuộc tính
const routes: Routes = [
{ path: 'heroes', component: HeroesComponent }
];
src/app/app-routing.module.ts
Cấu hình route từ đường dẫn gốc "/"
imports: [ RouterModule.forRoot(routes) ],
src/app/app-routing.module.ts
exports: [ RouterModule ]
Làm route có thể sử dụng trong toàn app
10. Tạo RoutingModule
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
import { HeroesComponent } from './heroes/heroes.component';
import { MessagesComponent } from './messages/messages.component';
import { AppRoutingModule } from './app-routing.module';
@NgModule({
imports: [
BrowserModule,
FormsModule,
AppRoutingModule
],
declarations: [
AppComponent,
DashboardComponent,
HeroesComponent,
HeroDetailComponent,
MessagesComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
2. Khai báo các component mới trong AppModule
src/app/app.module.ts
10. Tạo RoutingModule
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
import { MessageService } from './message.service';
@Injectable({ providedIn: 'root' })
export class HeroService {
constructor(private messageService: MessageService) { }
getHeroes(): Observable<Hero[]> {
// TODO: send the message _after_ fetching the heroes
this.messageService.add('HeroService: fetched heroes');
return of(HEROES);
}
getHero(id: number): Observable<Hero> {
// TODO: send the message _after_ fetching the hero
this.messageService.add(`HeroService: fetched hero id=${id}`);
return of(HEROES.find(hero => hero.id === id));
}
}
3. Thêm hàm getHeroById cho HeroService
src/app/hero.service.ts
10. Tạo RoutingModule
<h1>{{title}}</h1>
<nav>
<a routerLink="/dashboard">Dashboard</a>
<a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
<app-messages></app-messages>
4. Thêm link để click vào Dashboard trên AppComponent
src/app/app.component.html
10. Tạo RoutingModule
/* AppComponent's private CSS styles */
h1 {
font-size: 1.2em;
margin-bottom: 0;
}
h2 {
font-size: 2em;
margin-top: 0;
padding-top: 0;
}
nav a {
padding: 5px 10px;
text-decoration: none;
margin-top: 10px;
display: inline-block;
background-color: #eee;
border-radius: 4px;
}
nav a:visited, a:link {
color: #334953;
}
nav a:hover {
color: #039be5;
background-color: #cfd8dc;
}
nav a.active {
color: #039be5;
}
5. Thêm CSS cho AppComponent
src/app/app.component.css
6. Tạo component Dashboard
ng generate component dashboard
10. Tạo RoutingModule
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<a *ngFor="let hero of heroes" class="col-1-4"
routerLink="/detail/{{hero.id}}">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</a>
</div>
7. Tạo template cho Dashboard component
src/app/dashboard/dashboard.component.html
10. Tạo RoutingModule
import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: [ './dashboard.component.css' ]
})
export class DashboardComponent implements OnInit {
heroes: Hero[] = [];
constructor(private heroService: HeroService) { }
ngOnInit() {
this.getHeroes();
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes.slice(1, 5));
// chỉ lấy heroes từ vị trí từ nhất đến trước vị trí thứ 5
// (2nd, 3rd, 4th, and 5th)
}
}
7. Tạo getHeroes() trong Dashboard Typescript file
src/app/dashboard/dashboard.component.ts
10. Tạo RoutingModule
/* DashboardComponent's private CSS styles */
[class*='col-'] {
float: left;
padding-right: 20px;
padding-bottom: 20px;
}
[class*='col-']:last-of-type {
padding-right: 0;
}
a {
text-decoration: none;
}
*, *:after, *:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
h3 {
text-align: center;
margin-bottom: 0;
}
h4 {
position: relative;
}
.grid {
margin: 0;
}
.col-1-4 {
width: 25%;
}
.module {
padding: 20px;
text-align: center;
color: #eee;
max-height: 120px;
min-width: 120px;
background-color: #3f525c;
border-radius: 2px;
}
.module:hover {
background-color: #eee;
cursor: pointer;
color: #607d8b;
}
.grid-pad {
padding: 10px 0;
}
.grid-pad > [class*='col-']:last-of-type {
padding-right: 20px;
}
@media (max-width: 600px) {
.module {
font-size: 10px;
max-height: 75px; }
}
@media (max-width: 1024px) {
.grid {
margin: 0;
}
.module {
min-width: 60px;
}
}
8. Thêm CSS cho DashboardComponent
src/app/dashboard/dashboard.component.css
10. Tạo RoutingModule
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes">
<a routerLink="/detail/{{hero.id}}">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</a>
</li>
</ul>
9. Thêm link vào HeroesDetailComponent cho các template
src/app/heroes/heroes.component.html
10. Tạo RoutingModule
import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
heroes: Hero[];
constructor(private heroService: HeroService) { }
ngOnInit() {
this.getHeroes();
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
}
10. Bỏ hàm onSelect() Hero vì giờ có route rồi
src/app/heroes/heroes.component.ts
10. Tạo RoutingModule
/* HeroesComponent's private CSS styles */
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
position: relative;
cursor: pointer;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.heroes li:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.heroes a {
color: #333;
text-decoration: none;
position: relative;
display: block;
width: 250px;
}
.heroes a:hover {
color:#607D8B;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color:#405061;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
min-width: 16px;
text-align: right;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
11. Bỏ một số CSS không cần thiết cho HeroComponent
src/app/heroes/heroes.component.css
10. Tạo RoutingModule
<div *ngIf="hero">
<h2>{{hero.name | uppercase}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<div>
<label>name:
<input [(ngModel)]="hero.name" placeholder="name"/>
</label>
</div>
<button (click)="goBack()">go back</button>
</div>
12. Thêm nút goBack() cho HeroDetail component
src/app/hero-detail/hero-detail.component.html
10. Tạo RoutingModule
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: [ './hero-detail.component.css' ]
})
export class HeroDetailComponent implements OnInit {
hero: Hero;
constructor(
private route: ActivatedRoute,
private heroService: HeroService,
private location: Location
) {}
ngOnInit(): void {
this.getHero();
}
getHero(): void {
const id = +this.route.snapshot.paramMap.get('id');
this.heroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
goBack(): void {
this.location.back();
}
}
13. Bắt Hero dựa vào id trên url và xử lý goBack() trong HeroDetail component Typescript file
src/app/hero-detail/hero-detail.component.ts
getHero(): void {
const id = +this.route.snapshot.paramMap.get('id');
this.heroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
10. Tạo RoutingModule
/* HeroDetailComponent's private CSS styles */
label {
display: inline-block;
width: 3em;
margin: .5em 0;
color: #607D8B;
font-weight: bold;
}
input {
height: 2em;
font-size: 1em;
padding-left: .4em;
}
button {
margin-top: 20px;
font-family: Arial;
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
cursor: hand;
}
button:hover {
background-color: #cfd8dc;
}
button:disabled {
background-color: #eee;
color: #ccc;
cursor: auto;
}
14. Style cho HeroDetail component
src/app/hero-detail/hero-detail.component.css
11. Giả lập HttpClient - Server bằng service InMemoryData (dùng angular-in-memory-web-api)
và tạo component HeroSearch
Cài đặt In-memory Web API
npm install angular-in-memory-web-api --save
Tạo Service InMemoryData
ng generate service InMemoryData
11. Gọi HttpRequest trong Angular
// ...
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [
BrowserModule,
FormsModule,
AppRoutingModule,
HttpClientModule,
)
],
// ...
})
14. Import HttpClientModule
src/app/app.module.ts (HttpClientModule import)
11. Gọi HttpRequest trong Angular
// ...
import { HttpClientInMemoryWebApiModule }
from 'angular-in-memory-web-api';
import { InMemoryDataService } from './in-memory-data.service';
@NgModule({
imports: [
//...
// The HttpClientInMemoryWebApiModule module
// intercepts HTTP requests
// and returns simulated server responses.
// Remove it when a real server is ready to receive requests.
HttpClientInMemoryWebApiModule.forRoot(
InMemoryDataService, { dataEncapsulation: false }
)
],
})
15. Import HttpClientInMemoryWebApiModule và InMemoryDataService
src/app/app.module.ts (HttpClientModule import)
16. Tạo InMemoryDataService
ng generate service InMemoryData
11. Gọi HttpRequest trong Angular
import { InMemoryDbService } from 'angular-in-memory-web-api';
import { Hero } from './hero';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class InMemoryDataService implements InMemoryDbService {
createDb() {
const heroes = [
{ id: 11, name: 'Dr Nice' },
{ id: 12, name: 'Narco' },
{ id: 13, name: 'Bombasto' },
{ id: 14, name: 'Celeritas' },
{ id: 15, name: 'Magneta' },
{ id: 16, name: 'RubberMan' },
{ id: 17, name: 'Dynama' },
{ id: 18, name: 'Dr IQ' },
{ id: 19, name: 'Magma' },
{ id: 20, name: 'Tornado' }
];
return {heroes};
}
// Overrides the genId method to ensure that a hero always
// has an id.
// If the heroes array is empty,
// the method below returns the initial number (11).
// if the heroes array is not empty, the method below
// returns the highest
// hero id + 1.
genId(heroes: Hero[]): number {
return heroes.length > 0
? Math.max(...heroes.map(hero => hero.id)) + 1 : 11;
}
}
17. Thêm dummy data cho InMemoryDataService
src/app/in-memory-data.service.ts
Có thể xóa file mock-heroes.ts vì giờ không cần nữa
11. Gọi HttpRequest trong Angular
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, map, tap } from 'rxjs/operators';
import { Hero } from './hero';
import { MessageService } from './message.service';
@Injectable({ providedIn: 'root' })
export class HeroService {
private heroesUrl = 'api/heroes'; // URL to web api
httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
constructor(
private http: HttpClient,
private messageService: MessageService) { }
/** GET heroes from the server */
getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
tap(_ => this.log('fetched heroes')),
catchError(this.handleError<Hero[]>('getHeroes', []))
);
}
/** GET hero by id. Return `undefined` when id not found */
getHeroNo404<Data>(id: number): Observable<Hero> {
const url = `${this.heroesUrl}/?id=${id}`;
return this.http.get<Hero[]>(url)
.pipe(
map(heroes => heroes[0]), // returns a {0|1} element array
tap(h => {
const outcome = h ? `fetched` : `did not find`;
this.log(`${outcome} hero id=${id}`);
}),
catchError(this.handleError<Hero>(`getHero id=${id}`))
);
}
/** GET hero by id. Will 404 if id not found */
getHero(id: number): Observable<Hero> {
const url = `${this.heroesUrl}/${id}`;
return this.http.get<Hero>(url).pipe(
tap(_ => this.log(`fetched hero id=${id}`)),
catchError(this.handleError<Hero>(`getHero id=${id}`))
);
}
/* GET heroes whose name contains search term */
searchHeroes(term: string): Observable<Hero[]> {
if (!term.trim()) {
// if not search term, return empty hero array.
return of([]);
}
return this.http.get<Hero[]>(`${this.heroesUrl}/?name=${term}`)
.pipe(
tap(_ => this.log(`found heroes matching "${term}"`)),
catchError(this.handleError<Hero[]>('searchHeroes', []))
);
}
//////// Save methods //////////
/** POST: add a new hero to the server */
addHero (hero: Hero): Observable<Hero> {
return this.http.post<Hero>(this.heroesUrl, hero,
this.httpOptions)
.pipe(
tap((newHero: Hero) =>
this.log(`added hero w/ id=${newHero.id}`)),
catchError(this.handleError<Hero>('addHero'))
);
}
/** DELETE: delete the hero from the server */
deleteHero (hero: Hero | number): Observable<Hero> {
const id = typeof hero === 'number' ? hero : hero.id;
const url = `${this.heroesUrl}/${id}`;
return this.http.delete<Hero>(url, this.httpOptions).pipe(
tap(_ => this.log(`deleted hero id=${id}`)),
catchError(this.handleError<Hero>('deleteHero'))
);
}
/** PUT: update the hero on the server */
updateHero (hero: Hero): Observable<any> {
return this.http.put(this.heroesUrl, hero, this.httpOptions)
.pipe(
tap(_ => this.log(`updated hero id=${hero.id}`)),
catchError(this.handleError<any>('updateHero'))
);
}
/**
* Handle Http operation that failed.
* Let the app continue.
* @param operation - name of the operation that failed
* @param result - optional value to return as the
* observable result
*/
private handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
// TODO: send the error to remote logging infrastructure
console.error(error); // log to console instead
// TODO: better job of transforming error for
// user consumption
this.log(`${operation} failed: ${error.message}`);
// Let the app keep running by returning an empty result.
return of(result as T);
};
}
/** Log a HeroService message with the MessageService */
private log(message: string) {
this.messageService.add(`HeroService: ${message}`);
}
}
18. Gọi HTTP Request vào Heroes Server trong HeroService
src/app/hero.service.ts (file rất dài, nên ra file riêng để xem)
import { catchError, map, tap } from 'rxjs/operators';
/** GET heroes from the server */
getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
tap(_ => this.log('fetched heroes')),
catchError(this.handleError<Hero[]>('getHeroes', []))
);
}
src/app/hero.service.ts (update)
19. Tạo HeroSearchComponent
ng generate component hero-search
11. Gọi HttpRequest trong Angular
<h2>My Heroes</h2>
<div>
<label>Hero name:
<input #heroName />
</label>
<!-- (click) passes input value to add() and then clears the input -->
<button (click)="add(heroName.value); heroName.value=''">
add
</button>
</div>
<ul class="heroes">
<li *ngFor="let hero of heroes">
<a routerLink="/detail/{{hero.id}}">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</a>
<button class="delete" title="delete hero"
(click)="delete(hero)">x</button>
</li>
</ul>
20. Thêm nút Add/Delete Hero, bỏ class.selected và (onClick) trên <li> trong HeroesComponent
heroes/heroes.component.html
11. Gọi HttpRequest trong Angular
import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
heroes: Hero[];
constructor(private heroService: HeroService) { }
ngOnInit() {
this.getHeroes();
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
add(name: string): void {
name = name.trim();
if (!name) { return; }
this.heroService.addHero({ name } as Hero)
.subscribe(hero => {
this.heroes.push(hero);
});
}
delete(hero: Hero): void {
this.heroes = this.heroes.filter(h => h !== hero);
this.heroService.deleteHero(hero).subscribe();
}
}
21. Thêm hàm add và delete trong HeroesComponent TypeScript file
heroes/heroes.component.ts
11. Gọi HttpRequest trong Angular
/* HeroesComponent's private CSS styles */
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
position: relative;
cursor: pointer;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.heroes li:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.heroes a {
color: #333;
text-decoration: none;
position: relative;
display: block;
width: 250px;
}
.heroes a:hover {
color:#607D8B;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color:#405061;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
min-width: 16px;
text-align: right;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
button {
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
cursor: hand;
font-family: Arial;
}
button:hover {
background-color: #cfd8dc;
}
button.delete {
position: relative;
left: 194px;
top: -32px;
background-color: gray !important;
color: white;
}
22. Thêm styles cho HeroesComponent
heroes/heroes.component.css
11. Gọi HttpRequest trong Angular
<div *ngIf="hero">
<h2>{{hero.name | uppercase}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<div>
<label>name:
<input [(ngModel)]="hero.name" placeholder="name"/>
</label>
</div>
<button (click)="goBack()">go back</button>
<button (click)="save()">save</button>
</div>
23. Thêm nút Save cho HeroDetailComponent
hero-detail/hero-detail.component.html
11. Gọi HttpRequest trong Angular
import { Component, OnInit, Input } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: [ './hero-detail.component.css' ]
})
export class HeroDetailComponent implements OnInit {
@Input() hero: Hero;
constructor(
private route: ActivatedRoute,
private heroService: HeroService,
private location: Location
) {}
ngOnInit(): void {
this.getHero();
}
getHero(): void {
const id = +this.route.snapshot.paramMap.get('id');
this.heroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
goBack(): void {
this.location.back();
}
save(): void {
this.heroService.updateHero(this.hero)
.subscribe(() => this.goBack());
}
}
23. Thêm hàm save() cho HeroDetailComponent TS file
hero-detail/hero-detail.component.ts
11. Gọi HttpRequest trong Angular
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<a *ngFor="let hero of heroes" class="col-1-4"
routerLink="/detail/{{hero.id}}">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</a>
</div>
<app-hero-search></app-hero-search>
24. Gọi component HeroSearch trong Dashboard component
src/app/dashboard/dashboard.component.html
11. Gọi HttpRequest trong Angular
<div id="search-component">
<h4><label for="search-box">Hero Search</label></h4>
<input #searchBox id="search-box" (input)="search(searchBox.value)" />
<ul class="search-result">
<li *ngFor="let hero of heroes$ | async" >
<a routerLink="/detail/{{hero.id}}">
{{hero.name}}
</a>
</li>
</ul>
</div>
25. Tạo HeroSearch component template
hero-search/hero-search.component.html
11. Gọi HttpRequest trong Angular
import { Component, OnInit } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import {
debounceTime, distinctUntilChanged, switchMap
} from 'rxjs/operators';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-hero-search',
templateUrl: './hero-search.component.html',
styleUrls: [ './hero-search.component.css' ]
})
export class HeroSearchComponent implements OnInit {
heroes$: Observable<Hero[]>;
// Subject là 1 giá trị cho Observable
// hoặc là 1 Observable luôn
// Đẩy giá trị vào Subject (Observable) bằng câu lệnh next()
// Vì searchTerm người dùng nhập đến từ tương lđược
// không báo trước được
private searchTerms = new Subject<string>();
constructor(private heroService: HeroService) {}
// Push a search term into the observable stream.
search(term: string): void {
this.searchTerms.next(term);
}
ngOnInit(): void {
this.heroes$ = this.searchTerms.pipe(
// wait 300ms after each keystroke before considering the term
debounceTime(300),
// ignore new term if same as previous term
distinctUntilChanged(),
// switch to new search observable each time the term changes
switchMap((term: string) => this.heroService.searchHeroes(term)),
);
}
}
26. Tạo HeroSearch TS file
hero-search/hero-search.component.ts
this.heroes$ = this.searchTerms.pipe(
// wait 300ms after each keystroke before considering the term
debounceTime(300),
// ignore new term if same as previous term
distinctUntilChanged(),
// switch to new search observable each time the term changes
switchMap((term: string) => this.heroService.searchHeroes(term)),
);
src/app/hero-search/hero-search.component.ts
11. Gọi HttpRequest trong Angular
/* HeroSearch private styles */
.search-result li {
border-bottom: 1px solid gray;
border-left: 1px solid gray;
border-right: 1px solid gray;
width: 195px;
height: 16px;
padding: 5px;
background-color: white;
cursor: pointer;
list-style-type: none;
}
.search-result li:hover {
background-color: #607D8B;
}
.search-result li a {
color: #888;
display: block;
text-decoration: none;
}
.search-result li a:hover {
color: white;
}
.search-result li a:active {
color: white;
}
#search-box {
width: 200px;
height: 20px;
}
ul.search-result {
margin-top: 0;
padding-left: 0;
}
26. Thêm styles HeroSearchComponent
hero-search/hero-search.component.css
Kiến trúc tổng thể của một con app Angular
Kiến trúc app Angular