angular2的应用就是一系列组件的集合
我们需要创建可复用的组件供多个组件重复使用
组件是嵌套的,实际应用中组件是相互嵌套使用的
组件中的数据调用可以使用inputs和outputs
一个组件可以是一种指令
一个组件可以包含前端表现及后端逻辑
一个组件可以是一个代码片段,能够独立运行
进一步理解指令
一个指令就是一个组件
一个指令可以装饰指令,用于改变DOM
一个指令可以是模板指令,可以改变element
一辆车有门、方向盘、窗等等,假设车就是母组件,方向盘就是子组件
在angular2中组件是可以多级次的,那么他们间的交流就是数据交换,这种机制就是数据流
在当前angular2版本的数据绑定是单向的,父组件数据向子组件数据传递,再向子子组件数据传递
子组件可以使用event向父组件传递数据
所以我们可以说有两种方式数据绑定
我们能够通过判断ngmodel来实现这两种数据绑定方式
实例分析
myApp.ts
import {bootstrap} from 'angular2/platform/browser';
import {carComponent} from "./car.component";
bootstrap(carComponent);
这里首先说明angular入口,即我们需要启动的组件
这里看到首先要引入bootstrap,用于下面加载组件,其次引入我们需要的组件carComponent,也就是我们下面要定义的一个class
car.component.ts
///<reference path="../node_modules/angular2/typings/browser.d.ts"/>
import {Component} from 'angular2/core';
import {door} from './door.component';
@Component({
selector: "carTag",
template: `
<h3 class="titles">Mother Car Component</h3>
<input type ="text" #textInput bind-value="text" />
<button on-click="onCarChange(textInput.value)">Change</button>
<div class="child-style">
<door [textLevel1]="text" (changed)="onCarChange($event)">
</door>
</div>
`,
directives: [door],
styles:[ `
.titles {
color:#0099FF
}
.child-style {
background-color:#00ffff
}
`]
})
export class carComponent {
text: string="输入文字";
onCarChange(value) {
this.text ="向子组件传递数据:"+value;
}
}
door.component.ts
///<reference path="../node_modules/angular2/typings/browser.d.ts"/>
import {Component, EventEmitter} from 'angular2/core';
@Component({
selector: "door",
template: `
<h2>Child Component</h2>
<input type ="text" #textInput value="{{textLevel1}}">
<button (click)="onDoorChange(textInput.value)">Change</button>
`,
inputs: ['textLevel1'],
outputs: ['changed']
})
export class door {
textlevel: string;
changed = new EventEmitter();
onDoorChange(value) {
this.textlevel ="向父组件传递数据:"+ value;
this.changed.emit(value);
}
}
}
Car.html
<!DOCTYPE html>
<html>
<head>
<script>document.write('<base href="' + document.location + '" />');</script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSS file -->
<link href="css/bootstrap.min.css" rel="stylesheet" />
<!-- IE polyfills, keep the order please -->
<script src="node_modules/es6-shim/es6-shim.min.js"></script>
<script src="node_modules/systemjs/dist/system-polyfills.js"></script>
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="node_modules/rxjs/bundles/Rx.js"></script>
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
<script src="node_modules/typescript/lib/typescript.js"></script>
<!-- Agular 2 Router -->
<script src="node_modules/angular2/bundles/router.dev.js"></script>
<!-- Config Agular 2 and Typescript -->
<script>
System.config({
transpiler: 'typescript',
typescriptOptions: { emitDecoratorMetadata: true },
packages: {'app': {defaultExtension: 'ts'}}
});
System.import('app/myApp')
.then(null, console.error.bind(console));
</script>
</head>
<!-- Run the application -->
<body>
<carTag>Loading Sample...</carTag>
</body>
</html>
第一行,目前版本的bug,所有自定义组件在当前版本下需要加入
下面引入Component,@Companent是typescript的一种注解语言,用于配置我们当前组件,具体里面有很多的参数,后面细说。
引入我们需要的组件(import)
先看看selector,即标签,也就是我们在html中要使用的标签名称,类似于标准的html标签(如<div>,<h1>),我们知道一个component可以是一个指令,carComponent 就是一个director,通过定义selector来标识这个指令。
template:该组件的模板,可以理解为该组件的视图,在template中就可以使用刚刚定义的标签,也就是在selector中定义标签名称,方法就像使用标准html的标签一样。此处是一个内嵌的模板,使用一对“`”符号来标记模板中的内容。
在该模板中可以看到input标签中定义了一个局部变量,Angular2提供一种简单的语法将元素 映射为局部变量:添加一个以#或var-开始的属性,后续的部分表示变量名,这个变量对应元素的实例。在input的value处我们看到该值绑定了carComponent组件的一个属性,使用{{}}绑定,这是angular的一种插值绑定语法,另一种方式可使用使用一对中括号将HTML元素或组件的属性绑定到组件模型的某个表达式, 当表达式的值变化时,对应的DOM对象将自动得到更新如<input type ="text" #textInput [value]="text" />注意value后的text是carComponent的一个属性,而value是input对象的属性,还有一种表达方式你也可以使用bind-前缀进行属性绑定如<input type ="text" #textInput bind-value="text" />
该模板button中(click)代表着事件 ,使用一对小括号包裹事件名称,并绑定到表达式,也可使用事件名称前加on-前缀如
<button on-click="onCarChange($event)">Change</button>
textInput即是前面在input中定义的局部变量代表着input的实例,onCarChange是carComponent中定义的方法,接收 一个参数,这里是为了触发text属性变化来通知子组件,当然也可以直接在input中进行定义,如
<input type ="text" #textInput bind-value="text" (change)="onCarChange(textInput.value)" />
效果是一样的。
该模板中使用了door标签,也就是我们在door组件中定义的标签,其中的textLevel1和changed是在door组件中定义的输入和输出项
在car组件中的template中使用door标签,就意味着需要加入door组件,故形成了嵌套关系,那么首先需要import 引入door组件import {door} from './door.component';
在模板中我们使用door组件的door标签,那么就需要配置directives指令集
directives:指令集,注意是一个集合,也就是说在该组件中嵌入多个组件(需要先import),由于上面在模板中使用了door指令所以在该集合中必须加入door指令
我们看到在car组件中的模板是这样使用car组件的
<door [textLevel1]="text" (changed)="onCarChange($event)">
这里加入了door标签,而[textLevel1]是door标签的一个属性,可以理解为input标签的value属性 ,那么这个属性的定义需要在door组件中的inputs中加入
inputs: ['textLevel1']
表示door标签使用textLevel1来接收上级传来的数据,[textLevel1]="text"表示door组件的textlevel1属性绑定了car组件的text属性,通过这样设计就实现了父组件向子组件的数据传递。那么子组件又是如何向父组件传递数据的呢?就是使用outputs,outputs提供了一个事件的集合,利用子组件定义的事件来向上级传递数据,如(changed)=" onCarChange ($event)"中所示,(changed)是door组件定义的事件并在outputs配置中做了声明 ,此处注意调用的是父组件的事件处理函数。
outputs: ['changed']
$event是该事件相关的数据,这里就是textlevel1的值
为了能够让其他组件能够使用所以需要export该组件,组件的定义和class一样,使用typescirpt语法,组件中可以定义属性,方法,可以有公共的也可以是私有的,如果是私有的需要加入private声明,需要注意的是如果使用了事件需要引入EventEmitter。如import {Component, EventEmitter} from 'angular2/core';
事件定义使用
changed = new EventEmitter();
使用Emit 传递事件如this.changed.emit(value);
Angular2相比angular1有一个很大的改变就是默认情况下的绑定是单向绑定,也是就是说视图上的数据变更不能直接更新model了,需要程序化进行更新,这样做的目的是为了提高效率,当然目前版本还保留了[(ngModel)]这种绑定方式,可以实现双向绑定。那么组件间的数据相互如何传递呢,父-〉子,子-〉父,子-〉子?
在父组件中使用子组件就是简单的使用子组件标签方式,那么可以将子组件的一个属性绑定到父组件的一个属性上就实现了父组件数据向子组件传递。简单理解下
在父组件中定义一个子组件<child [childProperty]=”parentProperty”></child>
这很类似于在父组件中使用<input [value]=”parentProperty” ></input>
通过这种方式子组件就能够获取父组件的数据了,需要注意的是子组件需要将用于绑定的属性公布出去,即使用inputs:[‘childProperty’],这里是个数组,可以公布多个属性供外部绑定。
总结来说就是可以通过将父组件的一个属性绑定到子组件中公布的一个属性上就实现了父组件向子组件传递数据的效果。
子组件向父组件传递数据是通过事件的方式,首先需要在子组件中定义事件,子组件的事件被触发时引发父组件的事件响应,同时将事件参数传递给父组件的响应函数,这样就完成了子组件向父组件传递数据。如下所示
<childLevel1 [parentMsg]="parentMsg" (childLevel1Changed)="OnChildLevel1MsgChanged($event)"> </childLevel1>
[parentMsg]是子组件的一个属性,而值来自父组件的parentMsg,这样实现了父组件向子组件传递parenMs数据,另外(childLevel1Changed)是子组件中定义的事件,而OnChildLevel1MsgChanged是父组件的一个事件响应函数,而$event代表着事件源。从这个例子我们分析看看数据流。下面是子组件的事件定义:
outputs: ['childLevel1Changed']
<button (click)="OnChildLevel1MsgChanged(childLevel1Text.value)" >changeChild1</button>
OnChildLevel1MsgChanged(event) {
this.childLevel1Msg = event;
this.childLevel1Changed.emit(event);
}
事件的发生顺序由子组件的按钮引发交由事件处理程序,事件的参数是childLevel1Text. Value,事件处理程序将子组件定义的事件广播出去,并传递数据源,父组件检测到子组件的事件并交给父组件的事件处理程序来处理。这样就将子组件的数据传递给了父组件。
了解了上面的方式后,那么子到子就简单了,分成子到父,再父到子。父组件在中间起到协调作用
最后,看到这些有些疑惑了,一个真实应用会有很多的数据交互,如果使用这种机制,那很难去维护这里的关系了,难道没有更好的方式去实现组件间的数据交互吗?答案是服务!