zoukankan      html  css  js  c++  java
  • 迈向angularjs2系列(3):组件详解

    一: 以组件开发一个to-do list应用

    1.启动server:

    进入switchingToNG2/switching-to-angular2目录,运行npm start,那么打开浏览器,进入红色框的链接。

    2.进入switching-to-angular2/app/ch4/ts/todo-app目录

    app.ts:

    todo组件分为导入、接口定义、顶层组件、控制器、启动5个部分。

    //导入
    import {Component} from '@angular/core';
    import {bootstrap} from '@angular/platform-browser-dynamic';
    
    //接口定义
    interface Todo {
      completed: boolean;
      label: string;
    }
    
    //顶层组件
    @Component({
      selector: 'app',
      templateUrl: './app.html',
      styles: [
        `ul li {
          list-style: none;
        }
        .completed {
          text-decoration: line-through;
        }`
      ]
    })
    //控制器
    class TodoCtrl {
      todos: Todo[] = [{
        label: 'Buy milk',
        completed: false
      }, {
        label: "Save the world",
        completed: false
      }];
      name: string = 'John';
      addTodo(label) {
        this.todos.push({
          label,
          completed: false
        })
      }
      removeTodo(idx) {
        this.todos.splice(idx, 1);
      }
      toggleCompletion(idx) {
        let todo = this.todos[idx];
        todo.completed = !todo.completed;
      }
    }
    //启动
    bootstrap(TodoCtrl);

    (1)组件样式

    我们直接在组件装饰器加入styles属性。

      app/ch4/ts/todo-app/app.ts的样式代码:

    @Component({
      selector: 'app',
      templateUrl: './app.html',
      styles: [
        `ul li {
          list-style: none;
        }
        .completed {
          text-decoration: line-through;
        }`
       //styles属性,值为数组。
      ]
    })

    (2)组件控制器

    app.ts控制器代码:

    //控制器
    class TodoCtrl {
      todos: Todo[] = [{
        label: '呼风唤雨',
        completed: false
      }, {
        label: "保护妹妹",
        completed: false
      }];
      //todos数组。包含两个元素,每个元素的变量指定为Todo。
      name: string = '爱莎';
      //name属性
      addTodo(label) {
        this.todos.push({
          label,
          completed: false
        })
      }
      //用于添加元素
      removeTodo(idx) {
        this.todos.splice(idx, 1);
      }
      //用于移除元素
      toggleCompletion(idx) {
        let todo = this.todos[idx];
        todo.completed = !todo.completed;
      }
      //切换完成或者未完成
    }

    (3)模板app.html内容:

    <h1>你好 {{name}}!</h1>
    
    <p>
      添加todo:
      <input #newtodo type="text">
      <button (click)="addTodo(newtodo.value); newtodo.value = ''">Add</button>
    </p>
    
    <p>to-do清单:</p>
    
    <ul>
      <!--遍历todos数组-->
      <li *ngFor="let todo of todos; let index = index" [class.completed]="todo.completed">
        <input type="checkbox" [checked]="todo.completed"
          (change)="toggleCompletion(index)">
        {{todo.label}}
      </li>
    </ul>

     <li *ngFor="let todo of todos; let index = index" [class.completed]="todo.completed"> 这里使用了index索引。

    ● (change)="toggleCompletion(index)" 绑定了toggleCompletion函数到change事件上。

    ● [checked]="todo.completed" 绑定了控制器的todos数据的completed到checked属性上。

       [class.completed]="todo.completed" 意思是如果todo.completed为true,那么添加completed类。angular也允许绑定style和attribute的。

    最后启动app的时候,要把控制器传进去。

    //启动
    bootstrap(TodoCtrl);

    打开http://localhost:5555/dist/dev/ch4/ts/todo-app/,结果为

    3.组件的用户交互

    实现toggleCompletion方法。它是控制器里定义的,所以直接使用咯。

    toggleCompletion(idx) {
      let todo = this.todos[idx];
      todo.completed = !todo.completed;
    }
    //切换完成或者未完成的状态

    实现addToDo方法,用来添加todo元素。

    addTodo(label) {
        this.todos.push({
          label,
          completed: false
        })
      }
      //用于添加元素

    通过button按钮添加元素如图:

    4.app组件的切分

    先来看一下指令API的输入输出。可以把指令接受的属性看成输入。把指令触发的事件看成输出。使用第三方库的指令时最主要的关注点就是输入和输出,这些内容构成了指令的API。

    如果把todo应用切分成多个独立的组件,组件之间存在交互,那么分析一下这个todo应用咯。

    最外层的方框代表整个todo应用。内部的第一个方框有一个组件,用来新建todo项目。下面的方框用来存储所有的项目。

    根据分析,我们可以定义3个组件:

    ●TodoApp

    ●InputBox

    ●TodoList

    5.InputBox组件:关注输入和输出

    app/ch4/ts/inputs-outputs/app.ts完整的代码:

    //导入
    import {Component, Input, Output, EventEmitter} from '@angular/core';
    import {bootstrap} from '@angular/platform-browser-dynamic';
    
    //todo接口
    interface Todo {
      completed: boolean;
      label: string;
    }
    
    //InputBox组件
    @Component({
      selector: 'input-box',
      template: `
        <input #todoInput [placeholder]="inputPlaceholder">
        <button (click)="emitText(todoInput.value); todoInput.value = '';">
          {{buttonLabel}}
        </button>
      `
    })
    class InputBox {
      @Input() inputPlaceholder: string;
      @Input() buttonLabel: string;
      @Output() inputText = new EventEmitter<string>();
      emitText(text: string) {
        this.inputText.emit(text);
      }
    }
    //ToDoList组件
    @Component({
      selector: 'todo-list',
      template: `
        <ul>
          <li *ngFor="let todo of todos; let index = index" [class.completed]="todo.completed">
            <input type="checkbox" [checked]="todo.completed"
              (change)="toggleCompletion(index)">
            {{todo.label}}
          </li>
        </ul>
      `,
      styles: [
        `ul li {
          list-style: none;
        }
        .completed {
          text-decoration: line-through;
        }`
      ]
    })
    class TodoList {
      @Input() todos: Todo[];
      @Output() toggle = new EventEmitter<Todo>();
      toggleCompletion(index: number) {
        let todo = this.todos[index];
        this.toggle.emit(todo);
      }
    }
    //TodoApp组件
    @Component({
      selector: 'todo-app',
      directives: [TodoList, InputBox],
      template: `
        <h1>Hello {{name}}!</h1>
    
        <p>
          Add a new todo:
          <input-box inputPlaceholder="New todo..."
            buttonLabel="Add"
            (inputText)="addTodo($event)">
          </input-box>
        </p>
    
        <p>Here's the list of pending todo items:</p>
        <todo-list [todos]="todos"
          (toggle)="toggleCompletion($event)">
        </todo-list>
      `
    })
    class TodoApp {
      todos: Todo[] = [{
        label: 'Buy milk',
        completed: false
      }, {
        label: "Save the world",
        completed: false
      }];
      name: string = 'John';
      addTodo(label: string) {
        this.todos.push({
          label,
          completed: false
        });
      }
      toggleCompletion(todo: Todo) {
        todo.completed = !todo.completed;
      }
    }
    //启动
    bootstrap(TodoApp);
    查看完整的代码

    InputBox组件:

    首先看模块的引入。

    //导入
    import {Component, Input, Output, EventEmitter} from '@angular/core';
    import {bootstrap} from '@angular/platform-browser-dynamic';

    引入了@Component, @Input, @Output,装饰器和EventEmitter类。@Input和@Output用来声明组件的输入和输出。EventEmitter是一个通用类,可接收泛型参数,它和@output装饰器结合使用,用来产生输出。

    再来看组件的声明。 

    //InputBox组件
    @Component({
      //需要传对象字面量的组件装饰器
      selector: 'input-box',
      template: `
        <input #todoInput [placeholder]="inputPlaceholder">
        <button (click)="emitText(todoInput.value); todoInput.value = '';">
          {{buttonLabel}}
        </button>
      `
    })
      //定义了输出和输入的InputBox类
    class InputBox {
       @Input() inputPlaceholder: string; 
      @Input() buttonLabel: string;
      @Output() inputText = new EventEmitter<string>();
      emitText(text: string) {
        this.inputText.emit(text);
      }
    }

     <input #todoInput [placeholder]="inputPlaceholder"> 模板声明了一个id名为todoInput的文本输入框,placeholder的值为inputPlaceholder,它也是InputBox类定义的第一个输入项

     @Input() inputPlaceholder: string; 。buttonLabel也是类似的需要在InputBox类中通过@Input @Input() buttonLabel: string; 定义输入项。

     (click)="emitText(todoInput.value); todoInput.value = '';" 有点长,分别解读。首先来看点击click,那么执行emitText函数,并且todoInput的value设置为空。那么对应输出项定义如下。

    @Output() inputText = new EventEmitter<string>();
      //定义了一个名称为inputText的输出,初始值为EventEmitter。
      //所有组件的输出内容都是EventEmitter<string>的实例
      emitText(text: string) {
        //emitText内部调用了inputText实例的emit方法,把文本输入框的值作为参数传递过去。
        this.inputText.emit(text);
      }

    inputText最终是在input-box的组件中使用。

    <input-box inputPlaceholder="New todo..."
            buttonLabel="Add"
            (inputText)="addTodo($event)">
          </input-box>

    类似的ToDoList组件:

    只看类的定义

    class TodoList {
      @Input() todos: Todo[];
      //todos是ngFor指令接收的参数,那么是组件的输入
      @Output() toggle = new EventEmitter<Todo>();
      //只要todo项目的完成状态发生改变,组件都会触发change事件。属于组件的输出。
      toggleCompletion(index: number) {
        let todo = this.todos[index];
        this.toggle.emit(todo);
      }
    }

    6.传递输入与使用输出结果

    定义好了组件如何把它们整合起来实现完整的应用呢?

    最后看一下TodoApp组件,通过 bootstrap(TodoApp); 可以看出TodoApp是最顶层的组件,如何整合到一起的,就看装饰器的模板,魔法就在这里。

     <h1>Hello {{name}}!</h1>
    
        <p>
          Add a new todo:
          <input-box inputPlaceholder="New todo..."
            buttonLabel="Add"
            (inputText)="addTodo($event)">
          </input-box>
          <!--使用了input-box组件,
          使用了组件定义的输入项inputPlaceholder和buttonLabel。如果值是表达式,需要裹一层方括号
          使用了输出项inputText-->
        </p>
    
        <p>Here's the list of pending todo items:</p>
        <todo-list [todos]="todos"
                   
          (toggle)="toggleCompletion($event)">
          
        </todo-list>
        <!--输入项todos,因为值是变量,所以裹了一层方括号-->
        <!--输出项toggle->

    事件冒泡的分析:

    <input-box inputPlaceholder="New todo..."
            buttonLabel="Add"
            (click)="handleClick($event)"
            (inputText)="addTodo($event)">
          </input-box>
    <!--假设有click事件咯-->
    <!--input-box模板的内容-->
    <input #todoInput [placeholder]="inputPlaceholder">
        <button (click)="emitText(todoInput.value); todoInput.value = '';">
          {{buttonLabel}}
        </button>

    angular的事件冒泡与DOM事件相同。用户点击input-box组件内部模板中的按钮,就会执行handleClick($event)表达式。handleClick的第一个参数有一个属性叫target,指向按钮,但currentTarget属性指向input-box标签。

    同时,要强调的是EventEmitter触发的事件是不会冒泡的。

    7.重命名组件的输入输出

    首先在input和outut装饰器重命名:

    class TodoList {
      @Input('todos') todosList: Todo[];
      //todos重命名
      @Output('toggle') toggleEvent = new EventEmitter<Todo>();
      //toggle重命名
      toggle(index: number) {
        let todo = this.todos[index];
        this.toggle.emit(todo);
      }
    }

    然后在使用的地方依然保持一致就好了。

    <todo-list [todos]="todos"
                   
          (toggle)="toggleCompletion($event)">
          
        </todo-list>

    二: 内容投影详解

     1.ng-content指令的使用

    app/ch4/ts/ng-content/app.ts节选:

    @Component({
      selector: 'fancy-button',
      template: '<button>点击我</button>'
    })
    class FancyButton { /* Extra behavior */ }
    //组件设置了内联模板和选择器

    那么我们就可以使用该组件了。

    <fancy-button></fancy-button>

    这里的fancy-button组件可复用并不高,那么使用ng-content指令就可以动态修改标签了。接下来的使用是组件的标签内容就可以替换ng-content指令所占的内容了。

    @Component({
      selector: 'fancy-button',
      template: '<button><ng-content></ng-content></button>'
    })
    class FancyButton { /* Extra behavior */ }

    在顶级组件的装饰器模板里可以看到使用方法

    @Component({
      selector: 'app',
      template: `
        <fancy-button>
          <span>I will <i>be</i> projected</span>
        </fancy-button>
       //fancy-button的使用
        <br>
        <panel>
          <panel-title>Sample title</panel-title>
          <panel-content>Content</panel-content>
        </panel>
      `,
      directives: [FancyButton, Panel]
    })

    2.投射多块内容

    例如panel组件,包含标题区域和主题区域。

    <div class="panel">
          <div class="panel-title">
            标题区域
          </div>
          <div class="panel-content">
           主题区域
          </div>
     </div>`

    那么我们定义组件就可以这样做了

    @Component({
      selector: 'panel',
      styles: [
        `.panel {
           auto;
          display: inline-block;
          border: 1px solid black;
        }
        .panel-title {
          border-bottom: 1px solid black;
          background-color: #eee;
        }
        .panel-content,
        .panel-title {
          padding: 5px;
        }`
      ],
      template: `
        <div class="panel">
          <div class="panel-title">
            <ng-content select="panel-title"></ng-content>
            <!--标题区域-->
          </div>
          <div class="panel-content">
            <ng-content select="panel-content"></ng-content>
           <!--主题区域-->
          </div>
        </div>`
    })
    class Panel { }

    重点看组件的模板。定义了一个class为panel的div,包含标题区域和主体区域。通过selet属性匹配panel内部的标签。

    3.组件嵌套

    @Component({
      selector: 'sample-component',
      template: '<view-child></view-child>'
    })
    class Sample { /* Extra behavior */ }

    嵌套的代码

    <sample-component>
      <content-child></content-child>
    </sample-component>
  • 相关阅读:
    javascript中的style.display=block中的block要如何理解?
    AJAX的下拉查询,效果跟google的搜索提示类似
    XML 命名空间(XML Namespaces)
    生肖查询
    通过网银在线进行在线支付
    UpdatePanel完成后调用js
    Javascript基础知识
    div标题栏拖动
    生物信息学的核心课程
    医学遗传学词汇英语术语英文(Glossary) 7
  • 原文地址:https://www.cnblogs.com/chenmeng2062/p/7105612.html
Copyright © 2011-2022 走看看