Angular快速学习笔记(2) -- 架构

0. angular 与angular js

angular 1.0 google改名为Angular js
新版本的,2.0以上的,继续叫angular,但是除了名字还叫angular,已经是一个全新的开发框架了。

Angular 是一个用 HTML 和 TypeScript 构建客户端应用的平台与框架。 Angular 本身使用 TypeScript 写成的。它将核心功能和可选功能作为一组 TypeScript 库进行实现,你可以把它们导入你的应用中。

全新的Angular 是一个用 HTML 和 TypeScript 构建客户端应用的平台与框架。 Angular 本身使用 TypeScript 写成的。它将核心功能和可选功能作为一组 TypeScript 库进行实现,你可以把它们导入你的应用中。

1. 架构

Angular 的基本构造块是 NgModule,它为组件提供了编译的上下文环境。Angular 应用就是由一组 NgModule 定义出的,应用至少会有一个用于引导应用的根模块,通常还会有很多特性模块。
- 组件定义视图,是可视化部分,每个应用都至少有一个根组件
- 组件使用服务,组件提果数据可视化,而服务提供与视图不直接相关的功能,后台开发的容易理解。使用服务的好处是服务可以作为依赖被注入到组件中,实现复用,同时还能方便不同模块做通信。

组件和服务都是简单的类,这些类使用装饰器来标出它们的类型。Angular充分利用了装饰器(java里的annotation)来标识类的类型,并在装饰器中提供元数据(metadata)来告知ng如何使用它们。

1.1 模块

Angular 定义了 NgModule,它和 JavaScript(ES2015) 的模块不同而且有一定的互补性。 NgModule 为一个组件集声明了编译的上下文环境,它专注于某个应用领域、某个工作流或一组紧密相关的能力。 NgModule 可以将其组件和一组相关代码(如服务)关联起来,形成功能单元。

每个 Angular 应用都有一个根模块(root module),通常命名为 AppModule。根模块提供了用来启动应用的引导机制。 一个应用通常会包含很多功能模块。

像 JavaScript 模块一样,NgModule 也可以从其它 NgModule 中导入功能,并允许导出它们自己的功能供其它 NgModule 使用。 比如,要在你的应用中使用路由器(Router)服务,就要导入 Router 这个 NgModule。

1.1.1 定义一个模块

一个NgModule就是一个使用@NgModule 装饰器的类。

@NgModule 装饰器是一个函数,它接受一个元数据对象,该对象的属性用来描述这个模块。其中最重要的属性如下。

  • declarations(可声明对象表) —— 那些属于本 NgModule 的组件、指令、管道
  • exports(导出表) —— 那些能在其它模块的组件模板中使用的可声明对象的子集。
  • imports(导入表) —— 那些导出了本模块中的组件模板所需的类的其它模块。
  • providers —— 本模块向全局服务中贡献的那些服务的创建器。 这些服务能被本应用中的任何部分使用。(你也可以在组件级别指定服务提供商,这通常是首选方式。)
  • bootstrap —— 应用的主视图,称为根组件。它是应用中所有其它视图的宿主。只有根模块才应该设置这个 bootstrap 属性。

1.1.2 NgModule 和组件

NgModule 为其中的组件提供了一个编译上下文环境。根模块总会有一个根组件,并在引导期间创建它。 但是,任何模块都能包含任意数量的其它组件,这些组件可以通过路由器加载,也可以通过模板创建。那些属于这个 NgModule 的组件会共享同一个编译上下文环境。

enter description here

1.1.3 NgModule 和 JavaScript 的模块

NgModule 系统与 JavaScript(ES2015)用来管理 JavaScript 对象的模块系统不同,而且也没有直接关联。

JavaScript 中,每个文件是一个模块,文件中定义的所有对象都从属于那个模块。 通过 export 关键字,模块可以把它的某些对象声明为公共的。 其它 JavaScript 模块可以使用import 语句来访问这些公共对象。

NgModule更像一个逻辑上的概念,是一个软件包的概念。

1.1.4 Angular官方库

Angular 自带了一组 JavaScript 模块,你可以把它们看成库模块。每个 Angular 库的名称都带有 @angular 前缀。 使用 npm 包管理器安装它们,并使用 JavaScript 的 import 语句导入其中的各个部分。

enter description here

例如,从 @angular/core 库中导入 Component 装饰器:

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

还可以使用 JavaScript 的导入语句从 Angular 库中导入 Angular 模块:

import { BrowserModule } from '@angular/platform-browser'

在上面这个简单的根模块范例中,应用的根模块需要来自 BrowserModule 中的素材。要访问这些素材,就要把它加入 @NgModule 元数据的 imports 中,代码如下:

imports:      [ BrowserModule ],

1.2 组件

