当前位置:   article > 正文

Angular2 实例(一):TODO MVC

to do mvc .ts

本文使用 Angular 2 搭建一个 TODO MVC 的例子。如果你不知道什么是 TODO MVC 的话,引用官方的一句话是:“ToDoMVC Helping you select an MV* framework”。如果你没听说过什么是 TODO MVC,下图是本文完成后的大概样子。

todo-demo

本文需要做到:

  • 使用 Angular Cli 初始化一个 TODO MVC 项目。
  • 创建一个 Todo 类。用来描述 todo 的信息。
  • 创建一个 TodoService,用来对 todo 信息的增删改查。
  • 使用 AppComponent 来向用户展示。

1. 使用 Angular CLI 来初始化你的 Todo Application

使用 Angular CLI 是一个最简单高效的方法来创建你的 Angular 项目。如果你对 Angular 还不熟悉的话,可以看看我之前写的一篇文章,对 Angular 的历史,核心及项目结构有一个了解。

  • 安装 Angular CLI

    新版的angular-cli已改名成@angular/cli,更符合angular官方的命名规则

    >npm install -g @angular/cli

    等待安装完成后,若没有报错信息,在cmd中输入一下命令进行验证

    >ng -v

    1. _ _ ____ _ ___
    2. / \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
    3. / △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | |
    4. / ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | |
    5. /_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___|
    6. |___/
    7. @angular/cli: 1.1.0
    8. node: 6.10.3
    9. os: win32 x64

    出现以上结果,证明 angular cli 安装成功

  • 初始化你的项目

    使用 angular-cli 创建第一个工程。ng new 工程名

    >ng new todo-app

    请耐心等待。 创建新项目需要花费很多时间,安装npm包需要比较长的时间。

  • 启动服务

    >cd todo-app

    >ng serve

    正常情况下,启动成功后浏览器会自动打开。或者你可以使用 http://localhost:4200/. 来访问。

2. 创建 Todo Class

  • 使用 Angular CLI 创建 Todo 对象

    ng g cl Todo

    该命令会创建一个 TypeScript 类:

    src/app/todo.ts

    文件 todo.ts 内容很简单,如下(当然你也可以手动创建):

    1. export class Todo {
    2. }
  • 修改 todo 对象

    1. export class Todo {
    2. id: number;
    3. title: string = '';
    4. complete: boolean = false;
    5. constructor(values: Object = {}) {
    6. Object.assign(this, values);
    7. }
    8. }

    在这个类里面我们定义了三个属性和一个构造函数:

    • id: number类型, todo 的 id

    • title: string类型, todo 的标题

    • complete: boolean类型, 用来表示 todo 是否已经完成

    • constructor:构造函数,方便我们 new 一个 todo 对象:

2. 创建 TodoService

