- 单向从数据源到视图
{{expression}}
[target]="expression"
bind-target="expression"
单向从视图到数据源
(target)="statement"
on-target="statement"
双向
[(target)]="expression"
bindon-target="expression"
DOM property
的值可以改变;HTML attribute
的值不能改变。
在Angular
的世界中,attribute
唯一的作用是用来初始化元素和指令的状态。
当进行数据绑定时,只是在与"元素和指令"的 "property
和事件"打交道,而attribute
就完全靠边站了。如果忘了加方括号,
Angular
会把这个表达式当做字符串常量看待,并用该字符串来初始化目标属性。
下面这个例子把HeroDetailComponent
的prefix
属性初始化为固定的字符串"1+1",而不是模板表达式"2"。Angular
设置它,然后忘记它。
<app-hero-detail prefix="1+1" [hero]="currentHero"></app-hero-detail>
作为对比,[hero]
绑定是组件的 currentHero
属性的活绑定,它会一直随着更新。
- 插值表达式
{{...}}
,先对双花括号中的表达式求值,再把求值的结果转换成字符串。
实际上,在渲染视图之前,Angular
把这些插值表达式翻译成了相应的属性绑定。
- 注意:但数据类型不是字符串时,就必须使用属性绑定了。
<p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p>
<p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p>
- 不管是插值表达式还是属性绑定,都不会允许带有
script
标签的HTML
泄漏到浏览器中,二者都只渲染没有危害的内容。
src/app/app.component.ts
evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax';
src/app/app.component.html
<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>
<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>
-
attribute
绑定
一般情况下,我们通过属性绑定来设置元素的属性property
,而不用字符串设置元素的attribute
。
但考虑ARIA
,SVG
和table
中的colspan/rowspan
等attribute
。 它们是纯粹的attribute
,没有对应的属性可供绑定。
如果想写出类似下面这样的东西,就会暴露出痛点了:
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
会得到这个错误:
Template parse errors: Can't bind to 'colspan' since it isn't a known native property
方括号中的部分不是元素的属性名,而是由attr
前缀,一个点 (.)
和 attribute
的名字组成
<tr><td [attr.colspan]="1 + 1">One-Two</td></tr>
- 双向数据绑定
( [(...)] )
双向绑定语法实际上是属性绑定和事件绑定的语法糖。
- 内置属性型指令
- a.通过绑定到
NgClass
,可以同时添加或移除多个类。
把ngClass
绑定到一个key:value
形式的控制对象
(value
为boolean
值)
src/app/app.component.html
<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special</div>
src/app/app.component.ts
currentClasses: {};
setCurrentClasses() {
// CSS classes: added/removed per current state of component properties
this.currentClasses = {
'saveable': this.canSave,
'modified': !this.isUnchanged,
'special': this.isSpecial
};
}
- b.通过绑定
NgStyle
设置多个内联样式。NgStyle
需要绑定到一个key:value
控制对象。
src/app/app.component.html
<div [ngStyle]="currentStyles">
This div is initially italic, normal weight, and extra large (24px).
</div>
src/app/app.component.ts
currentStyles: {};
setCurrentStyles() {
// CSS styles: set per current state of component properties
this.currentStyles = {
'font-weight': 'bold',
};
}
- 模板输入变量和模板引用变量
- 模板输入变量
hero
前的let
关键字创建了一个名叫hero
的模板输入变量。
这个变量的范围被限制在所重复模板的单一实例上。事实上,你可以在其它内置结构型指令中使用同样的变量名。 - 模板引用变量
#phone
的意思就是声明一个名叫phone
的变量来引用<input>
元素。
模板引用变量的作用范围是整个模板。不要在同一个模板中多次定义同一个变量名,否则它在运行期间的值是无法确定的。
<input #phone placeholder="phone number">
- 你总是可以在"组件自己的模板"中绑定到组件的公共属性,而不用管它们是否输入(
Input
)属性或输出(Output
)属性。
但Angular
需要@Input()
和@Output()
装饰器来标记出那些允许被外部组件绑定到的属性。
声明输入与输出属性:
@Input() hero: Hero;
@Output() deleteRequest = new EventEmitter<Hero>();
另外:@Input get/set
写法
_oprType: string ='VIEW';
@Input("oprType")
get oprType(){
retrun this._oprType;
}
set oprType(oprType){
this._oprType = oprType;
...//其他逻辑处理
}
-
json
管道对调试绑定特别有用:
src/app/app.component.html (pipes-json)
<div>{{currentHero | json}}</div>
它生成的输出是这样的:
{ "id": 0, "name": "Hercules", "emotion": "happy",
"birthdate": "1970-02-25T08:00:00.000Z",
"url": "http://www.imdb.com/title/tt0065832/",
"rate": 325 }
- 生命周期
- 注意:构造函数在所有的生命周期钩子之前执行
ngOnChanges()-》ngOnInit()-》ngDoCheck()-》ngAfterContentInit()-》ngAfterContentChecked()-》ngAfterViewInit()-》ngAfterViewChecked()-》ngOnDestroy()
ngOnInit
生命周期钩子会在 DOM
更新操作执行前触发
-
rxjs
知识
var subject = new Subject<string>();
subject.next(1);
subject.subscribe({
next: (v) => console.log('observerA: ' + v)
});
//observerA: 1
纯(
pure
)管道与非纯(impure
)管道
默认情况下,管道都是纯的。
Angular
只有在它检测到输入值发生了纯变更时才会执行纯管道。
纯变更:是指对原始类型值(String、Number、Boolean、Symbol
)的更改,或者对对象引用(Date、Array、Function、Object
)的更改。
Angular
会忽略对象内部的更改。 如果你更改了输入日期(Date
)中的月份、往一个输入数组(Array
)中添加新值或者更新了一个输入对象(Object
)的属性,Angular
都不会调用纯管道。
Angular
会在每个组件的变更检测周期中执行非纯管道。 非纯管道可能会被调用很多次,和每个按键或每次鼠标移动一样频繁。ElementRef
import { Directive, ElementRef } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'yellow';
}
}
import
语句还从 Angular
的 core
库中导入了一个 ElementRef
符号。
你可以在指令的构造函数中注入 ElementRef
,来引用宿主 DOM
元素。
ElementRef
(对视图中某个宿主元素的引用)通过其 nativeElement
属性给了你直接访问宿主 DOM
元素的能力。
- 注意:允许直接访问
DOM
会导致你的应用在XSS
攻击前面更加脆弱。当需要直接访问
当需要直接访问DOM
时,请把本API
作为最后选择。
优先使用 Angular
提供的模板和数据绑定机制。
或者你还可以看看 Renderer2
(实现自定义渲染器),它提供了可安全使用的 API
—— 即使环境没有提供直接访问原生元素的功能。
import { Directive, ElementRef, Renderer2 } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(el: ElementRef, private renderer: Renderer2) {
//el.nativeElement.style.backgroundColor = 'yellow';
this.renderer.setStyle(this.el.nativeElement, "backgroundColor", 'yellow');
}
}
-
HostListener
把一个事件绑定到一个宿主监听器,并提供配置元数据。
@HostListener('mousedown') onMouseEnter() {
this.highlight(this.highlightColor || this.defaultColor || 'red');
}
mousedown
要监听的事件。
当mousedown
事件发生时,Angular
就会执行所提供的处理器方法onMouseEnter
。
- 你可以根据属性名在绑定中出现的位置来判定是否要加
@Input
。
当它出现在等号右侧的模板表达式中时,它属于模板所在的组件,不需要@Input
装饰器。
当它出现在等号左边的方括号([ ])
中时,该属性属于其它组件或指令,它必须带有@Input
装饰器。
<p [appHighlight]="color">Highlight me!</p>
color
属性位于右侧的绑定表达式中,它属于模板所在的组件。 该模板和组件相互信任。因此 color
不需要 @Input
装饰器。
appHighlight
属性位于左侧,它引用了 HighlightDirective
中一个带别名的属性,它不是模板所属组件的一部分,因此存在信任问题。 所以,该属性必须带 @Input
装饰器。
-
bootstrap
—— 根组件,Angular
创建它并插入index.html
宿主页面。 -
结构型指令中,星号
(*)
写法是个语法糖,Angular
会把它解开成一个<ng-template>
标记,包裹着宿主元素及其子元素。
Angular
会在真正渲染的时候填充<ng-template>
的内容,并且把<ng-template>
替换为一个供诊断用的注释。
<ng-template let-hero="hero">
<div *ngIf="hero" [class.odd]="odd">({{i}}) {{hero.name}}</div>
</ng-template>
- 注意:如果没有使用结构型指令,而仅仅把一些别的元素包装进
<ng-template>
中,那些元素就是不可见的(被注释掉了)。
- 每个宿主元素上只能有一个结构型指令。比如:
*ngFor
和*ngIf
不能放在同一个宿主元素上。
有一个简单的解决方案:把*ngFor
放在一个"容器"元素上,再包装进*ngIf
元素。 这个元素可以使用ng-container
,以免引入一个新的HTML
层级。
<ul>
<ng-container *ngFor="let menu of menus">
<li *ngIf="!menu.children?.length">
</li>
</ng-container>
</ul>
Angular
的 <ng-container>
是一个分组元素,但它不会污染样式或元素布局,因为 Angular
压根不会把它放进 DOM
中。
template: <ng-template #toolbarTemplate [ngTemplateOutletContext]="{ $implicit: this}"></ng-template>
class: @ViewChild("toolbarTemplate") toolbarTemplate: TemplateRef<any>
简化写法:
<ng-template [ngTemplateOutlet]="toolbarTemplate || defaultToolbarTemplate"
[ngTemplateOutletContext]="{ $implicit: this}"></ng-template>
该指令用于基于已有的 TemplateRef 对象,插入对应的内嵌视图。在应用 NgTemplateOutlet 指令时,我们可以通过 [ngTemplateOutletContext] 属性来设置 EmbeddedViewRef 的上下文对象。绑定的上下文应该是一个对象,此外可通过 let语法来声明绑定上下文对象属性名。
angular6.x中ngTemplateOutlet指令的使用示例
表达式中的上下文变量是由"模板引用变量
#aa
"、"模板输入变量let bb
"和"组件的成员cc
"叠加而成的。
优先级:模板输入变量>模板引用变量>指令的上下文变量>组件类的实例模板表达式不能引用全局命名空间中的任何东西,比如
window
和document
。它们也不能调用console.log
或Math.max
。它们只能引用表达式上下文中的内容。声明式组件和入口组件:
(1)声明式组件会在模板中通过组件声明的selector
(比如<cmb></cmb>
)加载组件。
(2)入口组件entryComponents
主要有3类:
- 在
@NgModule
中的bootstrap
声明的根组件 - 路由配置的组件
- 动态组件(例如ts代码里面创建modal需要用组件作为参数)
ngModule中的entryComponents是什么?
-
exports
在模块a
的ngModule
中exports
出一组组件、指令和管道
-》模板b
导入了模块a
-》模块b
下的所有组件的模板,都可以使用模块a
的ngModule
中exports
出的组件、指令和管道 - 父子组件交互
<div class="seconds">{{timer.seconds}}</div>
<app-countdown-timer #timer></app-countdown-timer>
这个本地变量方法#timer
是个简单便利的方法。但是它也有局限性,因为父组件-子组件的连接必须全部在父组件的html
模板中进行。父组件本身的ts
代码对子组件没有访问权。
当父组件类ts
需要这种访问时,可以把子组件作为 ViewChild
,注入到父组件里面。@ViewChild('timer') timer;
- 内容投影
ng-content
父组件页面parent.html
<attchment-upload>abcde</attchment-upload>
被父组件包含的子组件标签页面attchment-upload.html
<div>-- begins --</div>
<ng-content></ng-content>
<div>-- ends --</div>`
输出:
-- begins --
abcde
-- ends --
以上<ng-content>
标签是父组件页面中外来内容的占位符。 它指明在子组件标签页面的哪里插入这些父组件的外来内容abcde
。
注意:不要在<attchment-upload>组件标签的内部放任何内容 —— 除非你想把这些内容投影进这个组件中。
- NgTemplateOutlet基于已有的TemplateRef 对象,插入对应的内嵌视图。
Angular 4.x NgTemplateOutlet
<ng-container *ngTemplateOutlet="greet"></ng-container>
<ng-template #greet><span>Hello</span></ng-template>
或者
<ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
contentTemplate:TemplateRef<any>
this.contentTemplate=...
//TemplateRef就是对应html中的ng-template标签
-
ngIf
写法
<ng-container *ngIf="a.length==0; else elseTemplate">
...
</ng=container>
<ng-template #elseTemplate>
...
</ng-template>
30.ngTemplateOutletContext
<ng-template let-rowData let-rowIndex="rowIndex" let-columns="columns">
<tr>
<td *ngFor="let col of columns;">
// 通过 [ngTemplateOutletContext] 属性来设置 EmbeddedViewRef:commonBodyTemplate的上下文对象
<ng-container *ngTemplateOutlet="commonBodyTemplate;context:{$implicit:col,rowIndex:rowIndex,rowData:rowData,columns:columns}"></ng-container>
</td>
</tr>
</ng-template>
//若 let 语法未绑定任何属性名(let-col),则上下文对象中 $implicit 属性,对应的值col将作为默认值。
<ng-template #commonBodyTemplate let-col let-rowIndex="rowIndex" let-rowData="rowData">
<ng-container *ngIf="col.field=='idx'">
{{rowIndex+1}}
</ng-container>
<ng-container *ngIf="col.field=='attachSize'">
{{(rowData[col.field]||"--")+(rowData["attachSizeUnit"]||"")}}
</ng-container>
<ng-container *ngIf="editable;else elseTemplate">
<ng-container *ngIf="col.field=='fileName'">
<ng-container *ngIf="rowData['attachmentId']">
<a [href]="attachUrls[rowData['attachmentId']]" [download]="rowData[col.field]">{{rowData[col.field]||""}}</a>
</ng-container>
</ng-container>
<ng-container *ngIf="['idx','attachSize','fileName'].indexOf(col.field)==-1">
{{rowData[col.field]||"--"}}
</ng-container>
</ng-container>
<ng-template #elseTemplate>
<ng-container *ngIf="['idx','attachSize'].indexOf(col.field)==-1">
{{rowData[col.field]||"--"}}
</ng-container>
</ng-template>
</ng-template>
在父页面中嵌入<app-attachment></app-attachment>
报错:"app-attachment" is not a unknown element.
原因:app-attachment
子页面模板对应的ts
组件没有包括在父页面所在的module
中。
32.@Input('companyId') companyId: string;
子组件的初始化constructor
取不到companyId
值,ngOnInit()
可以
所以初始化放在ngOnInit
中。
33.响应式表单
this.formGroup=fb.group({
contractid:["123",[Validators.required]],
});
this.theDetailGroup.get("finProductName").setValue(this.finProductName);
this.formGroup.get("docmentId").valueChanges.subscribe(value=>{
if(this.existList.findIndex(item=>item.docmentId==value)>-1){
this.formGroup.get("docmentId").setErrors({duplicate:true});
}
});
this.formGroup.get("docmentId").value;
34.this.fb.group({})
响应式表单控件绑定,应该在生命周期之前,即constructor
构造函数中初始化。因为如果放在ngOnInit
中绑定,在表单初始化前对表单进行赋值操作,会报错。
35.自定义模块中exports
使用
将附件声明到公用模块
import { NgModule } from '@angular/core';
import { CommonModule} from '@angular/common';
import { AttachmentComponent } from './attachment.component';
@NgModule({
imports: [
CommonModule,
],
exports:[
CommonModule,
AttachmentComponent
],
providers:[],
declarations: [
AttachmentComponent
],
entryComponents:[
AttachmentComponent
]
})
export class SharedModule { }
宿主组件所在的module
中引入公共模块
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedModule } from 'src/app/shared';
@NgModule({
declarations: [
],
imports: [
CommonModule,
SharedModule
],
entryComponents:[]
})
export class MineModule { }
36.自定义双向数据绑定
- 父组件
<app-base [(companyId)]="companyId"></app-base>
- 子组件
app-base.ts
_companyId = '';
@Output() companyIdChange = new EventEmitter();
@Input('companyId')
get companyId() {
return this._companyId;
}
set companyId(companyId) {
this._companyId = companyId;
this.companyIdChange.emit(companyId);
}
注意:属性名 + Change
是双向绑定中Output
的固定写法
37.在使用一些第三方的组件的时候,要修改组件的样式。
这种情况下使用:
:host ::ng-deep .className{
新的样式......
}
38.要在html
直接使用某个变量。ts
中可以不要定义这个变量,直接get
。
html:
<div *ngIf="isStatusOkey"></div>
ts:
get isStatusOkey() {
return this.baseInfoConfig.baseInfoData['lpIndentifyStatus'] !== 'OKAY';
}
html:
<mat-grid-tile *ngFor="let item of attachmentTypeList"></mat-grid-tile>
ts:
get attachmentTypeList() {
return this.attachService.getAttachmentTypeList();
}
39.@injectable 一般用在Angular的Service中,他的意思是该Service实例可以注入到其他的service、component或者其他实例里面。换句话说,就是其他的实例要依赖他。
举例来说这里有一个service,声明了Injectable
@Injectable({
providedIn: 'root',
})
export class MockService(){
public foo(){
console.log("Mock service say: Hello world");
}
}
@Injectable({ providedIn: 'root', })代表这个类将被注入到根module中,也就是说该module中被注入了唯一的MockService实例。其他component就可以使用这个实例
export class MockComponent{
constructor(private mockService:MockService){
this.mockService.foo();
}
}
在这个例子里面,mock service只创建了唯一的实例,你看官方文档还有其他的provide方式。
40.如果您在代码中手动订阅EventEmitter,那么您必须取消订阅。angular – 您是否需要取消订阅@Output EventEmitter
41.Angular @HostBinding()和@HostListener()用法