每个 Angular 应用都至少有一个组件,也就是根组件,它会把组件树和页面中的 DOM 连接起来。 每个组件都会定义一个类,其中包含应用的数据和逻辑,并与一个 HTML 模板相关联,该模板定义了一个供目标环境下显示的视图。

enter description here

1.2.1 组件定义

使用@Component 装饰器来标识一个组件类,并为其指定元数据

@Component({
  selector:    'app-hero-list',
  templateUrl: './hero-list.component.html',
  providers:  [ HeroService ]
})
export class HeroListComponent implements OnInit {
/* . . . */
}
  • selector:是一个 CSS 选择器,它会告诉 Angular,一旦在模板 HTML 中找到了这个选择器对应的标签,就创建并插入该组件的一个实例。 比如,如果应用的 HTML 中包含 <app-hero-list></app-hero-list>,Angular 就会在这些标签中插入一个 HeroListComponent 实例的视图。
  • templateUrl:该组件的 HTML 模板文件相对于这个组件文件的地址,实现html与js的分离,推荐
  • 可以用 template 属性的值来提供内联的 HTML 模板,类似vuejs和react的单文件。
  • providers 是当前组件所需的依赖注入提供商的一个数组,组件需要用到的service,需要在这里提供

1.2.2 模板与视图

模板就是一种 HTML,它会告诉 Angular 如何渲染该组件。
视图通常会分层次进行组织,让你能以 UI 分区或页面为单位进行修改、显示或隐藏。
与组件直接关联的模板会定义该组件的宿主视图。该组件还可以定义一个带层次结构的视图,它包含一些内嵌的视图作为其它组件的宿主。

enter description here

1.2.3 模板语法

模板会把 HTML 和 Angular 的标记(markup)组合起来,这些标记可以在 HTML 元素显示出来之前修改它们。 模板中的指令会提供程序逻辑,而绑定标记会把你应用中的数据和 DOM 连接在一起。

<h2>Hero List</h2>

<p><i>Pick a hero from the list</i></p>
<ul>
  <li *ngFor="let hero of heroes" (click)="selectHero(hero)">
    {{hero.name}}
  </li>
</ul>

<app-hero-detail *ngIf="selectedHero" [hero]="selectedHero"></app-hero-detail>
  • *ngFor 指令告诉 Angular 在一个列表上进行迭代
  • {{hero.name}}(click)[hero]把程序数据绑定到及绑定回 DOM,以响应用户的输入。更多内容参见稍后的数据绑定部分
  • 模板中的 <app-hero-detail> 标签是一个代表新组件 HeroDetailComponent 的元素

1.2.3.1 数据绑定

Angular的数据绑定标记的四种形式。每种形式都有一个方向 —— 从组件到 DOM、从 DOM 到组件或双向

enter description here

例如:

<li>{{hero.name}}</li>
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
<li (click)="selectHero(hero)"></li>
  • {{hero.name}}插值表达式在 <li> 标签中显示组件的 hero.name 属性的值。
  • [hero]属性绑定把父组件 HeroListComponent 的 selectedHero 的值传到子组件 HeroDetailComponent 的 hero 属性中。
  • 当用户点击某个英雄的名字时,(click) 事件绑定会调用组件的 selectHero 方法。

再来看双向数据绑定,这个是脱离jq手动绑定数据的伟大发明。

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

在双向绑定中,数据属性值通过属性绑定从组件流到输入框。用户的修改通过事件绑定流回组件,把属性值设置为最新的值。Angular 在每个 JavaScript 事件循环中处理所有的数据绑定,它会从组件树的根部开始,递归处理全部子组件。

enter description here

数据绑定在模板及其组件之间的通讯中扮演了非常重要的角色,它对于父组件和子组件之间的通讯也同样重要。

enter description here

父组件,通过属性绑定向子组件传递数据,而子组件通过事件绑定向与父组件通信。

1.2.3.2 Pipes管道

一般的模板引擎都会提供pipes功能,angular也不例外,Angular 的管道可以让你在模板中声明显示值的转换逻辑。 带有 @Pipe 装饰器的类中会定义一个转换函数,用来把输入值转换成供视图显示用的输出值。

Angular 自带了很多管道,比如 date 管道和 currency 管道,完整的列表参见 Pipes API 列表。你也可以自己定义一些新管道。

使用管道:

{{interpolated_value | pipe_name}}

在需要处理的值后面,加上|, 管道还能接收一些参数,来控制它该如何进行转换。比如,你可以把要使用的日期格式传给 date 管道:

<!-- Default format: output 'Jun 15, 2015'-->

 <p>Today is {{today | date}}</p>

<!-- fullDate format: output 'Monday, June 15, 2015'-->

