blog
blog copied to clipboard
使用 Angular6 创建一个 CRUD 应用程序--Todolist
五一假期过后,Angular6发布正式版,相关联的UI组件库Material 和 脚手架CLI,也一并发布6。 升级核心依赖:
- TypeScript V2.7
- RxJS V6.0
工作中已经完成一个Angular6项目,这里来写一个简单的Angular6教程。
古语云:君子谋而后动,三思而后行。我们做一个功能,先规划功能细节。
先看个效果图:
在本文中,我们将构建一个Angular6 Todo web应用程序,允许用户:
- 快速创建新的todo,使用输入框输入内容并按回车键
- 切换todo是完成的或不完成的
- 删除不再需要的todo
- 双击修改未完成的todo
- 批量删除已完成todo
- 全选标记所有已完成或者取消全选标记所有未完成
这是一个演示应用程序,我们将一步步从零构建它们。
这里所有的代码都是公开的,所以你可以使用这些代码。
这是一个在线编辑器预览
让我们开始吧!
快速开始
这里看官网文档 快速开始。详细安装指南,这里不在一一介绍。
修改一下package.json
"scripts": {
"start": "ng serve --open",
...
},
这样可以直接使用npm start
启动开发服务器并且自动打开默认浏览器并访问 http://localhost:4200/
。
生成我们的Todo应用程序
现在我们有了Angular-CLI
,我们可以使用它来生成Todo应用程序:
生成我们的Todo应用程序
这里是生成项目的文档
ng new angular-todolist --style=scss
说明:生成一个angular-todolist
项目,css预处理器用scss。
生成文件
满足我们的Todo应用程序的需要,我们需要:
- Todo 类 代表个人待办事项
- TodoService 服务 创建、更新和删除待办事项
- TodoApp 组件 显示界面
- AfterViewFocus 指令 输入框自动获取焦点
我们所有相关应用都放在todoApp组件,在app组件里面使用todoApp组件。
这里是生成文件的文档
生成组件
ng g c todo-app
这样在app文件夹里面就出现todo-app文件夹
生成服务
ng g s todo-app/todo
注意:默认生成的文件都是以app
为开始路径,我们需要放在todo-app
里,所以是todo-app/todo
生成类
ng g cl todo-app/todo
注意:生成组件是c
,生成类是cl
。
生成指令
ng g d todo-app/after-view-focus
我们现在的todo-app
文件夹里结构应该是:
after-view-focus.directive.spec.ts
after-view-focus.directive.ts
todo-app.component.html
todo-app.component.scss
todo-app.component.spec.ts
todo-app.component.ts
todo.service.spec.ts
todo.service.ts
todo.ts
创建Todo类
因为我们使用TypeScript,我们可以使用一个类来表示Todo项目。
让我们打开src/app/todo.ts
并将其内容替换为:
export class Todo {
id: number;
value: string;
done: boolean = false;
edit: boolean = false;
constructor(values: Object = {}) {
Object.assign(this, values);
}
}
我们需要设计数据结构,每个Todo项有三个属性:
- id : number, todo项的唯一ID(可以用全局变量自增,也可以用时间戳,这里用时间戳)
- value : string, 待办事项的内容
- done : boolean, todo项是否完成
- edit : boolean, todo项是否编辑中
构造函数逻辑允许我们在实例化过程中指定属性值:
let todo = new Todo({
title: 'The first todos',
done: false
});
我们可以测试一下,Angular-CLI
提供单元测试和e2e测试,默认生成类文件不会带测试文件,我们需要手动创建一个todo.spec.ts
文件。
import { Todo } from './todo';
describe('Todo', () => {
it('应该创建一个实例', () => {
expect(new Todo()).toBeTruthy();
});
it('应该在构造函数中接受值', () => {
const todo = new Todo({
value: 'hello',
done: true
});
expect(todo.value).toEqual('hello');
expect(todo.done).toEqual(true);
expect(todo.edit).toEqual(false);
});
});
为了保证不受干扰,删除
app.component.spec.ts
文件,把todo-app.component.spec.ts
文件里面代码都注释起来。
为了验证我们的代码是否按预期工作,我们现在可以运行单元测试:
npm test
这将执行业力来运行所有单元测试。如果单元测试失败,可以联系我。
现在我们有了一个Todo类,让我们创建一个Todo服务来为我们管理所有的Todo项。
创建Todo服务
TodoService将负责管理我们的Todo项目。
在以后的文章中,我们将看到如何与REST API
通信,但是现在我们将把所有数据存储在内存存储中。
现在,我们可以将todo管理逻辑添加到src/app/todo.services.ts
中的TodoService中
import { Injectable } from '@angular/core';
import { Todo } from './todo';
@Injectable()
export class TodoService {
// Placeholder for todo's
todos: Todo[] = [];
/** Used to generate unique ID's */
nextId = 0;
constructor() { }
// Simulate POST /todos
addTodo(todo: Todo): TodoService {
todo.id = Date.now();
this.todos.push(todo);
return this;
}
// Simulate DELETE /todos/:id
deleteTodoById(id: number): TodoService {
this.todos = this.todos
.filter(todo => todo.id !== id);
return this;
}
// Simulate POST /todos/delete
deleteAllTodo(): TodoService {
this.todos = this.todos
.filter(todo => !todo.done);
return this;
}
// Simulate PUT /todos/:id
updateTodoById(id: number, values: Object = {}): Todo {
const todo = this.getTodoById(id);
if (!todo) {
return null;
}
Object.assign(todo, values);
return todo;
}
// Simulate GET /todos
getAllTodos(): Todo[] {
return this.todos;
}
// Simulate GET /todos/done
getAllDoneTodos(): Todo[] {
return this.todos.filter(todo => todo.done);
}
// Simulate GET /todos/:id
getTodoById(id: number): Todo {
return this.todos
.filter(todo => todo.id === id)
.pop();
}
// Toggle todo done
toggleTodoDone(todo: Todo) {
const updatedTodo = this.updateTodoById(todo.id, {
done: !todo.done
});
return updatedTodo;
}
}
我们已经完成必备的服务,实际的实现细节的方法不是本文的目的所必需的。这是主要表达意思, 我们的业务逻辑集中在服务。
确保我们的逻辑是预期,我们将单元测试添加到src/app/todo.service.spec
中。
Angular-cli已为我们生成测试模板,我们只需要关心如何实现测试:
import {
inject, TestBed
} from '@angular/core/testing';
import { Todo } from './todo';
import { TodoService } from './todo.service';
describe('Todo Service', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [],
providers: [TodoService]
});
});
describe('#getAllTodos()', () => {
it('应该默认返回一个空数组', inject([TodoService], (service: TodoService) => {
expect(service.getAllTodos()).toEqual([]);
}));
it('应该返回所有待办事项', inject([TodoService], (service: TodoService) => {
const todo1 = new Todo({ value: 'Hello 1', done: false });
const todo2 = new Todo({ value: 'Hello 2', done: true });
service.addTodo(todo1);
service.addTodo(todo2);
expect(service.getAllTodos()).toEqual([todo2, todo1]);
}));
});
describe('#save(todo)', () => {
it('应该自动分配一个时间戳的ID', inject([TodoService], (service: TodoService) => {
const todo1 = service.addTodo(new Todo({ value: 'Hello 1', done: false }));
const todo2 = service.addTodo(new Todo({ value: 'Hello 2', done: true }));
expect(service.getTodoById(todo1.id)).toEqual(todo1);
expect(service.getTodoById(todo2.id)).toEqual(todo2);
}));
});
describe('#deleteTodoById(id)', () => {
it('应该删除相应ID的待办事项', inject([TodoService], (service: TodoService) => {
const todo1 = service.addTodo(new Todo({ value: 'Hello 1', done: false }));
const todo2 = service.addTodo(new Todo({ value: 'Hello 2', done: true }));
expect(service.getAllTodos()).toEqual([todo2, todo1]);
service.deleteTodoById(todo1.id);
expect(service.getAllTodos()).toEqual([todo2]);
service.deleteTodoById(todo2.id);
expect(service.getAllTodos()).toEqual([]);
}));
it('如果没有找到使用相应ID的待办事项,则不应删除任何内容', inject([TodoService], (service: TodoService) => {
const todo1 = service.addTodo(new Todo({ value: 'Hello 1', done: false }));
const todo2 = service.addTodo(new Todo({ value: 'Hello 2', done: true }));
expect(service.getAllTodos()).toEqual([todo2, todo1]);
service.deleteTodoById(3);
expect(service.getAllTodos()).toEqual([todo2, todo1]);
}));
});
describe('#updateTodoById(id, values)', () => {
it('应该返回相应ID和更新的数据todo', inject([TodoService], (service: TodoService) => {
const todo = service.addTodo(new Todo({ value: 'Hello 1', done: false }));
const updatedTodo = service.updateTodoById(todo.id, {
value: 'new value'
});
expect(updatedTodo.value).toEqual('new value');
}));
it('如果未找到待办事项应该返回null', inject([TodoService], (service: TodoService) => {
const todo = service.addTodo(new Todo({ value: 'Hello 1', done: false }));
const updatedTodo = service.updateTodoById(2, {
value: 'new value'
});
expect(updatedTodo).toEqual(null);
}));
});
describe('#toggleTodoDone(todo)', () => {
it('应该返回更新后的待办事项与完成状态', inject([TodoService], (service: TodoService) => {
const todo = new Todo({ value: 'Hello 1', done: false });
service.addTodo(todo);
const updatedTodo = service.toggleTodoDone(todo);
expect(updatedTodo.done).toEqual(true);
service.toggleTodoDone(todo);
expect(updatedTodo.done).toEqual(false);
}));
});
});
检查我们的业务逻辑是否有效,我们运行单元测试:
npm test
好了,现在我们有一个可以通过测试的TodoService,是时候实现应用程序的主要部分了。
创建TodoApp组件
组件是Angular
最小的单元了,整个Angular
应用就是一个颗组件树构成。
我们生成项目时候,angular-cli默认为我们创建了app-root
根组件,我们现在生产的app-todo-app
,放到app.component.html
里面,删除其他html。
一个组件是有3部分组建成:
- 模板结构 todo-app.component.html
- 样式美化 todo-app.component.scss
- 交互行为 todo-app.component.ts
模板和样式也可以内联脚本文件中指定。Angular-CLI默认创建单独的文件,所以在本文中我们将使用单独的文件。
import { Component } from '@angular/core';
@Component({
selector: 'app-todo-app',
templateUrl: './todo-app.component.html',
styleUrls: ['./todo-app.component.scss']
})
export class TodoAppComponent implements OnInit {
constructor() { }
}
我们先来添加组件的视图todo-app.component.html
<header class="header">
<h1>Todos</h1>
<form class="todo-form" (ngSubmit)="addTodo()">
<input class="add-todo" [(ngModel)]="newTodo" name="first" placeholder="What needs to be done?" required="required" autocomplete="off">
<button type="submit" class="add-btn" *ngIf="newTodo.length">+</button>
</form>
</header>
<main class="main" *ngIf="todos.length">
<input class="toggle-all" type="checkbox" [(ngModel)]="allDone" (ngModelChange)="toggleAllTodoDone($event)">
<ul class="todo-list">
<li *ngFor="let todo of todos" [class.completed]="todo.done" (dblclick)="editingTodo(todo)">
<div class="view" *ngIf="!todo.edit">
<input class="toggle" type="checkbox" [checked]="todo.done" (click)="toggleDoneTodo(todo)">
<label>{{ todo.value }}</label>
<button class="destroy" (click)="destroyTodo(todo)"></button>
</div>
<input class="edit" *ngIf="todo.edit" appAfterViewFocus [value]="todo.value" #edit (blur)="cancelEditingTodo(todo)" placeholder="What do you need to write?" (keyup.enter)="editedTodo(todo, edit)">
</li>
</ul>
</main>
<footer class="footer" *ngIf="todos.length">
<span class="todo-count">
<strong>{{ todoCount }}</strong>
<span> items left</span>
</span>
<button class="clear-completed" (click)="destroyAllTodo()" [class.clear-operate]="clearCount">
<span>Clear </span>
<strong>{{ clearCount }}</strong>
<span> done items</span>
</button>
</footer>
来简单说一下Angular模板语法:
- [property]="expression" : 属性设置为表达式的结果
- (event)="statement" : 事件发生时执行语句
- [(property)]="expression" : 创建双向绑定表达式
- [class.special]="expression" : 表达式为真的时候添加特殊的CSS类元素
- [style.color]="expression" : 设置CSS的颜色属性为表达式的结果
更多的Angular模板语法,你应该阅读官方的文档模板的语法。
让我们一一介绍:
整个模板分为3个结构块: header,main,footer
;
先说输入创建一个新的待办事项:
<form class="todo-form" (ngSubmit)="addTodo()">
<input class="add-todo" [(ngModel)]="newTodo" name="first" placeholder="What needs to be done?" required="required" autocomplete="off">
<button type="submit" class="add-btn" *ngIf="newTodo.length">+</button>
</form>
- [(ngModel)]="newTodo" : 添加一个输入值和newTodo之间的双向绑定
- (ngSubmit)="addTodo()": 按enter键和点击+按钮时,使用ngSubmit告诉angular执行addTodo(),提交输入的值
- *ngIf="newTodo.length":newTodo不为空的时候才显示+号按钮
不要担心newTodo或addTodo()从哪里来,我们很快就会讲到那里,现在只需要试着去理解的模板语法。
接下来是一段显示待办事项:
<main class="main" *ngIf="todos.length"></main>
- *ngIf="todos.length" : 当至少有1个todo才显示待办事项容器
在这个部分中,我们循环一个元素来显示每个待办事项:
<li *ngFor="let todo of todos" [class.completed]="todo.done" (dblclick)="editingTodo(todo)"></li>
- *ngFor="let todo of todos" : 遍历所有待办事项,为当前的待办事项分配给一个变量为todo
- [class.completed]="todo.done":当todo.done为真时,给当前li元素添加一个CSS类
- (dblclick)="editingTodo(todo)":双击li元素时,执行editingTodo(),并把当前todo当做参数传递给控制器使用。
最后我们显示待办事项的细节为每个ngFor中的待办事项:
<div class="view" *ngIf="!todo.edit">
<input class="toggle" type="checkbox" [checked]="todo.done" (click)="toggleDoneTodo(todo)">
<button class="destroy" (click)="destroyTodo(todo)"></button>
</div>
<input class="edit" *ngIf="todo.edit" appAfterViewFocus [value]="todo.value" #edit (blur)="cancelEditingTodo(todo)" placeholder="What do you need to write?" (keyup.enter)="editedTodo(todo, edit)">
- *ngIf="!todo.edit" : 当前todo不在编辑中
- [checked]="todo.done": 给input元素绑定checked属性
- (click)="toggleDoneTodo(todo)": 单击复选框时执行toggleDoneTodo(todo)
- (click)="destroyTodo(todo)": 单击销毁按钮执行destroyTodo(todo)
- *ngIf="todo.edit" : 当前todo正在编辑中
- [value]="todo.value": 给input元素绑定value属性
- (blur)="cancelEditingTodo(todo)":失去焦点取消编辑执行cancelEditingTodo(todo)
- (keyup.enter)="editedTodo(todo, edit)": 回车确认编辑执行editedTodo(todo, edit)
appAfterViewFocus是angular属性型指令,在 Angular 中有三种类型的指令:
- 组件 — 拥有模板的指令
- 结构型指令 — 通过添加和移除 DOM 元素改变 DOM 布局的指令
- 属性型指令 — 改变元素、组件或其它指令的外观和行为的指令。
注意: 为什么要写这个指令,它作用是什么?它作用是当编辑时,input出现时候,自动获取焦点,不用用户再次去点击输入框,触发获取焦点事件,还有一个更重要的原因,如果没有焦点,失去焦点事件就无法执行,这样输入就不会被隐藏。它的写法很简单:
import { Directive, AfterViewInit, ElementRef } from '@angular/core';
@Directive({
selector: '[appAfterViewFocus]'
})
export class AfterViewFocusDirective implements AfterViewInit {
constructor(private elementRef: ElementRef) { }
ngAfterViewInit() {
this.elementRef.nativeElement.focus();
}
}
- ngAfterViewInit: 指令生命周期钩子,,初始化完组件视图及其子视图之后调用。 简单理解就是该dom已经出现在页面上了,js可以正常操作了。
- ElementRef: 简单解释,允许直接访问DOM,这里是宿主,也是使用者。
.nativeElement
拿到就是一个HTMLElement
, 这里是input这个dom。
#edit
是angular模板引用变量;
- 模板引用变量使用井号(#)来声明引用变量。
- 模板引用变量通常用来引用模板中的某个DOM元素,它可以引用Angular组件或指令或 Web Component。
- 我们可以在当前模板的任何地方使用模板引用变量。
注意: 这里拿到就是input这个dom,我们可以直接操作获取它上面的属性和方法。
为什么不用双向绑定[(ngModel)]
?
什么是双向绑定: 数据模型(Module)和视图(View)之间的双向绑定。
如果使用双向绑定,我们修改以后,我们数据就直接跟着一起被修改,那么我们要操作取消操作怎么办,增加一个临时的属性来记录它,取消时候就直接回滚,确认就直接清除这个临时属性。
如果不使用双向绑定,我们先赋值给视图,视图修改以后,我们的数据还没有变,取消操作直接取消就行,确认操作,拿到dom引用,把dom的值去更新数据。
关于全选效果
<input class="toggle-all" type="checkbox" [(ngModel)]="allDone" (ngModelChange)="toggleAllTodoDone($event)">
- [(ngModel)]="allDone" : 这里使用双向绑定,来监听allDone变化,如果是true,就选中,如果false就不勾选。
- (ngModelChange)="toggleAllTodoDone($event)":每次allDone变化都会执行toggleAllTodoDone($event)
我们来说最后一块统计结构:
<footer class="footer" *ngIf="todos.length">
<span class="todo-count">
<strong>{{ todoCount }}</strong>
<span> items left</span>
</span>
<button class="clear-completed" (click)="destroyAllTodo()" [class.clear-operate]="clearCount">
<span>Clear </span>
<strong>{{ clearCount }}</strong>
<span> done items</span>
</button>
</footer>
- *ngIf="todos.length" : 当至少有1个todo才显示待办统计容器
- {{ todoCount }}:显示当前有多少未完成的todos(这是angular模板表达式绑定和上面介绍属性表达式绑定一样)
- {{ clearCount }}:显示当前有多少已完成的todos
- (click)="destroyAllTodo()":单击按钮时执行destroyAllTodo()
- [class.clear-operate]="clearCount":如果clearCount不为0时候,给当前按钮添加一个类
clear-operate
模板我们已经介绍完,关于css不是我们重点,这里忽略讲解。
接下来我们该介绍todo-app.component.ts
:
首先需要引入依赖
import { TodoService } from './todo.service';
import { Todo } from './todo';
接下来就是angular特色之一依赖注入,这里不过多介绍。
@Component({
selector: 'app-todo-app',
templateUrl: './todo-app.component.html',
styleUrls: ['./todo-app.component.scss'],
providers: [TodoService]
})
export class TodoAppComponent {
newTodo: string = '';
constructor(
private todoService: TodoService
) { }
注意:providers可以在模块下,也可以在组件里,这也限定他们使用范围。模块里面注册,适用于该模块下所有的组件,服务,指令等;组件里面注册,只适用于当前组件和子组件。
- newTodo 提供一个属性变量,供模板双向绑定使用,绑定输入新的todo值。
每当视图中输入值的变化,更新组件实例的价值。当组件实例中的值改变,视图中输入元素中的值的变化。
接下来,我们实现我们在视图中使用的所有方法。
/**
* add todo
* @memberof TodoAppComponent
*/
addTodo(): void {
if (!this.newTodo) {
return alert('What do you need to write?');
}
this.todoService.addTodo(new Todo({
value: this.newTodo
}));
this.newTodo = '';
}
/**
* destroy todo
* @memberof TodoAppComponent
*/
destroyTodo(todo: Todo): void {
this.todoService.deleteTodoById(todo.id);
}
/**
* destroy done todo
* @memberof TodoAppComponent
*/
destroyAllTodo(): void {
if (!this.clearCount) {
return;
}
if (!confirm('Do you need to delete the selected one?')) {
return;
}
this.todoService.deleteAllTodo();
}
/**
* toggle todo done
* @memberof TodoAppComponent
*/
toggleDoneTodo(todo: Todo): void {
this.todoService.toggleTodoDone(todo);
}
/**
* toggle all todo done
* @memberof TodoAppComponent
*/
toggleAllTodoDone(event: boolean): void {
this.todos.forEach(item => item.done = event);
}
/**
* editing todo
* @memberof TodoAppComponent
*/
editingTodo(todo: Todo): void {
if (!todo.done) {
todo.edit = true;
}
}
/**
* cancel editing todo
* @memberof TodoAppComponent
*/
cancelEditingTodo(todo: Todo): void {
todo.edit = false;
}
/**
* edited todo
* @memberof TodoAppComponent
*/
editedTodo(todo: Todo, input: HTMLInputElement): void {
todo.value = input.value;
todo.edit = false;
}
/**
* get todos
* @memberof TodoAppComponent
*/
get todos(): Todo[] {
return this.todoService.getAllTodos();
}
/**
* get todos all done be get todos
* @memberof TodoAppComponent
*/
get allDone(): boolean {
const todos = this.todos;
return todos.length && todos.filter(item => item.done).length === todos.length;
}
/**
* get todos all not done number
* @memberof TodoAppComponent
*/
get todoCount(): number {
return this.todos.filter(item => !item.done).length;
}
/**
* get todos all done number
* @memberof TodoAppComponent
*/
get clearCount(): number {
return this.todos.filter(item => item.done).length;
}
- addTodo:添加操作,判断用户有没有输入,只有输入才添加,添加完成清空
newTodo
属性值 - destroyTodo:删除操作,直接调用服务对应方法即可
- destroyAllTodo:批量清除已完成项,先判断,二次确认,在调用服务对应方法即可
- editingTodo:启动编辑,如果当前todo已完成状态,不能编辑
- cancelEditingTodo:取消编辑,隐藏输入框
- editedTodo:确认编辑,获取dom值修改todo值
- todos:获取服务方法
- allDone:获取是不是全选状态
- todoCount:获取未完成todo个数
- clearCount:获取已完成todo个数
这里有4个
get
,在Typescript
叫存取器
, 通过getters/setters来截取对对象成员的访问。 它能帮助我们有效的控制对对象成员的访问。这里只要控制器里面值发送变化,模板就会更着改变,很方便。
注意:无论是服务还是组件里,都是需要熟练使用原生数据操作方法,比如数组,对象,字符串等。这里主要使用数组相关方法,如果你对这些还不熟,请赶紧去提升一下。es6以后又新增很多方法,操作数据会更方便。angular是数据驱动,如果不会玩转操作,基本很难继续下去。
功能很小,应该不言自明todoService我们代表所有的业务逻辑。
委派业务逻辑服务是良好的编程实践,因为它能让我们集中管理和测试业务逻辑。
我们还为大家编写一个E2E
测试用例,可以查阅e2e
文件里面文件,运行命名npm run e2e
即可。
部署到GitHub页面
github给我们每个项目都运行有一个预览页面,我们叫它github-pages
。
提交代码
- 先打包本地代码
ng build --prod --base-href https://jiayisheji.github.io/angular-todolist/
注意:github-pages预览地址是 你的用户名.github.io/你的项目名/
--base-href:修改html里面的base
的href
属性,如果有路由必须要使用的。
- 提交dist文件夹的内容到
gh-pages
分支
git add -f dist && git commit -n -m \"(release): git-pages\" && git subtree push --prefix dist origin gh-pages
注意:就是打包以后的目录,需要特别注意一下,angular-cli6是一个多工程的脚手架,打包后生成的是dist/angular-todolist
,我们最终需要上传是这个文件夹里面的内容,那么就需要改脚本。
git add -f dist && git commit -n -m \"(release): git-pages\" && git subtree push --prefix dist/angular-todolist origin gh-pages
- 提交本地代码到远程master并打tags
git push --follow-tags origin master
我在所有项目里面都会用到它
- 写成npm命令
"_github": "ng build --prod --base-href https://jiayisheji.github.io/angular-todolist/",
"_publish": "git add -f dist && git commit -n -m \"(release): git-pages\" && git subtree push --prefix dist/angular-todolist origin gh-pages",
"git-pages": "npm run _github && npm run _publish"
"release":"git push --follow-tags origin master"
运行命令
npm run git-pages
npm run release
代码提交需要去github,项目下设置里面开启github-pages
.
开启 Github-pages
- 打开你的项目,点击设置
- ctrl+f 搜索
GitHub Pages
- 点击设置分支,默认是none,选择
gh-pages branch
- 点击
save
.
就好出现你的
Github Pages
链接,你可以做代码演示,分享给其他小伙伴观看,也可以做静态blog
。
注意:一旦启用就不能再选择none,只能你删除项目。你删除分支,访问就会出现404。
总结
毫无疑问,Angular是一个平台。一个非常强大前端框架!
我们讨论了许多让我们回顾所学在本文中:
- 我们学习了如何安装Angular-CLI和它节省了多少时间为我们创建新的应用程序或功能添加到现有的应用程序。
- 我们学会了如何实现业务逻辑的Angular服务和如何测试我们的业务逻辑使用单元测试。
- 我们学习了如何使用一个组件与用户交互以及如何委派逻辑服务使用依赖注入。
- 我们学习了Angular模板语法的基本知识,并简要介绍了Angular依赖注入是如何工作的。
- 我们学习了如何编写单元测试和E2E测试。
- 最后,我们学会了如何快速部署应用程序到GitHub页面。
麻雀虽小,五脏俱全,Todo应用看起来,功能很简单,其实它里面功能可以做很多衍生,都是我们平常业务需要的,比如购物车, 全选等。
这个有个类似的变种需求功能:我也不知道叫什么名字,antd里面叫穿梭框
。这就留个大家一个作业吧。
如果你不知道如何下手,可以跟我交流。