TodoService 用来管理 Todo 实例的增删改查功能。这里我们会把数据直接保存在内存里。在实际的生产中一般会访问远程接口的 API 等,但这不是本文的重点。

  • 创建 TodoService 对象

    ng g s Todo

    该命令会创建一个两个文件:

    src\app\todo.service.spec.ts

    src\app\todo.service.ts

    todo.service.ts :

    1. import { Injectable } from '@angular/core';
    2. @Injectable()
    3. export class TodoService {
    4. constructor() { }
    5. }

    todo.service.spec.ts (测试):

    1. import { TestBed, inject } from '@angular/core/testing';
    2. import { TodoService } from './todo.service';
    3. describe('TodoService', () => {
    4. beforeEach(() => {
    5. TestBed.configureTestingModule({
    6. providers: [TodoService]
    7. });
    8. });
    9. it('should be created', inject([TodoService], (service: TodoService) => {
    10. expect(service).toBeTruthy();
    11. }));
    12. });
  • 修改 todo.service.ts 增加逻辑代码

    1. import { Todo } from './todo';
    2. import { Injectable } from '@angular/core';
    3. @Injectable()
    4. export class TodoService {
    5. // 增加 todo 时模拟自增id
    6. id: number = 0;
    7. // 用于在内存里保存 todo 信息
    8. todos: Todo[] = [];
    9. constructor() { }
    10. add(todo: Todo): Todo {
    11. if (!todo.id) {
    12. todo.id = ++this.id;
    13. }
    14. this.todos.push(todo);
    15. return todo;
    16. }
    17. deleteById(id: number): void {
    18. this.todos = this.todos
    19. .filter(todo => todo.id !== id);
    20. }
    21. update(todo: Todo): Todo {
    22. let t = this.findById(todo.id);
    23. if (!t) {
    24. return null;
    25. }
    26. Object.assign(t, todo);
    27. return t;
    28. }
    29. findById(id: number): Todo {
    30. return this.todos
    31. .filter(todo => todo.id === id)
    32. .pop();
    33. }
    34. findAll(): Todo[] {
    35. return this.todos;
    36. }
    37. toggleTodoComplete(todo: Todo){
    38. todo.complete = !todo.complete;
    39. let u = this.update(todo);
    40. return u;
    41. }
    42. }
  • 修改 todo.service.spec.ts 增加测试代码

    编写完 todo.service.ts 逻辑代码后,你需要确认你的服务接口是不是正常。这时候需要在todo.service.spec.ts编写 TodoService 的测试代码

    1. import { TestBed, inject } from '@angular/core/testing';
    2. import { TodoService } from './todo.service';
    3. import { Todo } from './todo';
    4. describe('TodoService', () => {
    5. beforeEach(() => {
    6. TestBed.configureTestingModule({
    7. providers: [TodoService]
    8. });
    9. });
    10. it('should be created', inject([TodoService], (service: TodoService) => {
    11. expect(service).toBeTruthy();
    12. }));
    13. it('get all todos', inject([TodoService], (service: TodoService) => {
    14. expect(service.findAll()).toEqual([]);
    15. }));
    16. it('add todo ', inject([TodoService], (service: TodoService) => {
    17. let todo1 = new Todo({ title: 'test', complete: false });
    18. service.add(todo1);
    19. expect(service.findById(1)).toEqual(todo1);
    20. }));
    21. it('toggle todo complete', inject([TodoService], (service: TodoService) => {
    22. let todo = new Todo({title: 'test', complete: false});
    23. service.add(todo);
    24. let updatedTodo = service.toggleTodoComplete(todo);
    25. expect(updatedTodo.complete).toEqual(true);
    26. service.toggleTodoComplete(todo);
    27. expect(updatedTodo.complete).toEqual(false);
    28. }));
    29. });

    这里只写了部分测试代码,使用一下命令测试:

    ng test

    运行后浏览器会自动打开

    todo-test

  • 看看 todo.service.spec.ts 的结构

    ng test 使用 jasmine 来进行测试,关于更多的内容你可以查看 jasmine 官方文档

    jasmine

    测试代码会调用全局的 jasmine 函数 describe 。describe 函数包含两个参数,一个描述,另外一个便是测试方法。

    1. beforeEach(() => {
    2. TestBed.configureTestingModule({
    3. providers: [TodoService]
    4. });
    5. });

    TestBed 是 @angular/core/testing 提供的用来配置和创建 Angular Test 模块。我们使用 TestBed.configureTestingModule()方法配置相关信息。 providers 属性告诉测试模块,当我们运行测试用例时需要什么东西。

    1. it('get all todos', inject([TodoService], (service: TodoService) => {
    2. expect(service.findAll()).toEqual([]);
    3. }));

    it 类似于 java 里的测试单元,该方法的第一个参数是一个名称,第二个参数是真正的测试方法。TestBad 注册器会为我们注入TodoService,以便我们在我们的测试方法里面访问到这个service。

3. 编写 AppComponent 组件

当我们初始化完这个项目的时候,Angular CLI 已经自动为我们创建了一个主组件 AppComponent ,包含了一下4个文件:

  1. src/app/app.component.css // css 样式文件
  2. src/app/app.component.html // html 结构文件
  3. src/app/app.component.spec.ts // 测试文件
  4. src/app/app.component.ts //逻辑代码