<p>The date is {{today | date:'fullDate'}}</p>

 <!-- shortTime format: output '9:43 AM'-->

 <p>The time is {{today | date:'shortTime'}}</p>

1.2.3.2 指令

Angular 的模板是动态的。当 Angular 渲染它们的时候,会根据指令给出的指示对 DOM 进行转换。 指令就是一个带有 @Directive 装饰器的类。

组件其实也是一个指令,但是组件非常独特、非常重要,因此 Angular 专门定义了 @Component 装饰器,它使用一些面向模板的特性扩展了 @Directive 装饰器。

组件就是特殊的指令

除了组件,还有两种指令:结构型指令属性型指令。和组件一样,指令的元数据把指令类和一个 selector 关联起来,selector 用来把该指令插入到 HTML 中。

在模板中,指令通常作为属性出现在元素标签上,可能仅仅作为名字出现,也可能作为赋值目标或绑定目标出现。

结构型指令
结构型指令通过添加、移除或替换 DOM 元素来修改布局

<li *ngFor="let hero of heroes"></li>
<app-hero-detail *ngIf="selectedHero"></app-hero-detail>

属性型指令
属性型指令会修改现有元素的外观或行为。 在模板中,它们看起来就像普通的 HTML 属性一样,因此得名“属性型指令”。

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

Angular 还有很多预定义指令,它们或者修改布局结构(比如 ngSwitch),或者修改 DOM 元素和组件的某些方面(比如 ngStyle 和 ngClass)。

1.3 服务与依赖注入(DI)

对于与特定视图无关并希望跨组件共享的数据或逻辑,可以创建服务类。 服务类的定义通常紧跟在 “@Injectable” 装饰器之后。该装饰器提供的元数据可以让你的服务作为依赖被注入到客户组件中。

服务是一个广义的概念,它包括应用所需的任何值、函数或特性。狭义的服务是一个明确定义了用途的类。它应该做一些具体的事,并做好。

Angular 把组件和服务区分开,以提高模块性和复用性,这比较契合后端的开发思想,一个类只需要把自己负责的事情做好即可,专业的事情交给专业的类去处理

  • 通过把组件中和视图有关的功能与其他类型的处理分离开,你可以让组件类更加精简、高效
  • 组件不应该定义任何诸如从服务器获取数据、验证用户输入或直接往控制台中写日志等工作。 而要把这些任务委托给各种服务。

定义一个服务:

# src/app/logger.service.ts (class)
export class Logger {
  log(msg: any)   { console.log(msg); }
  error(msg: any) { console.error(msg); }
  warn(msg: any)  { console.warn(msg); }
}

1.3.1 依赖注入(dependency injection)

组件是服务的消费者,也就是说,你可以把一个服务注入到组件中,让组件类得以访问该服务类。

如何使用:

  • 在 Angular 中,要把一个类定义为服务,就要用 @Injectable 装饰器来提供元数据,以便让 Angular 可以把它作为依赖注入到组件中
  • 使用 @Injectable 装饰器来表明一个组件或其它类(比如另一个服务、管道或 NgModule)拥有一个依赖。 依赖并不必然是服务,它也可能是函数或值等等。
  • 通常在构造函数,注入依赖的service: constructor(private service: HeroService) { }

当 Angular 发现某个组件依赖某个服务时,它会首先检查是否该注入器中已经有了那个服务的任何现有实例。如果所请求的服务尚不存在,注入器就会使用以前注册的服务提供商来制作一个,并把它加入注入器中,然后把该服务返回给 Angular。

enter description here

对于要用到的任何服务,你必须至少注册一个提供商。你可以在模块中或者组件中注册这些提供商。
- 当你往根模块中添加服务提供商时,服务的同一个实例会服务于你应用中的所有组件。
- 当你在组件级注册提供商时,你会为该组件的每一个新实例提供该服务的一个新实例, 要在组件级注册,就要在 @Component 元数据的 providers 属性中注册服务提供商

因此,对于模块机共用的service,最好再root模块中provide,这样方便一个实例实现共享和通信。

1.4 路由(Routing)

Angular 的 Router 模块提供了一个服务,它可以让你定义在应用的各个不同状态和视图层次结构之间导航时要使用的路径。 它的工作模型基于人们熟知的浏览器导航约定:

  • 在地址栏输入 URL,浏览器就会导航到相应的页面
  • 在页面中点击链接,浏览器就会导航到一个新页面
  • 点击浏览器的前进和后退按钮,浏览器就会在你的浏览历史中向前或向后导航

1.5 架构图

enter description here

延伸阅读:


作者:Jadepeng
出处:jqpeng的技术记事本--http://www.cnblogs.com/xiaoqi
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

posted @ 2018-05-21 11:44 JadePeng 阅读(...) 评论(...) 编辑 收藏