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ấu trúc của cả 1 chương trình
Cấu trúc của 1 component
- app.component.ts — controller (TypeScript).
- app.component.html— view (HTML).
- app.component.css— style cho view (CSS).
Các bước làm một project Angular
- Tạo project
- Chạy project
- Code thử (xem phản ứng)
- Tạo component Hero
- Hiển thị danh sách Hero
- Hiển thị chi tiết Hero
- Tạo component chi tiết Hero
- Tạo service HeroService
- Tạo component Message và gắn vào HeroServie
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
- --flat: để file ở ngay src/app thay vì trong thư mục của riêng nó.
- --module=app: đăng ký luôn module trong imports của file AppModule
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
- path: đường dẫn mong muốn.
- component: component sẽ được hiện ra
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
- router-outlet: nơi sẽ chứa view được route
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
- route.snapshot: bản sao thông tin của route
- paramMap: danh sách tham số xuất ra từ URL
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)
- pipe(): gán hành động xử lý phù hợp trong khi request
-
catchError(): thực thi khi request lỗi
- handleError(): hàm xử lý lỗi
- tap(): đọc dữ liệu, hành động với dữ liệu và cho dữ liệu đi tiếp
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
-
heroes$ (
heroes): heroes là Observable, không phải array - | async: vì *ngFor không thể làm việc với Observable, dùng async để chuyển về array luôn, chứ không cần làm trong TS file
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
- debounceTime(300): chờ 300ms thì mới được request tiếp
- distinctUntilChanged(): chỉ thực thi khi từ khóa trước khác từ khóa sau
- switchMap(): chuyển observable khi từ khóa thay đổi
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
- Module: Tập hợp các Components chung 1 mục đích
-
Component: Một tập hợp 3 file thành 1 view gồm: html, css, và ts (các hành động trên view)
- Metadata: selector, templateUrl, providers
- Data-binding: gọi data ra view {{value}} và truyền data vào file Typescript để xử lý (click event)
- Pipes: Xử lý dữ liệu trước khi xuất ra view
- Directives: Logic ẩn hiện trên view
- Services và Dependency Injection: Chia sẻ logic xử lý dữ liệu giữa các Component
- Routing: Hiện Component phù hợp dựa vào URL
Kiến trúc app Angular
Cấu trúc chương trình Angular
By dannguyencoder
Cấu trúc chương trình Angular
Kiến trúc tổng thể của một ứng dụng Angular
- 323