Angular入门

参考原文链接:半小时入门Angular2

Angular官方文档列出的8个核心概念:模块、组件、模板、元数据、数据绑定、指令、服务、依赖注入

从总览的角度来看,各个概念在应用中所处的位置如下图所示:

  • 与用户直接交互的是模板,模板并不是独立的模块,它是组成组件的要素之一。另一要素是组件类,用以维护组件的数据模型及功能逻辑;
  • 模板是通过元数据指定的,元数据还包含很多其他的重要信息,这些信息是用来告诉 Angular 如何去解释一个普通的类,如上图所示,元数据结合普通类而构成组件;
  • 指令是 Angular 里的独立构成,他与模板密切关联,用来增强模板特性,间接扩展了模板的语法;
  • 服务也是 Angular 里的独立构成,他是封装单一功能逻辑的单元,通常为组件提供功能扩展;
  • 服务要能为组件所使用,是通过“依赖注入”机制把服务引入到组件内部,服务既可以单独注入到某一组件,亦可注入到模块,两种注入方式使得服务的作用域不一样,后文详解。

组件

先来聚焦单个组件,每个 Angular 组件内部除了有独立的 JavaScript 逻辑,还包含有 HTML(即模板)及 CSS 代码。所以每个组件不仅有自己独立的业务逻辑,也有属于自己的视图层来渲染自己。

简单的组件示例代码如下:

import { Component } from '@angular/core'; 

@Component({

	selector: 'contact', 

	template: '<p>张三</p>' 

}) 

class ContactComponent {

	constructor() { }

} 

可以看出组件由 @Component 和 class 两部分组成,class是处理组件的业务逻辑,而 @Component是装饰器,装饰器是 TypeScript 提供的一种语言特性,用来往类、函数等注入额外的信息,这些额外的信息实际上就是 Angular 核心概念——元数据。

在 Angular 里,元数据主要以装饰器的函数参数指定。上例中定义了两个重要元数据 selector 和 template,template 顾名思义即为模板,selector 声明的是一个 CSS3 选择器,应用运行时匹配模板上的DOM元素,简单理解其实就是组件的标签名。

关系示意图如下所示:

如果我们仅仅定义了一个类,Angular 并不知道该如何解释这个类。当往这个类里注入组件元数据后,Angular 才知道把这个类解释为组件。类似的还有指令元数据,把普通类解释为一个指令。

虽然每个组件各司其职,但组件以树的形式来组织意味着,组件不可能是孤立的存在,父子组件之间存在着双向的数据流动。每个组件均可以定义自己的输入(@Input)输出(@Output)属性,这些属性成为了组件的对外接口,负责跟父组件进行交互。

示例代码如下:

// import Component, Input, Output, EventEmitter, etc

@Component({

 selector: 'contact',

 template: '<p>张三</p>'

})

export class ContactComponent {

 @Input() item: ContactModel; // 输入属性 

 @Output() update: EventEmitter<ContactModel>; // 输出属性 

 constructor() { }

 modify() {

	// ... this.update.emit(newValue);

}

}

@Input() 和 @Output() 也是装饰器,装饰的目标为类的成员属性,而 @Component() 装饰的目标是类。

@Input() 和 @Output 声明了组件 的输入输出接口,item 变量用来接收来自父组件的数据源输入(在父组件通过属性向子组件传值),update 事件用于向父组件发送数据(在父组件中通过监听update事件来接收数据)。输入输出属性分开。

属性绑定的 [] 和事件绑定 () 不能省略,这是语法的重要组成部分。

属性绑定和数据绑定均称为数据绑定,这个 Angular 强调的核心概念之一。

属性绑定和事件绑定既用于组件数据模型和模板视图之间的数据传递,也同时用于父子组件的数据传递,在父子组件通信的过程中,模板充当类似于桥梁的角色,连接着二者的功能逻辑。 如下图所示:

这种通讯方式适用于层级相隔不远的组件,层级太深或者不同分支的组件通讯通常采用其他方式,例如利用服务作为中介。

