学习网站:https://angular.cn/docs
angular的装饰器
类装饰器(class decorator)
装饰器会出现在类定义的紧前方,用来声明该类具有指定的类型,并且提供适合该类型的元数据。
可以用下列装饰器来声明Angular的类:
- @Component()
- @Directive()
- @Pipe()
- @Injectable()
- @NgModule()
类字段装饰器(class field decorator)
出现在类定义中属性紧前方的装饰器语句,用来声明该字段的类型。比如@Input 和 @Output
生命周期钩子
每个组件都有一个被Angular管理的生命周期
组件生命周期钩子概览
指令和组件的实例有一个生命周期:当Angular新建、更新和销毁它们时触发。
每个接口都有唯一的一个钩子方法,它们的名字是由接口名再加上ng前缀构成的。比如,OnInit接口的钩子方法叫做ngOnInit,Angular在创建组件后立刻调用它:
export class PeekABoo implements OnInit { constructor(private logger: LoggerService) { } // implement OnInit's `ngOnInit` method ngOnInit() { this.logIt(`OnInit`); } logIt(msg: string) { this.logger.log(`#${nextId++} ${msg}`); } }
没有指令或者组件会实现所有这些接口,并且有些钩子只对组件有意义。只有在指令/组件中定义过的那些钩子方法才会被Angular调用。
生命周期的顺序
当Angular使用构造函数新建一个组件或指令后,就会按下面的顺序在特定时刻调用这些生命周期钩子方法
ngOnChanges()
当Angular(重新)设置数据绑定输入属性时响应。该方法接受当前和上一属性值的SimpleChanges对象
在ngOnInit()之前以及所绑定的一个或多个输入属性的值发生变化时都会调用。
ngOnInit()
在Angular第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件
在第一轮ngOnChanges()完成之后调用,只调用一次。
ngDoCheck()
检测,并在发生Angular无法或不愿意自己检测的变化时做出反应
在每个变更检测周期中,紧跟在ngOnChanges()和ngOnInit()后面调用。
ngAfterContentInit()
当Angular把外部内容投影进组件/指令的视图之后调用
第一次ngDoCheck()之后调用,只调用一次。
ngAfterContentChecked()
每当Angular完成被投影组件内容的变更检测之后调用
ngAfterContentInit()和每次ngDoCheck()之后调用
ngAfterViewInit()
当Angular初始化完组件视图及其子视图之后调用
第一次ngAfterContentChecked之后调用,只调用一次
ngAfterViewChecked()
每当Angular做完组件视图和子视图的变更检测之后调用
ngAfterViewInit()和每次ngAfterContentChecked()之后调用
ngOndestroy()
每当Angular每次销毁指令/组件之前调用并清扫。在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏。
在Angular销毁指令/组件之前调用。
在TypeScript指令类中添加接口是一项最佳实践,它可以获得强类型和IDE等编辑器带来的好处。
Angular的其它子系统除了有这些组件钩子外,还可能有它们自己的生命周期钩子
Angular中的依赖注入
参考地址:https://angular.cn/guide/dependency-injection
依赖注入(DI)是一种重要的应用设计模式。Angular有自己的DI框架,在设计应用时常会用到它,以提升它们的开发效率和模块化程度。
依赖,是当类需要执行其功能时,所需要的服务或对象。DI是一种编码模式,其中的类会从外部源中请求获取依赖,而不是自己创建它们。
这里有一个英雄管理的例子,省略……
这种方法在原型阶段有用,但是不够健壮、不利于维护。一旦你想要测试该组件或想要从远程服务器获得英雄列表,就不得不修改HeroesListComponent的实现,并且替换每一处使用了HEROES模拟数据的地方。
创建和注册可注入的服务
DI框架让你能从一个可注入的服务类(独立文件)中为组件提供数据。为了演示,我们还会创建一个用来提供英雄列表的、可注入的服务类,并把它注册为该服务的提供商。
@Injectable()是每个Angular服务定义中的基本要素。
用服务提供商配置注入器
我们创建的类提供了一个服务。@Injectable()装饰器把它标记为可供注入的服务,不过在你使用该服务的provider提供商配置好的Angular的依赖注入器之前,Angular实际上无法将其注入到任何位置。
该注入器负责创建服务实例,并把它们注入到像HeroListComponent这样的类中。你很少需要自己创建Angular的注入器。Angular会在执行应用时为你创建注入器,第一个注入器是根注入器,创建于启动过程中。
提供商会告诉注入器如何创建该服务。要想让注入器能够创建服务(或提供其他类型的依赖),你必须使用某个提供商配置好注入器。
提供商可以是服务器本身,因此注入器可以使用new来创建实例。你还可以定义多个类,以不同的方式提供同一个服务,并使用不同的提供商来配置不同的注入器。
注入器是可继承的,这意味着如果指定的注入器无法解析某个依赖,它就会请求父注入器来解析它。组件可以从它自己的注入器来获取服务、从其祖先组件的注入器中获取,从其父NgModule的注入器中获取,或从root注入器中获取。
你可以在三种位置之一设置元数据,以便在应用的不同层级使用提供商来配置注入器:
在服务本身的@Injectable()装饰器中
在NgModule的@NgModule()装饰器中
在组件的@Component()装饰器中
@Injectable()装饰器具有一个名叫providedIn的元数据选项,在那里你可以指定把被装饰类的提供商放到root注入器中,或某个特定NgModule的注入器中。
@NgModule()和@Component()装饰器都有用一个providers元数据选项,在那里你可以配置NgModule级或组件级的注入器。
所有组件都是指令,而providers选项是从@Directive()中继承来的。你也可以与组件一样的级别为指令、管道配置提供商。
注入服务
HeroListComponent要想从HeroService中获取英雄列表,就得要求注入HeroService,而不是自己使用new来创建自己的HeroService实例。
你可以通过制定带有依赖类型的构造函数参数来要求Angular在组件的构造函数中注入依赖项。
下面的代码是HeroListComponent的构造函数,它要求注入HeroService。
当然,HeroListComponent还应该使用注入的这个HeroService做一些事情。
import { Component } from '@angular/core'; import { Hero } from './hero'; import { HeroService } from './hero.service' @Component({ selector: 'app-hero-list', template: `{{hero.id}} - {{hero.name}} ({{hero.isSecret ? 'secret' : 'public'}})` }) export class HeroListComponent { heroes: Hero[]; constructor(heroService: HeroService) { this.heroes = heroService.getHeroes(); } }
对比一下没有注入服务的代码如下:
import { Component } from '@angular/core'; import { HEROES } from './mock-heroes'; @Component({ selector: 'app-hero-list', template: `{{hero.id}} - {{hero.name}}` }) export class HeroListComponent { heroes = HEROES; }
必须在某些父注入器中提供HeroService。
HeroListComponent并不关心HeroService来自哪里。如果你决定在AppModule中提供HeroService,也不必修改HeroListComponent.
注入器树与服务实例
在某个注入器的范围内,服务是单例的。也就是说,在指定的注入器中最多只有某个服务的最多一个实例。
应用只有一个根注入器。在root或AppModule级提供UserService意味着它注册到了根注入器上。在整个应用中只有一个UserService实例,每个要求注入UserService的类都会得到这一个服务实例。除非你在子注入器中配置了另一个提供商。
Angular DI具有分层注入体系,这意味着下及注入器也可以创建它们自己的服务实例。Angular会有规律的创建下级注入器。每当Angular创建一个在@Component()中指定了providers的组件实例时,它也会为该实例创建一个新的子注入器。类似的,在运行期间加载一个新的NgModule时,Angular也可以为它创建一个拥有自己的提供商的注入器。
子模块和组件注入器彼此独立,并且会为所提供的服务分别创建自己的实例。当Angular销毁NgModule或组件实例时,也会销毁这些注入器以及注入器中的那些服务实例。
借助注入器继承机制,你仍然可以把全应用级的服务注入到这些组件中。组件的注入器是其父组件注入器的子节点,它会继承所有的祖先注入器,其终点则是应用的根注入器。Angular可以注入该继承谱系中任何一个注入器提供的服务。
比如,Angular既可以把HeroComponent中提供的HeroService注入到HeroListComponent,也可以注入AppModule中提供的UserService
组件
显示数据
https://angular.cn/guide/displaying-data
使用插值显示组件属性
import { Component } from '@angular/core'; @Component({ selector: 'hero-component', template: `{{title}}
我最喜欢的英雄是:{{myHero}}
英雄们:
` }) export class HeroComponent { title = 'Tour of Heroes'; myHero = '孙红雷'; }
为数据创建一个类
(内容省略)
小结:
- 带有双花括号的插值来显示一个组件属性
- 用ngFor显示数组
- 用一个TypeScript类来为你的组件描述模型数据并显示模型的属性
- 用ngIf根据一个布尔表达式有条件地显示一段HTML
模板语法
表达式也可以引用模板中的上下文属性,例如模板输入变量,
let customer
,或模板引用变量 #customerInput
。
表达式使用指南
当使用模板表达式时,请遵循下列指南:
- 非常简单
- 执行迅速
- 没有可见的副作用
简单
虽然也可以写复杂的模板表达式,不过最好避免那样做。
属性名或方法调用应该时常态,但偶然使用逻辑取反!也是可以的。其他情况下,应该把应用程序和业务逻辑限制在组件中,这样它才能更容易开发和测试。
快速执行
Angular会在每个变更检测周期后执行模板表达式。变更检测周期会被多种异步活动触发,比如Promise解析、HTTP结果、定时器时间、按键或鼠标移动。
表达式应该快速结束,否则用户就会感到拖沓,特别是在较慢的设备上。当计算代价较高时,应该考虑缓存哪些从其他值计算得出的值。
没有可见的副作用
模板表达式除了目标属性的值以外,不应该改变应用的任何状态。
这条规则是Angular“单向数据流”策略的基础。永远不用担心读取组件值可能改变另外的显示值。在一次单独的渲染过程中,视图应该总是稳定的。
幂等的表达式是最理想的,因为它没有副作用,并且可以提高Angular的变更检测性能。用Angular术语来说,幂等表达式总会返回完全相同的东西,除非其依赖值之一发生了变化。
在单独的一次事件循环中,被依赖的值不应该改变。如果幂等的表达式返回一个字符串或数字,连续调用它两次,也应该返回相同的字符串或数字。如果幂等的表达式返回一个对象(包括Date或Array),连续调用它两次,也应该返回同一个对象的引用。
对于*ngFor,这种行为有一个例外。*ngFor具有trackBy功能,在迭代对象时它可以处理对象的相等性。
模板语句
模板语句用来响应由绑定目标(如HTML元素、组件或指令)触发的事件。模板语句将在事件绑定一节看到,它出现在=号右侧的引号中,就像这样:(event)="statement".
模板语句有副作用。这是事件处理的关键。因为你要根据用户的输入更新应用状态。
响应事件是Angular中“单向数据流”的另一面。在一次事件循环中,可以随意改变任何地方的任何东西。
和模板表达式一样,模板语句使用的语言也像JavaScript。模板语句解析器和模板表达式解析器有所不同,特别之处在于它支持基本赋值(=)和表达式链(;和,).
语句上下文可以引用模板自身上下文中的属性。在下面的例子中,就把模板的$event对象、模板输入变量(let hero)和模板引用变量(#heroForm)传给了组件中的一个事件处理器方法。
绑定语法:概览
数据绑定是一种机制,用来协调用户可见的内容,特别是应用数据的值。虽然也可以手动从HTML中推送或拉取这些值,但是如果将这些任务转交给绑定框架,应用就会更易于编写、阅读和维护。您只需声明数据源和目标HTML元素之间的绑定关系就可以了,框架会完成其余的工作。
Angular提供了多种数据绑定方法。绑定类型可以分为三类,按数据流的方向分为:
从数据源到视图
绑定类型:插值、属性、Attribute、CSS类、样式
分类:单向,从数据源到视图
语法:
{{expression}} [target]="expression" bind-target="expression"
从视图到数据源
绑定类型:事件
分类:从视图到数据源的单向绑定
语法:
(target)="statement"
on-target="statement"
双向:视图到数据源到视图
分类:双向
语法:
[(target)]="expression"
bindon-target="expression"
HTML attribute与DOM property的对比
理解HTML属性和DOM属性之间的区别是了解Angular绑定如何工作的关键。Attribute是由HTML定义的。Property是从DOM节点访问的
一些HTML Attribute可以1:1映射到Property;例如“id”
某些HTML Attribute没有相应的Property。例如,aria-*
某些DOM Property没有相应的Attribute。例如,textContent
重要的是要记住,HTML Attribute和DOM Property是不同的,就算它们具有相同的名称也是如此。在Angular中,HTML Attribute的唯一作用是初始化元素和指令的状态
模板绑定使用的是Property和事件,而不是Attribute
编写数据绑定时,您只是在和目标对象的DOM Property和事件打交道
该通用规则可以帮助您建立HTML Attribute和DOM Property的思维模型:属性负责初始化DOM属性,然后完工。Property值可以改变;Attribute值则不能。
此规则有一个例外。可以通过setAttribute()来更改Attribute,接着它会重新初始化相应的DOM属性。
范例1:
当浏览器渲染 时,它会创建一个对应的DOM节点,其value Property已初始化为“Sarah”
<input type="text" value="Sarah">
当用户在中输入Sally时,DOM元素的value Property将变为Sally。但是,如果使用input.getAttribute('value')查看HTML的Attribute value,则可以看到该attribute保持不变——它返回了Sarah
范例2:禁用按钮
disabled Attribute是另一个例子。按钮的disabled Property默认为false,因此按钮是启用的。
当你添加disabled Attribute时,仅仅它的出现就将按钮的disabled Property初始化成了true,因此该按钮就被禁用了
<button disabled>测试按钮button>
添加和删除disabled Attribute会禁用和启用该按钮。但是Attribute的值无关紧要,这就是为什么您不能通过编写来启用此按钮的原因。
要控制按钮的状态,请设置disabled Property
虽然技术上说你可以设置[attr.disabled]属性绑定,但是它们的值是不同的,Property绑定要求一个布尔值,而其相应的Attribute绑定则取决于该值是否为null
<input [disabled]="condition ? true : false"> <input [attr.disabled]="condition ? 'disabled' : null">
Property绑定比Attribute绑定更值观。
绑定类型与绑定目标
数据绑定的目标是DOM中的对象。根据绑定类型,该目标可以是Property名(元素、组件或指令的)、事件名(元素、组件或指令的),有时是Attribute名。
绑定类型:属性
目标:元素的property,组件的property,指令的property
语法示例:
<div> <img [src]="itemImageUrl" width="120"> <app-hero-detail [hero]="currentHero">app-hero-detail> <div [ngClass]="{'special': isSpecial}">div> div>
绑定类型:事件
目标:元素的事件、组件的事件、指令的事件
语法示例:
<div> <button (click)="onSave()">保存button> <app-hero-detail (deleteRequest)="deleteHero()">app-hero-detail> <div (myClick)="clicked=$event" clickable>点击我呀div> div>
绑定类型:双向绑定
目标:事件与property
语法示例:
<input [(ngModel)]="name">
绑定类型:Attribute
目标:attribute(例外情况)、
语法示例:
<button [attr.aria-label]="help">helpbutton>
绑定类型:CSS类
目标:class property
<div [class.special]="isSpecial">Specialdiv>
绑定类型:样式
目标:style property
<button [style.color]="isSpecial ? 'res' : 'green'">button>
Property绑定[property]
使用Property绑定到目标元素或指令@Input()装饰器的set型属性。
单向输入
Property绑定的值在一个方向上流动,从组件的Property变为目标元素的Property.
您不能使用属性绑定从目标元素读取或拉取值。同样的,您也不能使用属性绑定在目标元素上调用方法。如果元素要引发事件,则可以使用事件绑定来监听它们。
例子1:img元素的src Property
<img [src]="itemImageUrl">
例子2:绑定到colSpan Property
<tr> <td [colSpan]="2">Span 2 columnstd> tr>
例子3:button disabled Property
<button [disabled]="isUnchanged">禁用按钮button>
例子4:设置指令的属性
<p [ngClass]="classes">[ngClass] binding to the classes property making this bluep>
例子5:设置自定义组件的模型属性——这是一种父级和子级组件进行通信的好办法
<app-item-detail [childItem]="parentItem">app-item-detail>
绑定目标
包裹在方括号中的元素属性名标记着目标属性。下列代码中的目标属性是image元素的src属性
<img [src]="itemImageUrl">
还有一种使用bind-前缀的替代方案
<img bind-src="itemImageUrl">
从技术上讲,Angular将这个名称与指令的@Input()进行匹配,它来自指令的inputs数组中列出的Property名城之一或是用@Input装饰的属性。这些输入都映射到指令自身的属性。
如果名字没有匹配上已知指令或元素的属性,Angular就会报告“未知指令”的错误
尽管目标名称通常是Property的名称,但是在Angular中,有几个常见属性会自动将Attribute映射为Property。这些包括class/className,innerHtmll/innerHTML和tabindex/tabIndex
消除副作用
模板表达的计算应该没有明显的副作用。表达式语言本身或您编写模板表达式的方式在一定程度上有所帮助。您不能为属性绑定表达式中的任何内容赋值,也不能使用递增和递减运算符。
最佳实践是坚持使用属性和返回值并避免副作用的方法。
返回正确的类型
模板表达式的计算结果应该是目标属性所需要的值类型。如果target属性需要一个字符串,则返回一个字符串;如果需要一个数字,则返回一个数字;如果需要一个对象,则返回一个对象,以此类推。
在下面的例子中,ItemDetailComponent的childItem属性需要一个字符串,而这正是你要发送给属性绑定的内容
<app-item-detail [childItem]="parentItem">app-item-detail>
一次性字符串初始化
当满足下列条件时,应该省略括号:
目标属性接受字符串值
字符串是一个固定值,您可以直接将其放入模板中
这个初始值永不改变
attribute、class和style绑定
模板语法为那些不太适用使用属性绑定的场景提供了专门的单向数据绑定形式。
要在运行中的应用查看Attribute绑定、类绑定和样式绑定
attribute绑定
可以直接使用Attribute绑定设置Attribute的值。一般来说,绑定时设置的是目标的Property,而Attribute绑定是唯一的例外,它创建和设置的是Attribute
<button [attr.aria-label]="actionName">{{actionName}} with Ariabutton>
类绑定
下面是在普通HTML中不用绑定来设置class Attribute的方法:
<div class="foo bar">Some textdiv>
创建单个类的绑定
<div [class.foo]="hasFoo">div>
输入类型:boolean | undefined | null
输入值范例:true, false
创建多个类的绑定
<div [class]="classExpr">div>
输入类型1:string
输入值范例:"my-class-1 my-class-2 my-class-3"
输入类型2:{[key: string]:boolean | undefined | null}
输入值范例:{foo: true, bar: false}
尽管此技术适用于切换单个类名,但在需要同时管理多个类名时请考虑使用NgClass指令
样式绑定
普通HTML设置style属性:
<div style="color: blue;">一些文字div>
创建单个样式的绑定 [style.width]="width"
<div [style.color]="colorName">一些文字div>
<div [style.width]="width">单一样式绑定div> <div [style.width.px]="width">带单位的单一样式绑定div>
多个样式绑定 [style]="styleExpr"
<div [style]="styleExpr">div>
styleExpr的值可以是字符串、对象或数组
styleExpr = "width:100px;height:100px" styleExpr = { width: '100px', height: '100px' } styleExpr = ['width', '100px']
样式的优先级规则
一个HTML元素可以把它的CSS类列表和样式值绑定到多个来源(例如,来自多个指令的宿主host绑定)
当对同一个类名或样式属性存在多个绑定时,Angular会使用一组优先级规则来解决冲突,并确定最终哪些类或样式会应用到该元素中
样式的优先级(从高到低)
模板绑定
1.属性绑定,例如:
<div [class.foo]="hasFoo">div> <div [style.color]="color">div>
2.Map绑定,例如:
<div [class]="classExpr">div> <div [style]="styleExpr">div>
3.静态值,例如:
<div class="foo">div> <div style="color: blue">div>
指令宿主绑定
1.属性绑定,例如:
host: {'[class.foo]': 'hasFoo'}
host: {'[style.color]': 'color'}
2.Map绑定,例如:
host: {'[class]': 'classExpr'}
host: {'[style]': 'styleExpr'}
3.静态值,例如:
host: {'class': 'foo'}
host: {'style': 'color:blue'}
组件宿主绑定
1.属性绑定,例如:
host: {'[class.foo]':'hasFoo'}
host: {'[style.color]': 'color'}
2.Map绑定,例如:
host: {'[class]': 'classExpr'}
host: {'[style]': 'styleExpr'}
3.静态值,例如:
host: {'class': 'foo'}
host: {'style': 'color:blue'}
某个类或样式绑定越具体,它的优先级就越高
对具体类(例如[class.foo])的绑定优先于一般化的[class]绑定,对具体样式(例如[style.bar])的绑定优先于一般化的[style]绑定。
委托优先级较低的样式
更高优先级的样式可以使用undefined值委托给低级的优先级样式。虽然把style属性设置为null可以确保该样式被移除,但把它设置为undefined会导致Angular回退到该样式的次高优先级。
管道
服务
模板
组件传值
dom元素操作