到这里整个

  • 修改 app.component.html

    1. 我们先看一下app.component.html原来的样子:
  • 编写 app.component.ts

    1. 我们在先看一下 Angular CLI 默认为我们创建的这个文件里都有什么:

      1. <h1>
      2. {{title}}
      3. </h1>
    2. 我们需要修改成我们的需要的结构:

      1. <section class="todoapp">
      2. <header class="header">
      3. <h1>Todos</h1>
      4. <input class="new-todo" placeholder="What needs to be done?" autofocus="" [(ngModel)]="newTodo.title" (keyup.enter)="addTodo()">
      5. </header>
      6. <section class="main" *ngIf="todos.length > 0">
      7. <ul class="todo-list">
      8. <li *ngFor="let todo of todos" [class.completed]="todo.complete">
      9. <div class="view">
      10. <input class="toggle" type="checkbox" (click)="toggleTodoComplete(todo)" [checked]="todo.complete">
      11. <label>{{todo.title}}</label>
      12. <button class="destroy" (click)="removeTodo(todo)"></button>
      13. </div>
      14. </li>
      15. </ul>
      16. </section>
      17. <footer class="footer" *ngIf="todos.length > 0">
      18. <span class="todo-count"><strong>{{todos.length}}</strong> {{todos.length == 1 ? 'item' : 'items'}} left</span>
      19. </footer>
      20. </section>
    3. 如果你对上面的模版语法还不熟悉,没关系,这里对 Angular 2 的模版语法做一个简单的介绍:

      • [property]="expression": 属性绑定,会将表达式的值设置给 property 属性。
      • [style.color]="expression": 属性绑定,会将表达式的值设置给样式属性 color。
      • [class.special]="expression": 属性绑定,如果表达式为真,则为 class 属性添加 special 样式。
      • (event)="statement": 事件绑定,当发生 event 事件时执行 statement。
      • [(property)]="expression": 双向数据绑定,表达式的值变化会影响 property。property 的值变化也会影响表达式的值
  • app.component.html 各个部分解析

    1. 首先在最上面是一个 input ,用来创建新的todo对象:

      <input [(ngModel)]="newTodo.title" (keyup.enter)="addTodo()" 其他属性 >
      
      • [(ngModel)]="newTodo.title" :

        双向数据绑定,在这里会绑定 newTodo.title 和这个 input 的 value 值。

      • (keyup.enter)="addTodo()" :

        事件绑定,当回车键发送 keyup 事件时,执行 addTodo() 这个方法。

    这里我们还没有在 AppComponent 里定义 newTodo 和 addTodo。 很快我们就会讲到,这里先试着了解 Angular 模版的语法。

    1. 接下来是一个 section 里面用于定义 todo 列表的展示方式:

      <section class="main" *ngIf="todos.length > 0">
      
      • *ngIf="todos.length > 0" :

        angular 内置指令,当 todos.length > 0 时才显示 section 和 section 的子元素。

    2. 接下来是 ul 里套着一个li:

      <li *ngFor="let todo of todos" [class.completed]="todo.complete">
      
      • *ngFor="let todo of todos" :

        angular 内置指令,循环 todos 对象,生成 n 条 li 标签,n = todos.length。并为每条 li 赋值一个 todo 对象。

      • [class.completed]="todo.complete" :

        当 todo.complete 等于 true 时,为该标签的 class 添加 completed 。

    3. 最后是是一个 view 用于展示每个 todo 的详细信息

      1. <div class="view">
      2. <input type="checkbox" (click)="toggleTodoComplete(todo)" [checked]="todo.complete">
      3. <label>{{todo.title}}</label>
      4. <button (click)="removeTodo(todo)"></button>
      5. </div>
      • (click)="toggleTodoComplete(todo)" :

        当发生点击事件时,会执行 toggleTodoComplete 方法 todo 对象作为参数传入。

      • [checked]="todo.complete" :

        绑定 todo.complete 的值到 checked 属性。

      • (click)="removeTodo(todo)" :

        当点击 button 按钮时,执行 removeTodo 方法,并将 todo 对象作为参数传入。