Angular中常用的生命周期钩子:

  1. 最先触发的是构造函数,你可以做些组件类的初始化工作,例如类变量初始赋值等。
  2. 接下来会触发 ngOnChanges 钩子,这是 ngOnChanges 钩子的第一次触发,主要用来接收来自父组件传入的数据,为接下来的组件的初始化工作提供数据支持。
  3. 然后就到了 ngOnInit 钩子,这个才是实际意义的组件初始化阶段,Angular 不推荐在构造器初始化阶段处理一些业务逻辑相关的工作,更好的方式是放在 ngOnInit 阶段来处理。
  4. 接下来,组件就进入稳定期,这个时期 ngOnChanges 钩子可以反复触发。只要从输入属性获取到的数据的发生变化,ngOnChanges 钩子就会触发一次。
  5. 最后,在组件销毁之前会触发 ngOnDestroy 钩子,在这个阶段可以用来做一些清理工作,如事件解绑,取消数据订阅等等。

模板

数据绑定是模板最基本的功能,除了前述提到的属性绑定和事件绑定,插值也是很常见的数据绑定语法

示例代码如下:

{{ item.name }}

插值语法是由一对双大括号 {{}} 组成,插值的变量上下文是组件类本身,插值是一种单向的数据流动 —— 从数据模型到模板视图。

上面提到的三种数据绑定(即属性绑定、事件绑定以及插值)语法的数据流动都是单向的,在某些场景下需要双向的数据流动支持(如表单)。结合属性绑定和事件绑定,Angular 模板可实现双向绑定的功能,如:

<input [(ngModel)]="form.name">

[()] 是实现双向绑定的语法糖,ngModel 是辅助实现双向绑定的内置指令。上述代码执行后,Input 控件和 form.name 之间就形成双向的数据关联,Input 的值发生变更时,可自动赋值至 form.name,而 form.name 的值被组件类改变时,亦可实时更新 Input 的值。

由上可知,数据绑定负责数据的传递与展示,而针对数据的格式化显示,Angular 提供了一种叫管道的功能,使用竖线 | 来表示,示例代码如下:

{{ form.telephone | phone }}

Angular 也提供了一些基本的内置管道命令,如格式化数字的 number、格式化日期的 date 等 。

指令

Angular 指令的范畴很广,实际上组件也是指令的一种。 组件与一般指令的区别在于:组件带有单独的模板,即 DOM 元素,而一般的指令是作用在已有的 DOM 元素上。一般的指令分为两种:结构指令和属性指令。

结构指令能够添加、修改或删除 DOM,从而改变布局,如 ngIf、ngFor。

属性指令用来改变元素的外观或行为,使用起来跟普通的 HTML 元素属性非常相似,如 ngStyle 指令,用于动态计算样式值

指令更具吸引力的地方在于支持开发者自定义,自定义指令能最大限度地实现 UI 层面的逻辑复用。

服务

服务是封装单一功能的单元,类似于工具库,常被引用于组件内部,作为组件的功能扩展。那服务包含什么?它可以是一个简单的字符串或是 JSON 数据,也可以是一个函数甚至是一个类,几乎所有的对象都可以封装成服务。以日志服务为例,一个简单的日志服务如下所示:

// import statement

@Injectable()

export class LoggerService {

 private level: string;

 setLevel(level: string) {

	 this.level = level;

 }

 debug(msg: string) { }

 warn(msg: string) { }

 error(msg: string) { }

}

@Injectable() 是服务类装饰器。

这个服务的功能很简单,只专注于日志功能,Angular 应用里每个组件都可以复用到这个日志服务给自己新增日志记录的能力,而不需要每个组件重复实现,这就是设计服务的主要原则。那么服务怎么样为组件所使用?这就需要引入依赖注入机制。

依赖注入

通过依赖注入机制,服务等模块可以被引入到任何一个组件(或模块,或其他服务)中,而开发者无须关心这些模块是如何被初始化。因为 Angular 已经帮你处理好,包括该模块本身依赖的其他模块也会被初始化。如下图所示,当组件注入日志服务后,日志服务以及它所依赖的基础服务都会被初始化。

一个简单的依赖注入例子如下所示:

import {LoggerService} from './logger-service';

// other import statement

@Component({

 selector: 'contact',

 template: '...'

 providers: [LoggerService]

})

export class ContactListComponent {

