Angular-cli 6.x +TS+Json-server打造经典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中


五、GITHUB源码地址

https://github.com/RiversCoder/angular-todo-app

猜你喜欢

转载自blog.csdn.net/WU5229485/article/details/83046781