1、模块 (Modules):
2、组件 (Components):
3、模板 (Templates):
4、元数据 (Metadata):
5、数据绑定 (Data Binding)
6、指令 (Directives)
7、服务 (Services):
8、依赖注入 (Dependency Injection)
9、路由(Route):建立URL路径和组件之间的对应关系,根据不同的URL路径匹配对应的组件并渲染。
一、元数据(Metadata)
元数据就是在定义模块、组件、服务的时候,Decorator(装饰器)方法里面的参数内容,例如一个AppComponent的元数据,就是 @Component 里面的参数,如下:
{
selector : 'mylist',
template : '<h2>元数据</h2>'
directives : [ComponentDetails]
}
在Angular2中,Decorator(装饰器)被大量使用,当我们定义模板、组件、服务、指令时,都是使用Decorator来定义。顾名思义,Decorator(装饰器)就是在一个类上面添加一些额外的属性或方法。
举个例子,根组件AppComponent,在定义它的时候,通过 @Component 才能把它定义成一个Angular的组件。然后我们在这个元数据里面,设置了这个组件对应的selector,模板和样式。
这样Angular框架在解析这个类的时候,就会按照组件的规则去解析并初始化。
当在一个页面里面遇到这个selector设置的标签时,就会初始化这个组件,渲染模板生成html显示到对应的标签里面,并应用样式。
二、模块 (Modules)
module是指使用@NgModule修饰的class。
@NgModule利用一个元数据对象来告诉Angular如何去编译和运行代码。
可以将组件、服务、指令、方法、管道等封装成一个模块,并且可以将它们的访问权限声明为公有,以便外部模块的组件可以访问和使用到它们。
内置模块
Angular2将许多常用功能分配到一个个的模块中:
- ApplicationModule:封装一些启动相关的工具
- CommonModule:封装一些常用的内置指令和内置管道等
- BrowserModule:封装在浏览器平台运行时的一些工具库,同时将CommonModule和ApplicationModule打包导出,所以通常在使用时引入BrowserModule就可以了
- FormsModule和ReactiveFormsModule:封装表单相关的组件指令等
- RouterModule:封装路由相关的组件指令等
- HttpModule:封装网络请求相关的服务等
在使用前,需导入相关模块包:
- @angular/core:存放核心代码,如变化监测机制、依赖注入机制、渲染等,核心功能的实现、装饰器(@Component、@Directive等)也会存放到这个模块。
- @angular/common:存放一些常用的内置指令和内置管道等。
- @angular/forms:存放表单相关的内置组件及内置指令等。
- @angular/http:存放网络请求相关的服务等。
- @angular/router:存放路由相关的组件和指令等。
- @angular/platform-<x>:存放的是引导启动相关的工具。angular支持在多个平台下运行,不同的平台都有对应的启动工具,这些启动工具会被封装到不同的模块里,如浏览器的启动工具存放在@angular/platform-browser下,服务端渲染的启动工具存放在@angular/platform-server下。
Angular 模块是一个带有 @NgModule 装饰器的类,它接收一个用来描述模块属性的元数据对象。
@NgModule的元数据属性:
- declarations:模块内部Components/Directives/Pipes的列表,声明一下这个模块内部成员
- providers:指定应用程序的根级别需要使用的service。(Angular2中没有模块级别的service,所有在NgModule中声明的Provider都是注册在根级别的Dependency Injector中)
- imports:导入其他module,其它module暴露的出的Components、Directives、Pipes等可以在本module的组件中被使用。比如导入CommonModule后就可以使用NgIf、NgFor等指令。
- exports:用来控制将哪些内部成员暴露给外部使用。导入一个module并不意味着会自动导入这个module内部导入的module所暴露出的公共成员。除非导入的这个module把它内部导入的module写到exports中。
- bootstrap:通常是app启动的根组件,一般只有一个component。bootstrap中的组件会自动被放入到entryComponents中。
- entryCompoenents: 不会在模板中被引用到的组件。这个属性一般情况下只有ng自己使用,一般是bootstrap组件或者路由组件,ng会自动把bootstrap、路由组件放入其中。 除非不通过路由动态将component加入到dom中,否则不会用到这个属性。
一个最简单的根模块:
//app/app.module.ts 文件:
import { NgModule } from '@angular/core'; //从 @angular/core 中引入 NgModule 修饰器
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
imports: [ BrowserModule ],
providers: [ Logger ],
declarations: [ AppComponent ],
exports: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }//定义根模块
三、组件 (Components)
组件是构成Angular2应用的砖块。
包括三个部分:带有 @Component()
装饰器的 TypeScript 类、HTML 模板和样式文件。
@Component的元数据属性:
- selector :css选择器,把该组件实例化在 HTML 中对应的选择器上。
- styleUrls :在单独文件中声明组件的样式。
- styles : 在组件内部声明样式。styles: ['h1 { font-weight: normal; }']
- standalone:描述组件是否需要 .NgModule
- template :一段 HTML,告诉应用如何渲染组件。
- templateUrl : HTML文件相对路径或绝对 URL。 (不能与template 同时使用)
一个简单的组件:
import { Component } from '@angular/core';//从 @angular/core 中引入 NgModule 修饰器
@Component({
selector: 'hello-world',
template: `
<h2>Hello World</h2>
<p>This is my first component!</p>
`
})
export class HelloWorldComponent {
//在此类中的代码驱动组件的行为。
}
在html中使用此组件:
<hello-world></hello-world>
在一个Angular2的应用中,组件是一个属性结构,就好像html的DOM树一样,每个Angular2应用都有一个根组件,然后它会有一个个的子组件。得到的是一个组件树。每个组件(除了根组件)都有一个父组件,每个组件定义中“selector”的值,对应父组件中的一个html标签。
组件通信
在Angular中,有多种方法可以实现父子组件通信。
以下是几种常用的方法:
- 输入属性(Input Properties)
- 输出属性(Output Properties)
- 服务(Services)
- ViewChild与ContentChild
1. 输入属性(Input Properties)
输入属性是一种用于从父组件向子组件传递数据的方法。通过使用@Input()装饰器,我们可以在子组件中定义一个公共属性来接收来自父组件的数据。
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-child',
template: '<p>{{ message }}</p>'
})
export class ChildComponent {
@Input() message: string;
}
在上述代码中,我们使用@Input()装饰器来定义了一个名为message的输入属性。在子组件的模板中,我们使用插值表达式{{ message }}
来展示接收到的消息。
2. 输出属性(Output Properties)
输出属性允许子组件向父组件传递信息。通过使用事件触发器和@Output()装饰器,我们可以在子组件中定义一个事件,并在适当的时候将数据作为事件参数发送给父组件。
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-child',
template: '<button (click)="sendMessage()">Send Message</button>'
})
export class ChildComponent {
@Output() messageEvent = new EventEmitter<string>();
sendMessage() {
this.messageEvent.emit('Hello from child component');
}
}
在上述代码中,我们定义了一个名为messageEvent的输出属性,并使用EventEmitter来创建一个新的事件。在子组件中,当用户点击按钮时,我们通过调用sendMessage()方法并使用emit()方法来触发messageEvent事件,并将一个字符串作为参数传递给父组件。
3. 服务(Services)
服务是一种共享数据和状态的有效方式。通过创建一个共享的服务,我们可以在任何组件之间传递数据和共享状态。组件可以通过依赖注入服务,并使用服务提供的方法和属性进行通信。
import { Injectable } from '@angular/core';
@Injectable()
export class DataService {
private message: string;
setMessage(message: string) {
this.message = message;
}
getMessage() {
return this.message;
}
}
在上述代码中,我们创建了一个名为DataService的服务,并在其中定义了一个私有的message属性和相应的设置和获取方法。通过在需要访问该数据的组件中注入DataService,我们可以在组件之间共享数据。
4. ViewChild与ContentChild
通过使用ViewChild和ContentChild装饰器,我们可以在父组件中获取对子组件的引用,并直接调用子组件的方法或访问其属性。这种方法适用于需要直接与子组件进行交互的情况。
import { Component, ViewChild } from '@angular/core';
import { ChildComponent } from './child.component';
@Component({
selector: 'app-parent',
template: `
<app-child></app-child>
<button (click)="callChildMethod()">Call Child Method</button>
`
})
export class ParentComponent {
@ViewChild(ChildComponent) childComponent: ChildComponent;
callChildMethod() {
this.childComponent.childMethod();
}
}
在上述代码中,我们使用@ViewChild()装饰器来获取对ChildComponent的引用,并将其赋值给childComponent属性。然后,在父组件的模板中,我们使用一个按钮来触发callChildMethod()方法,该方法会调用子组件中的childMethod()方法。
四、数据绑定(Data binding)
Angular2的数据更新检测是在每个组件上有一个检测器。这样,就算应用中有再多绑定的变量,当有一个数据修改后,也只是对应的那个组件的检测器被触发,来检查它以及它所有的子组件的数据修改。
Angular 添加了一些语法元素以扩展 HTML,让你可以从组件中插入动态值。当组件的状态更改时,Angular 会自动更新已渲染的 DOM。
数据绑定的语法有四种形式:
1、插值 {{}} : 在 HTML 标签中显示组件值。(单向)
<h3>
{{title}}
<img src="{{ImageUrl}}">
</h3>
2、属性绑定 []: 把元素的属性设置为组件中属性的值。(单向)
<img [src]="userImageUrl">
3、事件绑定 (): 通过在圆括号中指定事件名称来声明一个事件监听器(单向)
//在组件方法名被点击时触发
<button (click)="sayMessage()">保存</button>
//组件类中定义的方法:
sayMessage() {
alert(this.message);
}
4、双向绑定 [] (): 使用Angular里的NgModel指令可以更便捷的进行双向绑定。
<input [value]="currentUser.firstName"
(input)="currentUser.firstName=$event.target.value" >
双向绑定就是用户在页面上修改这个值时,这个值就会直接反馈到组件中。同样,如果在组件中通过某种方式修改了这个值,页面上,也会显示最新的值。
对于上面的 [] 和 () 两种类型的绑定,可以理解成’输入’和’输出’。
五、服务(Services)
Angular2并没有对服务的定义做任何的规则限制,任何的类都可以被定义成服务,这个类中可以包含业务方法,也可以包含环境配置变量。
一个简单的服务:
export class loggerServices {
log(msg: any) { console.log(msg); }
error(msg: any) { console.error(msg); }
warn(msg: any) { console.warn(msg); }
}
我们只需要定义一个class,并把它export就可以了。
六、依赖注入 (Dependency Injection)
Angular借用了java等语言中某些容器库的概念,它将所有service实例的创建都由容器来完成。当一个service需要引用另一个service的时候,不需要先创建service实例,然后通过实例调用它的方法或属性,而是直接从容器中获取相应service的实例,无需我们操心如何实例化它们。
在Angular2中,依赖注入 (Dependency Injection) 主要是用于管理service实例的注入。
使用 @Injectable
装饰器以声明此类可以被注入。
@Injectable
export class HeroService {
...
}
然后就可以在其它地方注入并使用它。
1:提供依赖项
-
在组件级别,使用
@Component
装饰器的 providers字段。在这种情况下,HeroService将可用于此组件的所有实例,以及它的模板中使用的其他组件和指令。也就是说,在当前节点,以及它所有的子节点的组件上,HeroService类的实例是共用的,它们都共享一个服务实例。例如:
@Component({
selector: '...',
template: '...',
providers: [HeroService]
})
class HeroListComponent {}
-
在 NgModule 级别,要使用
@NgModule
装饰器的 providers字段。在这种情况下,HeroService可用于此 NgModule ,或与本模块位于同一个 ModuleInjector 的其它模块中声明的所有组件、指令和管道。当你向特定的 NgModule 注册提供者时,同一个服务实例可用于该 NgModule 中的所有组件、指令和管道。要理解所有边缘情况,参见多级注入器。例如:
@NgModule({
declarations: [...]
providers: [HeroService]
})
class HeroListModule {}
-
在应用根级别,允许将其注入应用的其他类中。这可以通过将 providedIn: 'root'字段添加到
@Injectable
装饰器来实现:
@Injectable({
providedIn: 'root'
})
class HeroService {}
当你在根级别提供服务时,Angular 会创建一个 HeroService
的共享实例,并将其注入到任何需要它的类中。在 @Injectable
元数据中注册提供者还允许 Angular 通过从已编译的应用程序中删除没用到的服务来优化应用程序,这个过程称为摇树优化(tree-shaking)。
2:注入依赖项
最常见方法是在类的构造函数中声明它。当 Angular 创建组件、指令或管道类的新实例时,它会通过查看构造函数的参数类型来确定该类需要哪些服务或其他依赖项。例如,如果 HeroListComponent 要用 HeroService,则构造函数可以如下所示:
@Component({ … })
class HeroListComponent {
constructor(private service: HeroService) {}
}
七、组件交互:@Output(EventEmitter)、@Input
1、@Input 父传子
子组件:
import { Component, OnInit, Input} from "@angular/core";
export class childComponent implements OnInit {
@Input() unit: string = "";
ngOnInit() {
// 使用时候直接this.unit
}
}
父组件:
<app-child [unit]="unit"></app-child>
// ts中 定义变量即可
2、@Output子传父(一般通过事件来触发)
父组件:
<app-child (onBackVectorClick)="onBackVectorClick($event)"></app-child>
// ts
onBackVectorClick($event) {
// ...
}
子组件:
import { Component, OnInit, Input} from "@angular/core";
export class childComponent implements OnInit {
@Output() onBackVectorClick: EventEmitter<null> = new EventEmitter();
@Output() onDateTypeChange: EventEmitter<{ id: number | string, text: string, [key: string]: any }> = new EventEmitter();
ngOnInit() {
this.onDateTypeChange.emit(btn);//传递数据 给父组件,并触发父组件方法onBackVectorClick
}
}
3 、EventEmitter
用在带有 @Output 指令的组件中,以同步或异步方式发出自定义事件,并通过订阅实例来为这些事件注册处理器。
class EventEmitter<T> extends Subject<T> {
constructor(isAsync?: boolean): EventEmitter<T>
// isAsync,当为 true 时,异步传递事件。可选值。默认值为 false。
//
emit(value?: T): void
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): Subscription
}
// 子组件
//用 EventEmitter 和 Output 装饰器配合使用,<string> 指定变量类型
@Output() private outer = new EventEmitter<string>();
runNews():void{
console.info('开始emit广播数据');
this.outer.emit('我是news新闻组件'); //通过outer实例对象的emit方法广播数据
}
// 父组件
//父组件中挂载子组件,注意 (outer) 名称必须和子组件中的名称一致对应
<app-news (outer)="runParent($event)"></app-news>
runParent(e:any):void{
console.log('我是parent父组件的runParent方法');
console.log(e); //e就接收到子组件中方法的(emit)广播数据
}
方法:
1. emit(value)
发出包含给定值的事件。value要发出的值,可选值,默认值为 undefined。
2 subscribe())
发出包含给定值的事件。value要发出的值,可选值,默认值为 undefined。
总结
1. Input、Output(EventEmitter)区别:
Input相当于指令的值绑定,是将父作用域的值“输入”到子作用域中,然后子作用域进行相关处理。
Output相当于方法绑定,子作用域触发事件执行响应函数,而响应函数方法体则位于父作用域中,相当于将事件“输出到”父作用域中,在父作用域中处理。
2. EventEmitter
相当于实现了观察者模式(事件监听与触发)
4、非父子组件如何传递数据
- cookie:只要在一个组件的ts文件中设置了cookie,则其他组件也可以读取cookie的键值对。
- service:定义一个服务,将不同组件中要使用相同的函数用一个服务包裹,则可以在不同组件引入这个服务来公用同一方法。
- web存储:这个方法比cookie好,因为cookie的容量在4K之内,且cookie需随着请求发送给服务端,会影响请求与响应的速度。而web存储容量大,存储更加安全与快速,不影响网站的性能。
八、@ViewChild
1、作用:
- @ViewChild是Angular提供的,用来从模板视图中,获取匹配元素的一个属性装饰器。
- 变更检测器会在视图的 DOM 中查找能匹配上该选择器的第一个元素或指令。
- 如果视图的 DOM 发生了变化,出现了匹配该选择器的新的子节点,该属性就会被更新。
2 、参数
// 将查询到的第一个元素或者指令赋值给selector
@ViewChild('searchText', {read: ElementRef, static: false}) selector;
read: 告诉@ViewChild你返回的是什么类型的数据
read参数是可选的,因为 Angular 会根据 DOM 元素的类型推断出该引用类型。
一般Angular会推断出一些比较简单的类型如: ElementRef 、 TemplateRef。
一些引用类型如 ViewContainerRef 就不可以被 Angular 推断出来,所以必须在 read 参数中显式声明。
一个字符串, 如果没有提供read参数确切的告知返回的元素是什么类型,则其返回实例类型按照以下顺序:
- ElementRef实例(对于每个元素,都有一个ElementRef和ViewContainerRef)
- 如果没有对应的ElementRef, 则匹配同名的组件。
3 、使用
3.1 获取本html页面DOM元素
html
<input type="text" #myInput>
import { ViewChild, ElementRef } from '@angular/core';
@ViewChild('myInput') input; ==> @ViewChild('myInput') input: ElementRef;
ngAfterViewInit() {
console.dir(this.input);
}
this.input.nativeElement.focus(); // 获取焦点
3.2 父组件通过@ViewChild主动获取子组件的数据和方法。(在angular也提供了一个@Output修饰器来实现子组件给父组件传值,但是这个方法是较复杂的)
子组件:
content:'Zita';
changeChildCon() {
this.content = 'Zita1111'
}
html
<app-child #ChildrenView></app-child>//给子组件定义一个名称:#号后面加一个变量名,组成模板变量
父组件:
import { ViewChild } from '@angular/core';
@ViewChild('ChildrenView') aaaChildrenView: any; //在父组件中引入viewChild:
//ChildrenView为子组件中的#后边的值,aaaChildrenView是个名称用来指代子组件
this.childrenView.content // Zita 获取子组件中的值
this.childrenView.changeChildCon() // 执行子组件中的方法
this.childrenView.content // Zita1111