到这里整个 app.component.html 的所有部门都介绍完毕。当然你可能会一头雾水,里面的 todos ,newTodo 还是 addTodo() 等,都是些什么东西。他们要定义在哪里? 又是如何访问的。别着急,下面我们马上就会讲到。

  • 编写 app.component.ts

    1. 我们在先看一下 Angular CLI 默认为我们创建的这个文件里都有什么:

      1. import { Component } from '@angular/core';
      2. @Component({
      3. selector: 'app-root',
      4. templateUrl: './app.component.html',
      5. styleUrls: ['./app.component.css']
      6. })
      7. export class AppComponent {
      8. title = 'app works!';
      9. }

      这个文件很简单。首先是一个 @Component 的装饰器,装饰器作用在类上用来告诉 Angular 这个类的一些附加属性。而这些附加的属性就是称之为元数据

      • selector: 'app-root':

        css3 选择器,当 Angular 在模版里发现了 <app-root> 这个标签后,便会用本组件去渲染。

      • templateUrl: './app.component.html :

        模版Url,这里就是我们上面一小节讲到的 app.component.html。用于告诉 Angular 本组件该如何渲染。

      • styleUrls: ['./app.component.css'] :

        css Url,样式文件的路径。一个完美的 css 能让我们的组件看起来更赏心悦目。

    2. 接下来,我们要为 app.component.ts 注入 TodoService 服务。

      1. import {TodoService} from './todo.service';
      2. @Component({
      3. // ...
      4. providers: [TodoService]
      5. })
      6. export class AppComponent {
      7. // ...
      8. constructor(private todoService: TodoService) {
      9. }
      10. }
      • providers: [TodoService]

        我们在元数据里添加了一个 providers 属性。Angular 会根据 providers 的值会到注入器中查找是否已经有 TodoService 实例,如果没有,会自动帮我们实例化一个 TodoService 对象。并存放在注入器中。

      • constructor(private todoService: TodoService) {} :

        这句话便是真正的注入, Angular 注入器会为这个组件注入 TodoService 的实例,并赋值给 todoService 属性。其实这里等价于:

        1. // ...
        2. export class AppComponent {
        3. private todoService:TodoService;
        4. // ...
        5. constructor(todoService: TodoService) {
        6. this.todoService = todoService;
        7. }
        8. }
    3. 增加我们的逻辑代码:

      1. import { Component } from '@angular/core';
      2. import { TodoService } from './todo.service';
      3. import { Todo } from './todo';
      4. @Component({
      5. selector: 'app-root',
      6. templateUrl: './app.component.html',
      7. styleUrls: ['./app.component.css'],
      8. providers: [TodoService]
      9. })
      10. export class AppComponent {
      11. newTodo: Todo = new Todo();
      12. constructor(private todoService:TodoService){}
      13. addTodo() {
      14. this.todoService.add(this.newTodo);
      15. this.newTodo = new Todo();
      16. }
      17. toggleTodoComplete(todo) {
      18. this.todoService.toggleTodoComplete(todo);
      19. }
      20. removeTodo(todo) {
      21. this.todoService.deleteById(todo.id);
      22. }
      23. get todos() {
      24. return this.todoService.findAll();
      25. }
      26. }

      你可能不信,增加完上面的代码,我们的应用已经能正常运行了,不过我们还是来看看这个文件的内容:

      • newTodo: Todo = new Todo(); :

        newTodo 属性用于新增 todo 对象时绑定属性。还记得我们页面的第一个 input 标签吗? <input [(ngModel)]="newTodo.title" (keyup.enter)="addTodo()"> 这里对 input 值和 newTodo.title 的值做了双向绑定,只要这两个值中的一个变化,另外一个都会跟着变化。

      • addTodo() :

        1. addTodo() {
        2. this.todoService.add(this.newTodo);
        3. this.newTodo = new Todo();
        4. }

        可能你已经猜到了,当你在页面上按下回车时,那个(keyup.enter)="addTodo()" 调用方法的实现,就是在这里。addTodo会调用注入的 todoService 的 add 方法。把 newTodo 进行保存。并将 this.newTodo 赋值一个新的 Todo 对象。

      • toggleTodoComplete(todo) :

        1. toggleTodoComplete(todo) {
        2. this.todoService.toggleTodoComplete(todo);
        3. }

        页面<input type="checkbox" (click)="toggleTodoComplete(todo)" [checked]="todo.complete">,当你点击这个 checkbox 时,变会调用 toggleTodoComplete 方法并将 todo 对象传入,toggleTodoComplete 使用 todoService 服务的来转变 todo 的 complete 属性。当todo.complete 的值发生变化时,[checked] 的值也会跟着变化。

      • removeTodo(todo) :

        1. removeTodo(todo) {
        2. this.todoService.deleteById(todo.id);
        3. }

        页面<button (click)="removeTodo(todo)"></button>,当你点击删除按钮是,便会调用 removeTodo 方法,并传入 todo 对象作为方法的参数。而removeTodo 会根据传入的参数调用 todoService 服务的 deleteById 来删除这个 todo 对象。

      • get todos()

        1. get todos() {
        2. return this.todoService.findAll();
        3. }

        如果你对 java bean 的定义熟悉的话你应该能猜个大概。这个方法是一个属性方法。相当于定义了一个 todos 属性。 并为这个 todos 属性添加了 get 方法。所以当页面 <li *ngFor="let todo of todos" > 这个标签获取 todos 的数据时,便会调用 get todos() 方法。而 get todos() 会调用 todoService 服务的 findAll 方法,并返回所有的 todos。

    4. 编写我们的样式文件

      样式文件并不是本文的重点,这里贴出样式的代码,省的小伙伴自己编写。

      • ./src/style.css : 这个文件用于设置全局css属性:

        1. html,
        2. body {
        3. margin: 0;
        4. padding: 0;
        5. }
        6. button {
        7. margin: 0;
        8. padding: 0;
        9. border: 0;
        10. background: none;
        11. font-size: 100%;
        12. vertical-align: baseline;
        13. font-family: inherit;
        14. font-weight: inherit;
        15. color: inherit;
        16. -webkit-appearance: none;
        17. appearance: none;
        18. -webkit-font-smoothing: antialiased;
        19. -moz-font-smoothing: antialiased;
        20. font-smoothing: antialiased;
        21. }
        22. body {
        23. font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
        24. line-height: 1.4em;
        25. background: #f5f5f5;
        26. color: #4d4d4d;
        27. min-width: 230px;
        28. max-width: 550px;
        29. margin: 0 auto;
        30. -webkit-font-smoothing: antialiased;
        31. -moz-font-smoothing: antialiased;
        32. font-smoothing: antialiased;
        33. font-weight: 300;
        34. }
        35. button,
        36. input[type="checkbox"] {
        37. outline: none;
        38. }
        39. .hidden {
        40. display: none;
        41. }
      • /src/app/app.component.css : 编写我们组件的样式

        1. .todoapp {
        2. background: #fff;
        3. margin: 130px 0 40px 0;
        4. position: relative;
        5. box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
        6. 0 25px 50px 0 rgba(0, 0, 0, 0.1);
        7. }
        8. .todoapp input::-webkit-input-placeholder {
        9. font-style: italic;
        10. font-weight: 300;
        11. color: #e6e6e6;
        12. }
        13. .todoapp input::-moz-placeholder {
        14. font-style: italic;
        15. font-weight: 300;
        16. color: #e6e6e6;
        17. }
        18. .todoapp input::input-placeholder {
        19. font-style: italic;
        20. font-weight: 300;
        21. color: #e6e6e6;
        22. }
        23. .todoapp h1 {
        24. position: absolute;
        25. top: -155px;
        26. width: 100%;
        27. font-size: 100px;
        28. font-weight: 100;
        29. text-align: center;
        30. color: rgba(175, 47, 47, 0.15);
        31. -webkit-text-rendering: optimizeLegibility;
        32. -moz-text-rendering: optimizeLegibility;
        33. text-rendering: optimizeLegibility;
        34. }
        35. .new-todo,
        36. .edit {
        37. position: relative;
        38. margin: 0;
        39. width: 100%;
        40. font-size: 24px;
        41. font-family: inherit;
        42. font-weight: inherit;
        43. line-height: 1.4em;
        44. border: 0;
        45. outline: none;
        46. color: inherit;
        47. padding: 6px;
        48. border: 1px solid #999;
        49. box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
        50. box-sizing: border-box;
        51. -webkit-font-smoothing: antialiased;
        52. -moz-font-smoothing: antialiased;
        53. font-smoothing: antialiased;
        54. }
        55. .new-todo {
        56. padding: 16px 16px 16px 60px;
        57. border: none;
        58. background: rgba(0, 0, 0, 0.003);
        59. box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
        60. }
        61. .main {
        62. position: relative;
        63. z-index: 2;
        64. border-top: 1px solid #e6e6e6;
        65. }
        66. label[for='toggle-all'] {
        67. display: none;
        68. }
        69. .toggle-all {
        70. position: absolute;
        71. top: -55px;
        72. left: -12px;
        73. width: 60px;
        74. height: 34px;
        75. text-align: center;
        76. border: none; /* Mobile Safari */
        77. }
        78. .toggle-all:before {
        79. content: '❯';
        80. font-size: 22px;
        81. color: #e6e6e6;
        82. padding: 10px 27px 10px 27px;
        83. }
        84. .toggle-all:checked:before {
        85. color: #737373;
        86. }
        87. .todo-list {
        88. margin: 0;
        89. padding: 0;
        90. list-style: none;
        91. }
        92. .todo-list li {
        93. position: relative;
        94. font-size: 24px;
        95. border-bottom: 1px solid #ededed;
        96. }
        97. .todo-list li:last-child {
        98. border-bottom: none;
        99. }
        100. .todo-list li.editing {
        101. border-bottom: none;
        102. padding: 0;
        103. }
        104. .todo-list li.editing .edit {
        105. display: block;
        106. width: 506px;
        107. padding: 13px 17px 12px 17px;
        108. margin: 0 0 0 43px;
        109. }
        110. .todo-list li.editing .view {
        111. display: none;
        112. }
        113. .todo-list li .toggle {
        114. text-align: center;
        115. width: 40px;
        116. /* auto, since non-WebKit browsers doesn't support input styling */
        117. height: auto;
        118. position: absolute;
        119. top: 0;
        120. bottom: 0;
        121. margin: auto 0;
        122. border: none; /* Mobile Safari */
        123. -webkit-appearance: none;
        124. appearance: none;
        125. }
        126. .todo-list li .toggle:after {
        127. content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
        128. }
        129. .todo-list li .toggle:checked:after {
        130. content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
        131. }
        132. .todo-list li label {
        133. white-space: pre-line;
        134. word-break: break-all;
        135. padding: 15px 60px 15px 15px;
        136. margin-left: 45px;
        137. display: block;
        138. line-height: 1.2;
        139. transition: color 0.4s;
        140. }
        141. .todo-list li.completed label {
        142. color: #d9d9d9;
        143. text-decoration: line-through;
        144. }
        145. .todo-list li .destroy {
        146. display: none;
        147. position: absolute;
        148. top: 0;
        149. right: 10px;
        150. bottom: 0;
        151. width: 40px;
        152. height: 40px;
        153. margin: auto 0;
        154. font-size: 30px;
        155. color: #cc9a9a;
        156. margin-bottom: 11px;
        157. transition: color 0.2s ease-out;
        158. }
        159. .todo-list li .destroy:hover {
        160. color: #af5b5e;
        161. }
        162. .todo-list li .destroy:after {
        163. content: '×';
        164. }
        165. .todo-list li:hover .destroy {
        166. display: block;
        167. }
        168. .todo-list li .edit {
        169. display: none;
        170. }
        171. .todo-list li.editing:last-child {
        172. margin-bottom: -1px;
        173. }
        174. .footer {
        175. color: #777;
        176. padding: 10px 15px;
        177. height: 20px;
        178. text-align: center;
        179. border-top: 1px solid #e6e6e6;
        180. }
        181. .footer:before {
        182. content: '';
        183. position: absolute;
        184. right: 0;
        185. bottom: 0;
        186. left: 0;
        187. height: 50px;
        188. overflow: hidden;
        189. box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
        190. 0 8px 0 -3px #f6f6f6,
        191. 0 9px 1px -3px rgba(0, 0, 0, 0.2),
        192. 0 16px 0 -6px #f6f6f6,
        193. 0 17px 2px -6px rgba(0, 0, 0, 0.2);
        194. }
        195. .todo-count {
        196. float: left;
        197. text-align: left;
        198. }
        199. .todo-count strong {
        200. font-weight: 300;
        201. }
        202. .filters {
        203. margin: 0;
        204. padding: 0;
        205. list-style: none;
        206. position: absolute;
        207. right: 0;
        208. left: 0;
        209. }
        210. .filters li {
        211. display: inline;
        212. }
        213. .filters li a {
        214. color: inherit;
        215. margin: 3px;
        216. padding: 3px 7px;
        217. text-decoration: none;
        218. border: 1px solid transparent;
        219. border-radius: 3px;
        220. }
        221. .filters li a.selected,
        222. .filters li a:hover {
        223. border-color: rgba(175, 47, 47, 0.1);
        224. }
        225. .filters li a.selected {
        226. border-color: rgba(175, 47, 47, 0.2);
        227. }
        228. .clear-completed,
        229. html .clear-completed:active {
        230. float: right;
        231. position: relative;
        232. line-height: 20px;
        233. text-decoration: none;
        234. cursor: pointer;
        235. }
        236. .clear-completed:hover {
        237. text-decoration: underline;
        238. }
        239. .info {
        240. margin: 65px auto 0;
        241. color: #bfbfbf;
        242. font-size: 10px;
        243. text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
        244. text-align: center;
        245. }
        246. .info p {
        247. line-height: 1;
        248. }
        249. .info a {
        250. color: inherit;
        251. text-decoration: none;
        252. font-weight: 400;
        253. }
        254. .info a:hover {
        255. text-decoration: underline;
        256. }
        257. /*
        258. Hack to remove background from Mobile Safari.
        259. Can't use it globally since it destroys checkboxes in Firefox
        260. */
        261. @media screen and (-webkit-min-device-pixel-ratio:0) {
        262. .toggle-all,
        263. .todo-list li .toggle {
        264. background: none;
        265. }
        266. .todo-list li .toggle {
        267. height: 40px;
        268. }
        269. .toggle-all {
        270. -webkit-transform: rotate(90deg);
        271. transform: rotate(90deg);
        272. -webkit-appearance: none;
        273. appearance: none;
        274. }
        275. }
        276. @media (max-width: 430px) {
        277. .footer {
        278. height: 50px;
        279. }
        280. .filters {
        281. bottom: 10px;
        282. }
        283. }

4. 总结

到这里我们就使用 Angular 2 创建了一个 TODO MVC 项目。让我们一起回顾下,本文以后我们都学习到了什么:

  • 使用 Angular CLI 创建并初始化一个 Angular 项目。
  • 使用 Angular CLI 创建 Todo 对象,TodoService 并实现里面的逻辑代码。
  • 使用 ng test 测试我们 TodoService 的逻辑代码
  • 对 Angular 模版语法有了一定的了解,属性绑定,事件绑定,双向绑定等。
  • 对 Angular 组件有一定的了解,组件装饰器里的元数据与模版和样式文件关联,组件与模版的通过绑定进行交互,组件通过元数据和构造函数注入服务。

转载于:https://my.oschina.net/bluesummer/blog/968446

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/空白诗007/article/detail/874426
推荐阅读
相关标签
  

闽ICP备14008679号