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

  1. app.component.ts — controller (TypeScript).
  2. app.component.html— view (HTML).
  3. app.component.css— style cho view (CSS).

Các bước làm một project Angular

  1. Tạo project
  2. Chạy project
  3. Code thử (xem phản ứng)
  4. Tạo component Hero
  5. Hiển thị danh sách Hero
  6. Hiển thị chi tiết Hero
  7. Tạo component chi tiết Hero
  8. Tạo service HeroService
  9. 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
  1. --flat: để file ở ngay src/app thay vì trong thư mục của riêng nó.
  2. --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

  1.     path: đường dẫn mong muốn.
  2.     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

  1. 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

  1. route.snapshot: bản sao thông tin của route
  2. 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)

  1. pipe(): gán hành động xử lý phù hợp trong khi request
  2. catchError(): thực thi khi request lỗi
    • handleError(): hàm xử lý lỗi
  3. 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

  1. heroes$ (heroes): heroesObservable, không phải array
  2. | async:*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

  1. debounceTime(300): chờ 300ms thì mới được request tiếp
  2. distinctUntilChanged(): chỉ thực thi khi từ khóa trước khác từ khóa sau
  3. 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

  1. Module: Tập hợp các Components chung 1 mục đích
  2. Component: Một tập hợp 3 file thành 1 view gồm: html, css,ts (các hành động trên view)
    1. Metadata: selector, templateUrl, providers
    2. Data-binding: gọi data ra view {{value}} và truyền data vào file Typescript để xử lý (click event)
    3. Pipes: Xử lý dữ liệu trước khi xuất ra view
    4. Directives: Logic ẩn hiện trên view
  3. ServicesDependency Injection: Chia sẻ logic xử lý dữ liệu giữa các Component
  4. 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

  • 307