Angular-cli 6.x +TS打造經典Todo案例
一、閒嘮嗑幾句
以前用Angular的時候也只是用的js構建的應用,還未涉及ts;趁這次有機會,有時間用ts、angular的腳手架工具angular-cli構建一個經典的todo案例;幾乎所有的vue、react的基礎經典案例都離不開Todo List的應用構建。所以這次也是以Todo案例,來簡單瞭解下ts構建單頁面應用,以及請求響應邏輯處理。這次後端的邏輯主要採用前端工具json-server偽裝處理後端邏輯,返回REST API;
最終效果預覽圖:
二、開始配置環境
1.安裝 Angular-cli 2.初始化一個自定義的anglar專案 3.全域性安裝json-server 4.定義基本的資料庫json檔案 5.開啟專案服務,開啟json後臺服務
注:
1.這部分可以參考我的其他博文:
https://blog.csdn.net/WU5229485/article/details/82917389 https://blog.csdn.net/WU5229485/article/details/83028007
2.json資料庫檔案(db,json)
{ "todos": [ { "id": 1, "title": "Read SitePoint article", "complete": false }, { "id": 2, "title": "Clean inbox", "complete": false }, { "id": 3, "title": "Make restaurant reservation", "complete": false } ] }
三、建立應用的基礎服務配置
1.建立開發環境和生產環境的分別對應的URL
(1)在開發環境和生產環境儲存不同的API URL
(2)Angular-cli 預設提供兩種環境配置:
src/environments/environment.ts # 開發環境
src/environments/environment.prod.ts. # 上線環境
(3)命令列動態載入( 就不用手動改程式碼啦 ~ )
ng serve 或者 ng build 預設載入開發環境 src/environments/environment.ts ng serve --environment prod 或者 ng build --environment prod 預設載入上線環境 src/environments/environment.prod.ts
2.建立後臺增刪改查處理邏輯(基類、控制類)
(1)建立Todo基類,定義應用所需要的公共屬性
export class Todo {
id: number;
title = '';
complete = false;
constructor(values: Object = {}) {
Object.assign(this, values);
}
}
(2)建立 TodoDataService 控制類,AppServer類中的方法,向其AppServer類方法中傳遞Todo引數
import {Injectable} from '@angular/core';
import {Todo} from './todo';
@Injectable()
export class TodoDataService {
// Placeholder for last id so we can simulate
// automatic incrementing of ids
lastId: number = 0;
// Placeholder for todos
todos: Todo[] = [];
constructor() {
}
// POST /todos
addTodo(todo: Todo): TodoDataService {}
// DELETE /todos/:id
deleteTodoById(id: number): TodoDataService {}
// PUT /todos/:id
updateTodoById(id: number, values: Object = {}): Todo {}
// GET /todos
getAllTodos(): Todo[] {}
// GET /todos/:id
getTodoById(id: number): Todo {}
// Toggle todo complete
toggleTodoComplete(todo: Todo){}
}
(3)建立TodoDataService 控制類的單元測試
import { TestBed, inject } from '@angular/core/testing';
import { TodoDataService } from './todo-data.service';
import { ApiService } from './api.service';
import { ApiMockService } from './api-mock.service';
describe('TodoDataService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
TodoDataService,
{
provide: ApiService,
useClass: ApiMockService
}
]
});
});
it('should ...', inject([TodoDataService], (service: TodoDataService) => {
expect(service).toBeTruthy();
}));
});
3. 建立與後臺聯調的AppServer類,在類中實現對db.json資料庫檔案的增刪改查,然後在TodoDataService例項化AppSrever類,呼叫其內部公共方法,返回db.json中的資料,賦值給Todo類中的屬性
import { Injectable } from '@angular/core';
import { environment } from 'environments/environment';
import { Http, Response } from '@angular/http';
import { Todo } from './todo';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
const API_URL = environment.apiUrl;
@Injectable()
export class ApiService {
constructor(
private http: Http
) {
}
public getAllTodos(): Observable<Todo[]> {
return this.http
.get(API_URL + '/todos')
.map(response => {
const todos = response.json();
return todos.map((todo) => new Todo(todo));
})
.catch(this.handleError);
}
public createTodo(todo: Todo): Observable<Todo> {
return this.http
.post(API_URL + '/todos', todo)
.map(response => {
return new Todo(response.json());
})
.catch(this.handleError);
}
public getTodoById(todoId: number): Observable<Todo> {
return this.http
.get(API_URL + '/todos/' + todoId)
.map(response => {
return new Todo(response.json());
})
.catch(this.handleError);
}
public updateTodo(todo: Todo): Observable<Todo> {
return this.http
.put(API_URL + '/todos/' + todo.id, todo)
.map(response => {
return new Todo(response.json());
})
.catch(this.handleError);
}
public deleteTodoById(todoId: number): Observable<null> {
return this.http
.delete(API_URL + '/todos/' + todoId)
.map(response => null)
.catch(this.handleError);
}
private handleError (error: Response | any) {
console.error('ApiService::handleError', error);
return Observable.throw(error);
}
}
四、元件互動
1. 一共包括下列元件:
Todos 最外層盒子:app.component Todos 頭部:todo-list-header Todos 腳部:todo-list-footer Todos 列表盒子:todo-list Todos 單個列表項:todo-list-item
2. 新增一條Todo列表項邏輯,涉及元件(todo-list-header、app.component):
(1)在todo-list-header中,回車實現新增操作
2-1-1.結構:
<header class="header">
<h1>Todos</h1>
<app-site-header></app-site-header>
<input class="new-todo" placeholder="What needs to be done?" autofocus="" [(ngModel)]="newTodo.title"
(keyup.enter)="addTodo()">
</header>
2-1-2. 邏輯操作
import { Component, Output, EventEmitter } from '@angular/core';
import { Todo } from '../todo';
@Component({
selector: 'app-todo-list-header',
templateUrl: './todo-list-header.component.html',
styleUrls: ['./todo-list-header.component.css']
})
export class TodoListHeaderComponent {
newTodo: Todo = new Todo();
@Output()
add: EventEmitter<Todo> = new EventEmitter();
constructor() {
}
addTodo() {
this.add.emit(this.newTodo);
this.newTodo = new Todo();
}
}
(2)app.component元件中呼叫todo-list-header,並且傳遞方法繫結
2-2-1.結構:
<app-todo-list-header (add)="onAddTodo($event)" ></app-todo-list-header>
2-2-2. 邏輯操作:
onAddTodo(todo) {
this.todoDataService
.addTodo(todo)
.subscribe(
(newTodo) => {
this.todos = this.todos.concat(newTodo);
}
);
}
(3)然後把更新的todos傳遞給子元件todo-list-footer、todo-list
<app-todo-list [todos]="todos" ></app-todo-list>
<app-todo-list-footer [todos]="todos" ></app-todo-list-footer>
3. 刪除和更新一條Todo列表項邏輯
(1)觸發刪除和更改選中狀態的元件todo-list-item
3-1-1. 結構:
<div class="view">
<input class="toggle" type="checkbox" (click)="toggleTodoComplete(todo)" [checked]="todo.complete">
<label>{{todo.title}}</label>
<button class="destroy" (click)="removeTodo(todo)"></button>
</div>
3-1-2. 邏輯處理
@Input() todo: Todo;
@Output()
remove: EventEmitter<Todo> = new EventEmitter();
@Output()
toggleComplete: EventEmitter<Todo> = new EventEmitter();
constructor() {
}
toggleTodoComplete(todo: Todo) {
this.toggleComplete.emit(todo);
console.log(todo);
}
removeTodo(todo: Todo) {
this.remove.emit(todo);
}
(2)由於toggleComplete和remove方法是父元件todo-list傳遞過來的方法,所以需要在負元件中實現
3-2-1. todo-list元件中呼叫toto-list-item元件
<section class="main" *ngIf="todos.length > 0">
<ul class="todo-list">
<li *ngFor="let todo of todos" [class.completed]="todo.complete">
<app-todo-list-item
[todo]="todo"
(toggleComplete)="onToggleTodoComplete($event)"
(remove)="onRemoveTodo($event)"></app-todo-list-item>
</li>
</ul>
</section>
3-2-2. 父元件todo-list邏輯處理
@Input()
todos: Todo[];
@Output()
remove: EventEmitter<Todo> = new EventEmitter();
@Output()
toggleComplete: EventEmitter<Todo> = new EventEmitter();
constructor() {
}
onToggleTodoComplete(todo: Todo) {
this.toggleComplete.emit(todo);
}
onRemoveTodo(todo: Todo) {
this.remove.emit(todo);
}
(3)在元件todo-list中,remove和toggleComplete方法依舊是其父元件傳遞過來,所以需要在todo-list父元件app.component元件中實現
3-3-1. 結構元件呼叫
<app-todo-list
[todos]="todos"
(toggleComplete)="onToggleTodoComplete($event)"
(remove)="onRemoveTodo($event)"
></app-todo-list>
3-3-2. 邏輯處理
onToggleTodoComplete(todo) {
this.todoDataService
.toggleTodoComplete(todo)
.subscribe(
(updatedTodo) => {
todo = updatedTodo;
}
);
}
onRemoveTodo(todo) {
this.todoDataService
.deleteTodoById(todo.id)
.subscribe(
(_) => {
this.todos = this.todos.filter((t) => t.id !== todo.id);
}
);
}
* 資料傳遞總結:
整個資料經歷了兩次連續傳遞:
第一次: 元件todo-list-item傳遞到todo-list中 第二次: 元件todo-list傳遞到app.component中