迈向angularjs2系列(3):组件详解

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

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

app.ts:

//导入
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;
  }
  //切换完成或者未完成
}

模板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 {
      width: 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>
时间: 2024-10-10 16:51:46

迈向angularjs2系列(3):组件详解的相关文章

Android组件系列----Activity组件详解

[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/3924567.html 联系方式:[email protected] [正文] 注:四大组件指的是应用组件:Activity.Service.BroadcastReceiver.ContentProvider:之前的控件指的是UI组件. 博文目录: 一.Activity简介 二.Activity的状

Vue学习系列(二)——组件详解

前言 在上一篇初识Vue核心中,我们已经熟悉了vue的两大核心,理解了Vue的构建方式,通过基本的指令控制DOM,实现提高应用开发效率和可维护性.而这一篇呢,将对Vue视图组件的核心概念进行详细说明. 什么是组件呢? 组件可以扩展HTML元素,封装可重用的HTML代码,我们可以将组件看作自定义的HTML元素. 为什么要用到组件呢? 为了可重用性高,减少重复性开发,让我们可以使用独立可复用的小组件来构建大型应用. 基本 一.组件注册 1.通过Vue.extend()创建,然后由component来

迈向angularjs2系列(2):angular2组件和指令详解

<%= INIT %> 内容 一:angular2 helloworld! 为了简单快速的运行一个ng2的app,那么通过script引入预先编译好的angular2版本和页面的基本框架. index.html: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> &l

Tomcat系列之服务器的安装与配置以及各组件详解

Tomcat系列之服务器的安装与配置以及各组件详解 大纲 一.前言 二.安装与配置Tomcat 三.Tomcat 目录的结构 四.Tomcat 配置文件 注,本文的测试的操作系统为CentOS 6.4 x86_64,软件版本为jdk-7u40.apache-tomcat-7.0.42.博文中的所有软件请到这里下载:http://yunpan.cn/QGBCLwrZnpLMS. 一.前言 在上一篇博文中我们主要讲解的Tomcat的基础知识以及相关的Java知识,对于不怎么清楚的博友可以参考一下:h

mongo 3.4分片集群系列之六:详解配置数据库

这个系列大致想跟大家分享以下篇章(我会持续更新的(^ω^)): 1.mongo 3.4分片集群系列之一:浅谈分片集群 2.mongo 3.4分片集群系列之二:搭建分片集群--哈希分片 3.mongo 3.4分片集群系列之三:搭建分片集群--哈希分片 + 安全 4.mongo 3.4分片集群系列之四:搭建分片集群--哈希分片 + 安全 + 区域 5.mongo 3.4分片集群系列之五:详解平衡器 6.mongo 3.4分片集群系列之六:详解配置数据库 7.mongo 3.4分片集群系列之七:配置数

Android 四大组件 详解

[置顶] Android四大组件详解 分类: Android四大组件2013-02-09 16:23 19411人阅读 评论(13) 收藏 举报 Android开发 注:本文主要来自网易的一个博主的文章,经过阅读,总结,故留下文章在此 Android四大基本组件介绍与生命周期 Android四大基本组件分别是Activity,Service服务,Content Provider内容提供者,BroadcastReceiver广播接收器. 一:了解四大基本组件 Activity : 应用程序中,一个

Android中Intent组件详解

Intent是不同组件之间相互通讯的纽带,封装了不同组件之间通讯的条件.Intent本身是定义为一个类别(Class),一个Intent对象表达一个目的(Goal)或期望(Expectation),叙述其所期望的服务或动作.与动作有关的数据等.Android则根据此Intent对象之叙述,负责配对,找出相配的组件,然后将 Intent对象传递给所找到的组件,Android的媒婆任务就完成了. 在Google Doc中是这样描述Intent的(摘自Android中文翻译组)当接收到ContentR

基于jQuery的TreeGrid组件详解

一.TreeGrid组件相关的类 1.TreeGrid(_config) _config:json格式的数据,组件所需要的数据都通过该参数提供. 2.TreeGridItem(_root, _rowId, _rowIndex, _rowData) _root:显示组件实例的目标容器对象. _rowId:选中行的id. _rowIndex:选中行的索引. _rowData:json格式的行数据. 二._config参数详解 id:组件实例的id. width:组件实例的宽度. renderTo:用

vue.js基础知识篇(6):组件详解

第11章:组件详解 组件是Vue.js最推崇也最强大的功能之一,核心目标是可重用性. 我们把组件代码按照template.style.script的拆分方式,放置到对应的.vue文件中. 1.注册 Vue.js的组件注册分为全局注册和局部注册. 全局注册使用Vue.component方法.第一个参数是组件名字,第二个参数是组件的构造函数,要么是function,要么是object. <!DOCTYPE html> <html lang="en"> <hea