 constructor(logger: LoggerService) {

	 logger.debug('xxx');

 }

}

@Component 装饰器中的 providers 元数据是依赖注入操作的关键,它会为该组件创建一个注入器对象,并新建 LoggerService 实例存储到这个注入器里。组件需要引入 LoggerService 实例时,只需在构造函数声明 LoggerService 类型的参数即可,Angular 自动地通过类型匹配,找出注入器里预先实例化好的 LoggerService 对象,在组件实例化化时作为参数传入,这样组件便获得了 LoggerService 的实例引用。

值得注意的是,组件上创建的这个注入器对象是可以被子组件复用的,这就意味着我们只需在根组件上注入一次服务,即在根组件的 providers 声明注入该服务,整棵组件树上的组件都能使用这个服务,并且保持单例。这个特性非常有用,大大节省了服务的内存占用,并且由于服务是单例的,注入到组件后,可以作为中转桥梁,实现这些组件之间的数据传递。

模块

一个大型应用由大量组件、指令、管道、服务构成,这些构件中有些是没有交集的,而有些则协同工作来完成某个特定的功能,我们希望把这些有关联的构件包装到一块,形成一个比较独立的单元,这样的单元在实际意义上就称为模块。 所以简单的说,模块就是对应用内零散的组件、指令、服务按功能进行归类包装。其关系示意图如下:

模块还有一个重要的实际意义。因为默认情况下,一个组件是不能直接引用其他组件,也不能直接使用其他指令的功能,要想使用需要先导入,其他前面讲父子组件时候已经提到过,这个导入的过程就是应用模块实现的。 总结来说,一个组件可以任意使用同模块的其他组件和指令。 但是,跨模块里的组件指令则不能直接相互使用,如模块A的组件不能直接使用模块C的指令,若要跨模块访问,则需结合模块的导入导出功能,要理解导入导出的内容 ,先来看一个简单的模块例子:

// import statement

@NgModule({

 imports: [SomeModule], // 导入其他模块

 declarations: [SomeComponent, SomeDirective, SomePipe], // 引入组件、指令、管道

 providers: [LoggerService], // 依赖注入

 exports: [SomeComponent, SomeDirective, SomePipe] // 导出组件、指令、管道

 // bootstrap: [AppComponent] // 根模块才有,标记哪个组件是根组件

})

export class AppModule { }

可以看出,声明模块使用的是 @NgModule() 装饰器。先来看 imports 和 exports 属性,他们即为模块的导入导出属性,模块间的导入导出关系如下图所示:

由图可知,模块A 导入了 模块B,模块B 通过 exports 属性暴露了 组件B1 和 指令B2。 很显然,组件B1 和 指令B2 能够被 组件A1 使用,而 组件B3 并不能。 所以可以看出,Angular 模块既可以对外暴露出一些构件,同时又有一定的封装性,能够隐藏内部的一些实现。

讲完了模块内的组件和指令(管道的访问方式跟组件指令一致),接下我们来看一下服务,接上文依赖注入抛过来的水球。服务既可注入到组件也可注入到模块,二者的使用方法大致相同,区别在于作用域。所有的模块上都共享着一个应用级别的注入器,这就意味着注入到任何一个模块的服务可以在应用全局(所有模块)里使用,而注入到组件里的,仅能在该组件以及它的子组件上使用 。

关于应用级注入器和组件级注入器的关系如下所示:

Angular 已经封装了不少常用的模块,如:

  • ApplicationModule:封装一些启动相关的工具;
  • CommonModule:封装一些常用的内置指令和内置管道等;
  • BrowserModule:封装在浏览器平台运行时的一些工具库,同时将 CommonModule 和 ApplicationModule 打包导出,所以通常在使用时引入 BrowserModule 就可以了;
  • FormsModule 和 ReactiveFormsModule:封装表单相关的组件指令等;
  • RouterModule:封装路由相关的组件指令等;
  • HttpModule:封装网络请求相关的服务等。

所以,如果你想使用 ngIf 和 ngStyle 等这些内置指令,记得先导入 CommonModule,其他的模块使用方法一致。

原文地址:https://www.cnblogs.com/cherishSmile/p/11374660.html