更新: 2020-11-06
当 ngFor 里面有 ng-template-outlet 然后你要 queryList 就会翻车
<div *ngFor="let group of groupList"> {{ group.label }} <ng-container [ngTemplateOutlet]="template" [ngTemplateOutletContext]="{ $implicit: group }"> </ng-container> </div> <ng-template #template let-group> <div #option="appOption" appOption [appOptionGroup]="group.label" style="color: red;" *ngFor="let option of group.options"> {{ option.value }} </div> </ng-template>
如果我在 component 里 @ViewChildren('option', { read: OptionDirective }), 当 grouplist 发生变化或, option 的顺序可能是错误的.
因为 ngTemplateOutlet 渲染的 template 其实是放在外面的, 而不是在 ngfor 里面
那么如果 group 被 track by 复用的话, 那么新的 group 创建的 option 就会在之前的 option 下面.
它的顺序并不依赖 group 而是在外面。上面这种用法是比较奇葩的,我是因为要复用所以才这样写。 因为想更好的封装办法才对.
更新: 2020-06-01
ngZone.onStable
从 ng 1 我就一直遇到一种情况,有时候拿不到一个值,或者更新不到一个值。
然后你放一个 settimeout 就可以了.
其实关键就是渲染的 timing 不熟悉导致的.
有时候我们修改了我们的属性, 然后希望在 ng 渲染之后去获取到修改后的 dom 的一些值. 比如 width 之类的.
这时候你可能闪过几个 delay 的方式
比如 timeout, promise.resolve, onStable, request animation
那到底哪一个才 ok 呢
promise.resolve 会最早触发, app docheck 还没有运行它就触发了,估计 ng 也用了 promise.resolve, 所以这个基本上是不满足需求的
然后触发的是 app.docheck, 这个时候 ng 开始渲染了.
接着就是 on stable 触发了. 到渲染完成后, 立刻就触发了 onStable, 这个时候如果我们要拿值就 ok 了, 所以推荐用这个
然后才是 request animation
最后是 timeout
有时候, material 组件本身就会用 on stable 来做一些事情,如果我们还想比它后面,那么可以用 request animation 哦
更新: 2020-04-05
@Input 和 cdk coercion
abc.component.ts 有一个·@Input value
当 <abc> 组件实例不会有这个 key, Object.keys 拿不到
当 <abc value > 没有写 = 和 <abc value="" > 效果一样, 里面获取到 empty string 有 key 了
当 <abc [value]> 没有写 = 和 <app [value]=""> 和 <app-abc> 效果一样, 里面没有 key
当 <abc [value]="undefined"> 里面有 key, value 是 undefined
当 <abc [value]="null"> 里面有 key 是 null
当 <abc value="what ever value"> 里面获取到的都是 string value
coercion 的任务就是把 string value 转成 boolean or number 等, 这样在外部写起来就可以比较简单. (当然也有人不鼓励这种写法, 任务 should be 写成 [value]=" 'dada' ")
比如, undefined null false 最终等于 false, 其余情况返回 true
export function coerceBooleanProperty(value: any): boolean { return value != null && `${value}` !== 'false'; }
number 的话就必须是可以转 number 的 value 不然就是 fallback to 0
export function coerceNumberProperty(value: any, fallbackValue = 0) { return _isNumberValue(value) ? Number(value) : fallbackValue; }
更新: 2020-03-22
?. safe natigation operation
javascript 有 obj?.prop 和 ??
目前 ng 只有 ?.
js 的返回值是 undefined, ng 是 null
这里有点不同啦.... ng 的世界大部分都是 null. 可能是这个原因才和 js 不一样吧
更新 : 2020-02-13
当 ng-content 遇上 ng-if
https://github.com/angular/angular/issues/22972
下面这个是正确用法
<ng-template #content><ng-content></ng-content></ng-template> <div *ngIf="flag"> <ng-container *ngTemplateOutlet="content"></ng-container> </div> <div *ngIf="!flag"> <ng-container *ngTemplateOutlet="content"></ng-container> </div>
如果我们不把 ng-content 放入到 template 之后, 那么结果是它永远会放到第一个 element 里面,不过 ngIf 是 true or false
原理就是 ng-content 是不理会其它人的,你放哪里它就只能在哪里,而且只能渲染一次.
所以上面这个方案就很巧妙的实现了.
更新: 2019-11-28
ngProjectAs 和 contentchild
@ContentChild('name') name 是一个模板变量而不是一个 css selector, 通常如果需要 content 的话,我们一般会特地创建指令, 这样比较好管理
<ng-content select="cssSelector" > 这个却是一个 css selector, 有时候会 confuse.
在说说 ngProjectAs
有时候外部的内容被 ng-container 抱起来了
<ng-container> <div myDir >dada</div> </ng-container> <ng-content select="[myDir]" > @ContentChild(MyDirDirective)
由于被 ng-container wrap 了,所以上面 2 个 select 都出不来.
解决方法是
<ng-container ngProjectAs="[myDir]" > <div myDir >dada</div> </ng-container>
ivy 目前有 bug, 希望赶快 fix.
https://github.com/angular/angular/issues/34120
更新 : 2019-01-03
原来 @ContentChild 是可以找到 sibling 指令或组件的
不小心看到 routerLinkActive 的源码,发现它是使用 @Contentchild 来获取 routerlink 的
本来觉得有点怪,contentchild 不是只能获取到 transclude 的指令吗, 可是 <a routerlink routerlinkactive > 也可以 sibling 用啊.
做了测试才知道原来一直都是可以的.
多个 ng-content 时要留意
refer : https://www.youtube.com/watch?v=PTwKhxLZ3jI
<ng-content></ng-content>
after <ng-content></ng-content>
当模板中有超过一个 ng-content 时, 只有最后一个会显示出来. 就是 after 之后的那个.
<ng-content *ngIf="true" ></ng-content> after <ng-content *ngIf="false" ></ng-content>
这样的话, 你可能以为第一个会展现,但其实是不会,因为 ng-content 是最后一个,而最后一个 ngif 确实 false 所以就什么也没展现了.
试试用 ng-template + ng-container 来 append
<ng-template #template> <ng-content></ng-content> </ng-template> <ng-container #target ></ng-container> <div (click)="append()" >append</div>
结果第一个是有 content 的,接下来就没有了. ngFor 也是如此
如果我们可以接受只有其中一个 ngIf 获取到 content 那么,写法可以是这样的.
这个其实也是上面 template + container 的概念做的.
<ng-template #content> <ng-content></ng-content> </ng-template> <ng-container *ngIf="true" > <ng-template [ngTemplateOutlet]="content" ></ng-template> </ng-container> <ng-container *ngIf="true" > <ng-template [ngTemplateOutlet]="content" ></ng-template> </ng-container>
更新 : 2018-12-22
viewchild viewchildren contentchild 使用细节
补上一些之前没有写清楚的细节
XXChild vs XXChildren
child 返回第一个被 select 中的值,
children 返回一个 queryList 对象
child 的好处是简单, 适合用于只有一个值而且这个是固定存在的情况下
children 除了可以获取到多个值以为,另一个特色是它可以 watch, 当数量变化时可以监听到.
contentXX vs viewXX
区别是查找的区域不同
比如你在 test.component 里写了这 2 个 query
view 的查找区域是 test.component.html
ng-content, ng-template内, 内组件的模板都不在其查找范围内哦
<ng-content></ng-content>
<ng-template>
<abc></abc>
</ng-template>
<abc></abc>
上面只有最后一行是在范围内的, 而 abc.html 里头的 component 也不在范围内了. (所以没有层中层找的概念)
content 的查找区域来至于 trancslude
同样的 ng-template, ng-content. 组件内的模板也都不算在范围哦
<test>
<ng-template>
<abc></abc>
</ng-template>
<ng-content></ng-content>
<abc></abc>
<div>
<abc></abc>
</div>
</test>
这里有个值得注意的点
@ContentChild(Abc, { descendants: true })
descendants 子孙, 默认是 false. 意思是只搜索第一层的 element, 最后那个被 div 包起来的 abc 组件是搜索不到的
如果设置是true, 那么 div 内的 abc 才可以获取到. 它的区别只是这个而已哦.
另外还要讲讲 read
XXChild(firstParam).
firstParam 可以是任何依赖注入. 比如一个 class, 一个 token. 虽然说是模板查找但是其实是依赖注入查找来的.
比如 :
ViewChild(Xyz)
假设 abc.compomnent 的 decorator 声明了 providers : [{ provide Xyz, useClass: WhatEverClass }]
那么 ViewChild 是会找到这个 WhatEverClass 的
除了 class token, 还可以是模板变量
这里就会需要用到 read 了
比如 test.html
<abc #abc ></abc> <div #div ></div> <div someDir #dir1 ></div> <div someDir #dir2="someDir" ></div>
viewchild('abc') 获取到 abc component
viewchild('div') 获取到 elementRef 注意这里是 ref 而不是直接拿到 element
viewchild('dir1') 获取到 elementRef 模板变量遇到 component 就会拿 component, 遇到指令不会有设么特别反应,所以依然获取的是 elementRef
viewchild('dir2') 获取到 someDir 因为我们写了一个 =exportAsDir
所以最关键的地方是第 1 个, 当模板变量没有指定获取指令时,如果 element 是 component, ng 会认为我们要的是 component 而不是原生 html element
这个合理, 因为其实 component 当然也是 html element 丫,在 custom element 的情况下也是. 当然对于后端渲染就不一样啦
所以呢,如果我们想要的是原生 element 而不是 component 我们就必须做一些额外的处理.
viewchild('abc', { read : ElementRef })
这样就可以了, 随带一提, 这个默认选 component 的设计, 在我们只使用模板变量时尤其糟糕, 因为我们无法告诉 ng 选择原生 html element 唯一的方法就是通过 viewchild 或者使用另个一指令来曝露. 比如写一个 native 指令来干这个事儿...
<abc native #native="native" ></abc> <div (click)="log(native.element)" >click</div>
最后如果是第 4 个场景
<div someDir #dir2="someDir" ></div>
viewchild('dir2') 会获取到 someDir 但是如果我们硬加上 read, 最后 read 会优胜.
viewchild('dir2', { read : ElementRef }) 最后依然是 elementRef
更新 : 2018-12-19
模板语法 'as' 的原理
<div *ngIf="condition as value; else elseBlock">{{value}}</div>
以前我以为 'as' 应该被视为 NgIfAs 指令, 但是在源码里却没找到
问了大神 Trotyl 后才知道
as 是 let 的严格语法糖,`foo as bar` 就是 `let bar = foo`,不过两者省略内容时的自动补全不同,`as bar` 会被补全为 `<directiveName> as bar` 而 `let bar` 会被补全为 `let bar = $implicit`。。一个很常见的用户误解是认为 as 和前面的表达式有关系,*ngIf="condition as value" 会被直接断句为 *ngIf="condition; as value" 而后补全为 *ngIf="condition; ngIf as value",换成 let 语法就是 *ngIf="condition; let value = ngIf"。。
依据上面这个解析, 如果有 2 给 async 回如何呢 ?
<div *ngIf="(can$ | async) as can && (super$ | async) as super else loading" > {{ super }} </div>
自然是直接报错咯~~
只能有一个 as, 下面这样才对. ng 会用最后一个 async 作为 context.ngIf 的值, 如果我们把顺序调过来, super 的也会跟着变化
<div *ngIf="(can$ | async) && (super$ | async) as super else loading" > {{ super }} </div> <ng-template [ngIf]="(can$ | async) && (super$ | async)" [ngIfElse]="loading" let-super="ngIf" > {{ super }} </ng-template>
如果遇到这种情况,可以使用 combineLatest 做一个合并的流, 或者 nested ngIf 来实现. 虽然看上去有点蠢...哈哈
更新 : 2018-04-02
component 也可以当指令用
<div app-super >kknd</div> <div> <app-super> kknd </app-super> </div>
上面几乎是等价的, 虽然 component 通常都是 element, 但其实也是可以写成 attribute 的, 不过一个 element 只能绑定 1 个.
@Component({ selector: '[app-super]', templateUrl: './super.component.html', styleUrls: ['./super.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush })
更新 : 2018-04-01
结构指令 + 微语法 + template
refer : https://angular.cn/guide/structural-directives#microsyntax (微语法规范)
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
其实之前也说过了,只是说的不详细, 多举些例子熟悉一下.
像上面这种写法,非常适合做动态插入模板.
原理是用一个结构指令把 template 存起来,稍后才输出
<app-super *dada="let contextName = name; let $implicitValue; columns: 100"> {{ $implicitValue }} <br> {{ contextName }} </app-super> <ng-template dada [dadaColumns]="100" let-$implicitValue let-contextName="name"> <app-super>{{ $implicitValue }} <br> {{ contextName }}</app-super> </ng-template>
上面 2 个写法是完全一样的意思, 第一个在 ng 编辑之后会变成第 2 个. (第一个只是美一点罢了)
我们通常会使用第一种写法.
需要注意几个事儿.
* 表示是 ngtemplate 把 super 包起来,
dada 是结构指令
let $implicitValue 可以写成任何的变量, let whatever, 最终它的值是 ngTemplateContext.$implicit
columns 编辑后会变成 [dadaColumns] 就是把 *dada <--这个指令名加上去 prefix. dadaColumns 可以是 input or 另一个指令都可以,
这里有一个点要留意, 就是第一个字大小写, 按理说 dadaColumns input 应该时对应 Columns 第一个字大写,但是 ng 会无视掉, 大小写都可以. 但是呢, ide 会报错.
虽然报错,但是运行时正确的.
let contextName = name 这个对应 变量 = ngTemplateContext.name
在要使用它的地方调用就可以了. 从 dada 里面拿出 template 输入 context
<ng-container *ngTemplateOutlet="dada.template; context: { name : 'xinyao', $implicit : 'value' }"></ng-container>
p,s 微语法的顺序要注意,
*dada="columns: 100;let contextName = name; let $implicitValue;" <-- 这样是不可以的,开头一定要 let ...
dada 指令
import { Directive, TemplateRef, Input } from '@angular/core';
export class Person {
name: string
$implicit = 'value'
}
@Directive({
selector: '[dada]',
exportAs : 'dada'
})
export class DadaDirective {
constructor(
// 通过注入获取到当前模板, 因为指令会放在模板上面,所以可以注入到
//
public template: TemplateRef<Person>
) { }
@Input('dadaColumns') // ng 的规范是
columns: number
}