angular2 学习笔记 ( Component 组件)

更新 : 2019-01-03css

原来 @ContentChild 是能够找到 sibling 指令或组件的html

不当心看到 routerLinkActive 的源码,发现它是使用 @Contentchild 来获取 routerlink 的 html5

原本以为有点怪,contentchild 不是只能获取到 transclude 的指令吗, 但是 <a routerlink routerlinkactive > 也能够 sibling 用啊. c#

作了测试才知道原来一直都是能够的.后端

 

多个 ng-content 时要留意缓存

refer : https://www.youtube.com/watch?v=PTwKhxLZ3jI性能优化

<ng-content></ng-content>
after
<ng-content></ng-content>

当模板中有超过一个 ng-content 时, 只有最后一个会显示出来. 就是 after 以后的那个.app

<ng-content *ngIf="true" ></ng-content>
after
<ng-content *ngIf="false" ></ng-content>

这样的话, 你可能觉得第一个会展示,但实际上是不会,由于 ng-content 是最后一个,而最后一个 ngif 确实 false 因此就什么也没展示了.dom

试试用 ng-template + ng-container 来 appendasync

<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="let contextName = name; let $implicitValue; columns: 100" <-- OK
*dada="let $implicitValue; let contextName = name; columns: 100" <-- OK

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 

}
View Code

 

 

更新 : 2018-03-11

[attr.href]="null"  null 能够把 attribute href 给移除.

 

更新 : 2018-03-09

补充 : 

constructor 阶段
app.html
view scan 顺序是 上到下,外到里 (dir 的顺序不是预计你放哪一个在前面,而是 declarations 的顺序)
<1 3 2 >
   <4></4>
</1>
<5></5>
依照顺序每一个 component and directive constructor 运行.
这一个 view 搞定后, 进入 1 comp 的 view 重复上面的 view scan,一直重复往里面进, 直到没有 component or directive 了就退回上一个 view
好比退回到这 view, 再进入 4 的 view... 而后 5 的 view..
搞定后这个阶段就结束了。全世界的 comp and dir 都 constructor 了.

onInit, onContentInit, ContentChecked, render 阶段
依照上面的顺序,逐个 oninit, 这时 @input 就有了. 但要当心,由于 1 comp onInit 比 2 dir 早
若是 1 comp 依赖 2 dir 的值就要当心哦,
你能够依赖 2 dir constructor 后的值,但不能够依赖 2 dir onInit 的值 (由于它都尚未 onInit)
这一个 view 搞定 onInit 后, 先不要进入 1 comp 的 view。
而是继续在这个 view 跑 onContentInit 和 ContentChecked
顺序是这样的 里到外,上到下 (dir 后于 comp 哦)
<2 4 3>
   {{ render1 }}
   <1>{{ render2 }}</1>
</2>
<5></5>
onContentInit和ContentChecked 是一块儿跑的,而不是跑彻底部人的 onContentInit 才跑 ContentChecked. 是紧接着一块儿,不是分开哦.
这一个 view 完成后, 就跑这一页的 render,这时 render1,2 的 getter 就会触发了.
到这里 app view 渲染算是完成了,如今要处理它的孩子们.
咱们从 2 comp 的 view 开始运行 onInit 阶段. 而后递归进去..
这里有个关键就是,何时一个 comp 的 viewInit 会被触发.
答案是 这个 comp 的 parent view 处理完全部 comp init 阶段后.
好比 2 comp 的 viewinit 何时触发.
2 comp view 即便 render 了它也不会触发.
而是等到 2 comp 的 parent 也就是 app view 处理完全部 comp (2,4,3,1,5 comps) 以后, 才会逐个调用 2,4,3,1,5 的 viewInit and viewchecked.
这 2 也是紧接着一块儿触发的。

ng 的顺序 

start from html
scan 上到下,外到里 把全部组件和指令都实例化 constructor
note : constructor 阶段拿不到 input 值
顺序是这样
app.component.html
<1>
<2>{{ renderValue1 }}</2>
</1>
{{ renderValue2 }}
<3></3>
完了以后按顺序进入组件的模板, 而后重复上面的实例化一直 loop 到最里面里面完.
所有实例化完后,回到最初的模板.
开始一个个组件 OnInit, 顺序如出一辙
init 的时候就能够拿到 input 值了,若是有 viewchild 也都拿到了
由于全部组件都实例化了嘛, 不过特别注意,虽然你拿到 viewchild 组件,可是这些组件都尚未 OnInit 哦.
这也是为何会有 afterViewInit 的意思了。懂了吧.
这一模板 OnInit 完了以后
开始一个个组件 run AfterContentInit and AfterContentCheck, 上到下, 里到外
为何是这个时候 run 呢?
由于咱们上一个步骤才把这一页全部的组件 OnInit 啊.
好比 <2></2> 就是一个 transclude 的, 咱们刚把它 Oninit 好,如今就要通知 <1></1>
调用它的 afterContentInit and checked 了
特别注意, 虽然这里调用了checked, 但其实目前这个模板是尚未 reader 的
好比 renderValue 是一个 getter, 这个时候, 是尚未被调用过的.
等到这一模板全部的 afterContentInit and checked 后, 这个模板就开始 render 了
renderValue getter 被触发.
这一模板算是搞定一大半了, 如今咱们就开始进入 <1> 的模板
在 <1> 的模板里,要作的事情依旧是一个个组件 OnInit -> afterContent -> render
和刚才同样. 假设这个 <1> 模板 reder 完了以后发现, 发现已经没有子层组件能够继续深刻了。
那么它就会调用 <1> 的 AfterViewInit.
而后退出这个模板回到上一个.
因此这个进入返回的循环就成立了。
当咱们深刻完 <3></3> 而且返回了以后,最初的模板就没有组件能够在深刻了。这时咱们就调用
AfterViewInit 假设咱们最初这个模板就是 AppComponnt, 那么 AppCompoennt.AfterViewInit 后
整个循环也就结束了.

 

实例化全部的 component and 指令 (全部包括子孙的 view 里面的)

init 这个模板的全部 component (外面到里面)

template 若是在这个模板上会被渲染的话, 会先去 render template 的内容, 好比 *ngIf='true', templateoutlet 

contentInit 这个模板的全部 component (里面到外面)

render 这个模板 (binding data)

递归的进入子孙层, 循环上面的顺序 

循环完了之后, viewInit 这个模板的 component (里面到外面), 概念 : component 的 viewInit 是依据使用它的组建的模板决定执行时间的,不是说这个 component render 了之后就立刻执行 viewInit. 注意哦

 

更新 : 2018-02-08 

ng 的顺序 

一个组件的顺序很好理解 construtor -> init -> content -> view 

但层中层有时候就容易昏头了 

这里记入一下, 

1. 全部的 construtor 必定是先执行的 (一直到子孙层, 所有执行, 除了在 ng-template 的不会立刻运行)

2. 把模板中全部的 component onInit 一遍, 上到下,外到里 

3. 而后在模板中寻找拥有 transclude 的 component, 并运行 afterContent 里到外 

4.只要模板中存有其它 component, 就必须等到这些 component 都 after view 了, 本身才能 after view. 

4. 模板中的 component 要 afterviewinit 必须等到整个模板的子孙层都遍历 2-4 一遍, 子孙所有好了,
在依据上到下,里到外去把模板中每个 component 运行 afterviewinit 另外说一点,  ng-template + transclude content + ngtemplateoutlet 其实就是把模板内的组件移入子层,它会在 aftercontent 以后才运行 construtor, 而后其它的顺序就连上普通的了. 

我以前一直觉得 template 会在 after view 以后才跑,其实不会, 它和通常 component 是同样的执行只是 construtor 有点奇葩而已.  

看例子 : youtube link :  https://www.youtube.com/watch?v=rQEfUG3GN40&feature=youtu.be (给我本身看的)

 

 

更新 : 2018-02-01

viewchild 的循环引用 

有时候咱们会遇到循环引用好比 parent 使用了 viewchild 同时 child 又 inject 了 parent component 

这时咱们可使用 

@ViewChildren(forwardRef(() => SZoomComponent))
 或者是  construtor @Inject(forwardRef(() => SZoomComponent))
2者运用场景不一样,这里不细说了。 
 
 
 

更新 : 2017-10-18 

当 local variable 遇到 ngIf 

<div (click)="input.focus()" >click</div>
<input *ngIf="true" #input type="text">

上面 focus 会报错, 由于 ngIf 会打乱顺序. 

解决方法就是使用 viewchild 

  @ViewChild('input', { read : ElementRef })
  input : ElementRef
<div (click)="input.nativeElement.focus()" >click</div>
<input *ngIf="true" #input type="text">

注意这时 input 变成了 ElementRef 类型了 

 

select option 用 [value] 和 [ngValue] 的区别 

基本上用 [ngValue] 就对了, [value] 只能是 String.

若是用 mat-option 那是没有 [ngValue] 的, 可是它的 [value] 其实就是 [ngValue] 的功能

 

更新 : 2017-10-14 

ViewChildren + 派生类 

<form [formGroup]="form"
    [fGroup]="edm"
    (ngSubmit)="submit()">
    <s-mat-input controlName="name"></s-mat-input>
    <s-mat-upload controlName="images"></s-mat-upload>
    <button type="submit">submit</button>
</form>

好比咱们想把全部的 control select 出来 

app.component.ts

  @ViewChildren(AbstractAccessorComponent, { read: AbstractAccessorComponent })
  accessors: QueryList<AbstractAccessorComponent>

mat-input.ts 经过依赖注入就能够了 

@Component({
  selector: 's-mat-input',
  templateUrl: './input.component.html',
  styleUrls: ['./input.component.scss'],
  providers: [{
    provide: AbstractAccessorComponent,
    useExisting: forwardRef(() => InputComponent)
  }]
})
export class InputComponent extends AbstractAccessorComponent implements OnInit {
  
  required = false;

  ngOnInit() {
    super.ngOnInit();
    this.required = this.fControl.validators.find(v => v.name == 'required') != null;
    this.inputType = this.fControl.ui.accessorType.substring('input'.length).toLowerCase();
  }
}

 

 

 

更新 : 2017-09-05 

<app-abc [comp]="ttc" ></app-abc>
<app-efg #ttc ></app-efg>

组件能够这样沟通, 但执行顺序咱们要搞清楚哦. 

依据 element 的结构, app-abc 会先被 onInit, 这时咱们能够获取到 efgComponent 实例, 可是这个 efg 实例只是个对象有一些原始值, 但尚未被 onInit 过. 因此若是咱们想依据它来作渲染, 那么要 settimeout 哦。 

 

 

 

 

更新 2017-08-30 

监听 transclude components 变化

test-dada.component.html

<test-dada>
    <dada-child *ngFor="let item of items"></dada-child>
</test-dada>

dada-child.component.ts 

export class DadaComponent implements OnInit, AfterContentInit {

  @ContentChildren(DadaChildComponent, { read : DadaChildComponent }) 
  dadaChildComponents: QueryList<DadaChildComponent>

  ngAfterContentInit() {
     this.dadaChildComponents.changes.subscribe((c) => {
       console.log('changed', c);
   console.log(c === this.dadaChildComponents); // 是同样的 }); } }

test-dada.component.ts 

changItem() {
  this.items.push({ Id : 4, value : 'd' }); // ok
  this.items = []; // ok
  this.items[0].Id = 5; // ok or fail (依据有没有使用 trackby)
  this.items[0].value = 'dada'; // fail
}

无论有没有使用 onPush, 无论有没有 immutable, ng 均可以监听到 array 的增长和减小. 须要注意的是 ng 是i经过 trackby 来作对比的, 而后判断 array 的存在与否. 

 

 

要在 component 内绑定全局事件的话,可使用 @HostListener, 它会随着 component destroy 而 unbind, 很方便的哦.

export class StoogesAppComponent implements OnInit {
  @HostListener('document:click', ['$event'])
  private documentClick(event: Event) {
    this.globalClicked$.emit(null);
  }
  @HostListener('window:resize', ['$event'])
    onResize(event) {
    console.log(event.target.innerWidth);
  }
 }

 

 

更新 2017-08-29 

ng-template 使用 

以前讲过, 不过例子不整齐, 这里重写一个 

<app-dada>
  <ng-template #resultTemplate let-insideValue="insideValue">
    {{ insideValue }}
  </ng-template>
  <ng-template #optionTemplate let-i="index" let-item="$implicit">
    {{ i }} : {{ item }}
  </ng-template>
</app-dada>

咱们想把 2 个模板传进 dada 组件里, 其中一个仍是用于 ngFor 哦 (注意, $implicit 用来获取 ngFor 的 item)

此外咱们还经过 let-insideValue 来和 dada 组件沟通, 这个 insideValue 是由 dada 组件负责填入的. 

export class DadaComponent implements OnInit {

  constructor() { }
    @ContentChild('resultTemplate', { read: TemplateRef }) resultTemplateRef: TemplateRef<{ insideValue: string }>
  @ContentChild('optionTemplate', { read: TemplateRef }) optionTemplateRef: TemplateRef<NgForOfContext<string>>

  items = ['a', 'b', 'c'];

  ngOnInit() {

  }

}

达达组件定义好 2 个 templateRef, 咱们也可使用 @Input 传入 templateRef, 但这里咱们用的是 translude 的手法. 

dada.component.html

<ng-container *ngTemplateOutlet="resultTemplateRef; context: { insideValue : 'insideValueDone' }"></ng-container> 
<ng-template ngFor [ngForOf]="items" [ngForTemplate]="optionTemplateRef"></ng-template>

这样就能够了.

 

更新 

<img [sImage]="[image, scene]" >
若是 input/directive 的表达式是 array [], 那么 ng 会判断里面的每个值, 若是有一个不一样就会触发 onChange 若是没有则不会. 
 

更新 2017-06-30

组件是能够继承来用的. 不少时候 template, css 是一直变化的,可是 controller 逻辑却常常同样,因此就可使用继承来解决了.

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styles: [],
  changeDetection: ChangeDetectionStrategy.OnPush,
  // 若是父组件是 accessor 记得这里要写 provider 哦 
  providers: [{
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => TestComponent),
      multi: true
  }], 
})
export class TestComponent extends PaginationComponent implements OnInit {

  constructor(
    cdr: ChangeDetectorRef,
    validators: ValidatorsService
  ) {
    super(cdr, validators); // 父组件须要什么就注入什么
  }

  ngOnInit() {
    super.ngOnInit(); // 记得调用父组件哦 
    // do something else ... 
  }
}

使用 extends 就能够了, selector, css, template 都必须 override 

ngOnInit, OnDestroy 这些,若是子组件没有 override, 父组件依然会自动跑,若是你 override 了请本身决定要不要调用.

 

 

更新 2017-03-15

image src biding 404 注意事项

<img [src]="data" >

data = "" 就不会去加载图片( undefined, null 也是去加载哦 ), 其他状况下,模板一旦渲染就会立刻去加载. 

好的处理就是写个 ngIf 等待 async data 回来才渲染图片. 

 

 

更新 2017-03-03: 

ng 上写 keyCode 事件

<input 
    (keydown.arrowUp)="$event.preventDefault()"
    (keydown.shift.tab)="$event.preventDefault()"                            
    type="text"  />

看的出就是把 keyCode.key 写成驼峰式就能够监听到了. 配合 shift 也很容易写哦.

 

QueryList & transclude Content

@ContentChildren(OptionComponent) options: QueryList<OptionComponent>

在处理 ContentChildren 时要注意几件事. 若是咱们想监听它的 length 改变, 可使用 change.subscribe(...) 

若是你想监听 content 内 component 的事件,你要记得 rewatch,像我上面那样,由于 content 的数量是动态的,compoennt 会被建立和销毁, 一旦销毁了, 你的监听也被销毁,而新建立的你得再监听一次. 

若是你想在数量变化以后对 content 进行操做,你得写一个 timeout, 让它触发下一次的 digest 

 

最近本身在写 multiple select 有些发现 

ng 的 select option 的 2 个小逻辑 : 

<select [(ngModel)]="hero.skillId" name ="skillId">
    <option *ngFor ="let skill of skills" [ngValue]="skill.id">{{ skill.name}}</ option>
</select>

1.若是你修改 skills 的话, this.skills[0].value = "bla bla" 或 this.skills = new array ..

  ng 经过 doCheck 检测到你的修改, 而后作出更新. 若是当前的 model value 没法在新的 options 中匹配到的话, ng 不会作任何处理. 

2.即便你写 trackBy, 当你把 skill.value 换掉后, ng 没法经过 trackBy 找到对的 value 来更新. e.g your select is { Id :1,value :"v" }, then you change skills to [{ Id:1,value:"ttc" }] after detectchange    your model still "v".

3.因为是直接 muta skills, ng 只能经过 doCheck 来检查, 在 ChangeDetectionStrategy.OnPush 的状况下, ng 会直接坏掉哦. 解决方法就是手动触发 changeDetect. 参考 OnPush : http://www.cnblogs.com/keatkeat/p/5963295.html

 

 

更新 : 2017-02-11 

angular drag & drop 若是不要使用 plugin 的话, 能够用最基本的写法

能够参考原生 html 的例子 : http://www.w3schools.com/html/html5_draganddrop.asp

<form [formGroup]="form" submitableForm> 
  <s-upload #imagesUpload [config]="uploadConfig" formControlName="images"></s-upload> 
    <div *ngFor="let fileData of imagesUpload.fileDatas" 
      draggable="true" 
      (dragstart)="imagesUpload.dragingFileData = fileData"
      (dragend)="imagesUpload.dragingFileData = null" 
      (dragenter)="imagesUpload.dragingFileData && imagesUpload.moveFileData(fileData,imagesUpload.dragingFileData)" 
      [sDragover]="imagesUpload.dragingFileData"  
      class="uploadImage">
      <img [src]="fileData.file" width="100px">
      <i [show]="fileData.loading" class="fa fa-spin fa-spinner loading"></i>
      <i (click)="imagesUpload.removeFileData(fileData)" [show]="!fileData.loading" class="fa fa-times close"></i>
  </div>
</form>

dragingFileData 用于缓存变量

(dragend) 清楚缓存, 缓存还有一个重要用途就是若是你有 2 个 upload file 的时候不容许它们 cross drag

之因此不直接使用 (dragover) 是由于它会一次触发 digest (性能优化), sDragover 是一个指令里面手动添加了 event, 这样就不会一直 digest 了. 

 

 

更新 : 2017-01-26 

component selector 不须要担忧和将来游览器撞名字, ng 最终会胜利. 

directive 和 component input 不须要担忧冲突, 2 个能够一块儿工做, 都拿获得值.

 

 

2016-08-28

refer : 

https://angular.cn/docs/ts/latest/guide/template-syntax.html

https://angular.cn/docs/ts/latest/cookbook/component-communication.html

https://angular.cn/docs/ts/latest/guide/displaying-data.html

https://angular.cn/docs/ts/latest/guide/user-input.html

https://angular.cn/docs/ts/latest/guide/lifecycle-hooks.html

 

ng 的组件和游览器原生的组件是同一个概念,在方方面面都很类似. 

 

和 ng1 同样,组件少不了数据绑定 

1. model to view 绑定 

template: `
    <my-product [model-to-view-value]="'Derrick' + 'Yam'" ></my-product>
`

@Input("model-to-view-value") //若是属性名字不同的话能够调整
modelToViewValue: string;

 有几个点容易让人混淆

1. 左边不使用 [括弧] : <my-product value="abc" > 这样的写法右边的值 "abc" 只是一个很普通的 string, 它并非引用咱们 component instance 的属性哦. 也由于如此, 你以后就不可能改变它的值了. 算是 one time binding and only for string. (html 就是这样).

2.你可能会问若是这样呢 : <my-product value="{{ abc }}" > 这样的话右边的 "abc" 会引用 component instance, 并且会一直保持同步, 也就是说不是 one time binding 了. 但值依然只能是 string. abc must be string.

3. 左边是 [括弧] : <my-product [value]="abc"> 这样的话 abc 就能够传入任何类型了, 也会一直保持同步. 这也是咱们最经常使用的表达模式. 

 

2. 事件绑定 (监听嘛) 

和 html 同样,咱们内部只返回一个值, 外部经过关键字 $event 来获取 

selector: "my-app",
template: `
    <my-product (myClick)="doSomething($event)" ></my-product>
`
doSomething(data: string) { console.log(data) }


selector: "my-product",
template: `
    <div>this is product component</div>
    <div (click)="publish($event)" >publish</div>
`
@Output()
myClick: EventEmitter<string> = new EventEmitter();
publish($event) {
   //$event = html original click event
this.myClick.emit("value"); }

很惋惜 ng 的事件不支持冒泡 很惋惜 ng 的自定义事件不支持冒泡, 这里和 dom event 不一样,很差记 /.\ 

 

3.双向绑定 

selector: "my-app",
template: `      
    <p> value outside :  {{ someValue }}</p>
    <div (click)="updateValue()" >update value from outside</div>
    <my-product [(someValue)]="someValue" ></my-product>
`
someValue: string = "value start";
updateValue() {
    this.someValue = "value updated from outside";
}

selector:
"my-product", template: ` <div> value inside : {{ someValue }}</div> <div (click)="updateValue()" >update value from inside</div> ` @Input() someValue: string; @Output() someValueChange: EventEmitter<string> = new EventEmitter(); updateValue() { this.someValueChange.emit("value updated from inside"); }

其实ng是经过数据绑定加上事件绑定来完成这事儿的, [(value)] 只是语法糖

把它拆开是这样的 

<my-product [someValue]="someValue" (someValueChange)="someValue = $event" ></my-product>

若是要绑定原生 html 好比 input, select 等就是用 ng 帮咱们作好的 [(ngModel)] 就能够了, 以后我会和表单一块儿讲.

 

当咱们绑定到原生游览器组件时有些概念要清楚.

dom Attribute 和 dom Property 不是同一个东西

<img src="123.jpg" /> 写在 element 上的是 Attribute, 它的值只用来初始化 property 的值 

imgDomObject.src = "456.jpg" 这个才是 property.

ng 的绑定是在 property 上的. 

Attribute 和 Property 不老是一对一匹配的, 有些 Attribute 是没有 property 的,这时咱们要这样写 

<some-elem [attr.attributeName]="..." > 要在前面加 attr. 

 

没有 ng1 的 ng-options 了,如今是这样 : 

ng-repeat = *ngFor (*表示结构类型的指令)

<select [(ngModel)]="hero.skillId" name ="skillId">
    <option *ngFor ="let skill of skills" [ngValue]="skill.id">{{ skill.name}}</ option>
</select>

 

模板中若是有引用对象属性,可是对象是 null 时, 会报错哦. 可使用 ?. 来处理,这个和 c# 6.0 语法同样

<div>{{ someObj?.value }}</div> 

 

*ngFor 的值使用的是"微语法"解析, 它和咱们平时写模板表达式是不一样的哦.

*ngIf 用的也是 "微语法". 

track by 的值是一个方法 (必定要是一个方法)

track by 的做用 : 

ng 很聪明, 若是 heroes 是值类型的话, 它不会随便的 rebuild element, 好比 : heroes = ["a","b"], 而后 heroes = ["b","a"] 这样 ng 是不会 rebuild 的, 它只会更新 element.

可是若是是引用类型, heroes = [{ Id : 1 },{ Id : 2 }] 而后 [{Id : 2},{Id :1}] , ng 就会 rebuild element 了

为了要性能好一些, 咱们尽力不要 rebuild, 能够用 trackby, trackby return 的值决定了需不须要 rebuild.

trackby index 和 Id 的区别 : 

[{Id : 1}, {Id :2}] change to [{Id: 2}]

trackby index 的话 destroy 的是 Id 2  (由于 ng 是把 slot 1 的 Id update 去了 2, 而后 destroy 掉 2)

trackby Id 的话 destroy 的是 Id 1 

依据你的需求来决定怎样 trackby 哦.

 

template: ` 
    <div *ngFor="let hero of heroes; let i=index; trackBy:trackByHeroes" >
        <p>{{ hero.Id }}  {{ hero.name }}</p>
    </div>
`
trackByHeroes(index: number, hero: Hero) {        
    return hero.Id;
}

全部的 * 结构指令都只是语法糖, 下面这个才是真正的写法

<ng-template ngFor let-number [ngForOf]="[1,2,3]" let-i="index" [ngForTemplate]="templateRef" [ngForTrackBy]="trackByFn" >
    
</ng-template>

因此这 3 个是同样的: 

1.
<abc>
    <div *="let insideValue = insideValue;" >
        {{ outsideValue }} vs {{ insideValue }}          
    </div>
</abc>

2.
<abc>
    <div template="let insideValue = insideValue;" >
        {{ outsideValue }} vs {{ insideValue }}          
    </div>
</abc>

3.
<abc>
  <ng-template let-insideValue="insideValue" >
    <div>
        {{ outsideValue }} vs {{ insideValue }}          
    </div>
  </ng-template>  
</abc>


补上一个对应的

@Component({

  selector: 'abc',
  template : `
    <ng-template [ngTemplateOutlet]="templateRef" [ngOutletContext]="{ insideValue : insideValue }" ></ng-template>
  `
})

注意, 第 3 个的 let-inside 有一个 "-"

我推荐你们使用第 3 个, 虽然长一点,不过好理解,也不容易遇到坑 

好比 : 

<abc>
  <ng-template #firstContent let-insideValue="insideValue" >
    <div>
        {{ outsideValue }} vs {{ insideValue }}          
    </div>
  </ng-template>  
</abc>

 @ContentChild("firstContent") templateRef; // 是对的

<abc>
    <div #firstContent *="let insideValue = insideValue" >
        {{ outsideValue }} vs {{ insideValue }}          
    </div>
</abc>

 @ContentChild("firstContent") templateRef; // 是错的, 由于拿到的是 div 而不是 template 了.

 

ngForTemplate 容许咱们绑定 templateRef, 好比 transclude 的 viewContent 等等. 

<div *ngFor="let number of [1,2,9]" >        
    <template [ngTemplateOutlet]="templateRef" [ngOutletContext]="{ data : number }" ></template>
</div>

ngTemplateOutlet 和 ngOutletContext ( ng还在试验中 ) 一样容许咱们绑定 templateRef 并且能够注入 context 让 templateRef 经过 let-abc="data" 来调用到 number.

refer : http://stackoverflow.com/questions/37676593/how-to-repeat-a-piece-of-html-multiple-times-without-ngfor-and-without-anoher-c 

 

 

模板引用变量 ( template reference variables )

template: ` 
    <input type="text" #input >
    {{ someValue }}
    <div (click)="someValue = input.value" >click</div>
`

#input 表示那个组件( 自定义组件也能够哦 ), 可用范围很广, 父亲,兄弟, 孩子都用的到 

它还能够配合自定义指令的 exportAs 来使用, 相似 #variable="exporstAs"; 当一个 element 上有不少指令时, 咱们就能够选择要使用那个 class  

咱们能够很容易的把一个外部的模板传进去组件中,而且沟通. (下面还会介绍一个 transclude)

//outer component

<template #abc let-innerValue="data" >
    <div>{{ outerValue }}</div>
    <div>{{ innerValue }}</div>
</template>
<inner [template]="abc" ></inner>

//inner component 

<p>inner</p>
<template [ngTemplateOutlet]="template" [ngOutletContext]="{ data : 'innervValue' }" ></template>

 此外还能用它来获取 element 的 style 好比 height, width, scrollTop 等等  <div #div (click)="doSomething(div.scrollTop)">

  

管道 Pipe

import { Pipe, PipeTransform  } from "@angular/core";
//pure 的意思是 angular1 的 $stateful
@Pipe({ name: "demoCombine", pure : true })
export class DemoCombinePipe implements PipeTransform {  
    transform(value: string, param: string): string {
        return value + " " + param;
    }
}
template: `
    <div>
        {{ name | demoCombine : value }}
    </div>
`,
pipes: [DemoCombinePipe]

 

transclude 和 ng1 相似

template: `
    <demo-transclude-dir>
        <div class="first" >first space</div>
        <div class="second" >second space</div>
    </demo-transclude-dir>
`,

能够选择任何方式来表示不一样分区,我这里使用的是 class 

template: `
    <div>       
    <ng-content select=".first" ></ng-content> 
    <ng-content select=".second"></ng-content>
    </div>
`

内部经过 <ng-content> 配合 dom selector 来匹配区块 

transclude 一般会用到一个叫 viewProviders 的东西, 做用是隔离依赖注入, 好比咱们不但愿外部嵌套进来的 content 能依赖注入内部的 providers 就能够定义载 viewProviders. 

viewProviders 只有本身和 child 能够注入到, content 则注入不到.

 

这里懒得写了 

说说一些概念就好 

https://angular.cn/docs/ts/latest/guide/lifecycle-hooks.html#!#afterview (生命周期钩子)

主要是说 

ngAfterContentInit, ngAfterContentChecked 

ngAfterViewInit, ngAfterViewChecked 的使用 

Content 拦截指的是 transclude 的内容, 在子层作拦截能够获取 transclude 组件对象而后动一点手脚. 

afterView 则指的是子层完成后,在父层作拦截, 这里能够调用到子层组件对象作一点手脚, 不过记得要跑一个 timeout 否则不会渲染 (由于这个阶段渲染已经结束了)

下面是以前写的一些代码 :

demo-interception.component.ts 

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

import { DemoInterceptionParentComponent } from "./demo-interception-parent.component";
import { DemoInterceptionContentComponent } from "./demo-interception-content.component";


@Component({
    selector: "demo-interception", 
    //注意 demo-interception-content 须要定义一个模板引用和一个class 
    //还有 demo-interception-content 的 [value] 也可让 parent 去拦截作 binding, 不必定要在这里作 binding, 由于重要是 create content component 时要有就能够了.
    template: `
        <div>
           <demo-interception-parent [value]="'parent value'" >                
                <demo-interception-content #firstContentRef class="firstContent" [value]="'content value1'" ></demo-interception-content>  
           </demo-interception-parent>           
        </div>
    `,   
    directives: [
        DemoInterceptionParentComponent,
        DemoInterceptionContentComponent
    ]
})
export class DemoInterceptionComponent 
{   


}
View Code

demo-interception-content.component.ts

import { Component, Input } from "@angular/core";

import { FirstContentInterface } from "./demo-interception-parent.component";

@Component({
    selector: "demo-interception-content",
    template: `
        <div>{{ value }}</div>
    `
})
export class DemoInterceptionContentComponent implements FirstContentInterface {

    @Input() value: string;

}
View Code

demo-interception-parent.component.ts

import {
    Component,
    OnInit, OnDestroy, OnChanges, DoCheck, AfterContentChecked, AfterContentInit, ContentChild, ViewChild, AfterViewInit, AfterViewChecked, 
    Input, SimpleChange
} from "@angular/core";

import { DemoInterceptinChildComponent } from "./demo-interception-child.component"; 

export interface FirstContentInterface
{
    value: string;
}
@Component({
    selector: "demo-interception-parent",      
    template: `
        <div>   
            <ng-content select=".firstContent"></ng-content>
            <demo-interception-child [value]="'child value'" ></demo-interception-child>
        </div>
    `,
    directives: [
        DemoInterceptinChildComponent
    ]
})
export class DemoInterceptionParentComponent implements OnInit, OnDestroy, OnChanges, DoCheck, AfterContentChecked, AfterContentInit, AfterViewInit, AfterViewChecked {
    @Input() value: string;

    ngOnInit() {
        console.log("init");
    }
    ngOnDestroy() {
        console.log("destroy");
    }
    //note : 比如之前的 dirty check, 动不动就会跑的, 小心性能哦
    ngDoCheck() {
        console.log("doCheck");
    }
    //note : 当input属性发生变化的时候除非, 使用简单的 === 若是是对象的话不会作深沉对比哦
    ngOnChanges(changes: { [propertyName: string]: SimpleChange }) {
        console.log("onChange");
        //console.log(changes);
        for (let propName in changes) {
            let chng = changes[propName];
            let cur = JSON.stringify(chng.currentValue);
            let prev = JSON.stringify(chng.previousValue);
            //console.log(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
        }
    }

    //教程是使用 class 而非 Interface 的
    //e.g. : @ContentChild(ComponentClass) contentChild: ComponentClass;
    //可是呢,实际开发的时候, 内部组件不可能预测到外部将传进来什么组件
    //也不可能要求传进来的组件必须继承任何特定的类 
    //咱们顶多能要求他实现咱们的接口而已. 
    //"firstContentRef" 这个是经过模板引用来实现的,也算规范, 教程有说能够用模板引用
    @ContentChild("firstContentRef") contentChild: FirstContentInterface; 
    
    ngAfterContentChecked() {
        this.contentChild.value = "new content child value";
        console.log("afterContentChecked")
    }
    ngAfterContentInit() {
        console.log("afterContentInit")
    }


    //viewChild 拦截是在整个 view 作好以后才出发的
    //因此若是咱们想在目前这个 parent component 中对其作调整的话
    //咱们必须写一个 setTimeout 使它在 digest 一轮作更新
    @ViewChild(DemoInterceptinChildComponent) viewChild: DemoInterceptinChildComponent;
    ngAfterViewInit() {
        console.log('afterViewInit'); 
    }
    private record = 0;
    ngAfterViewChecked() {
        console.log(this.viewChild.value);
        //固然须要 if else 啦, 否则不是死循环吗.. 
        if (this.record == 0) {
            this.record = 1;
            setTimeout(() => {
                this.viewChild.value = "888";
            });
        }      
        console.log('afterViewChecked');   
    }
}
View Code

demo-interception-child.component.ts

import { Component, Input } from "@angular/core";

@Component({
    selector: "demo-interception-child",
    template : `<div>{{ value }}</div>`
})
export class DemoInterceptinChildComponent
{

    @Input() value: string;

}
View Code

 

组件通信的几个方式

@Input()

@Output()

service

@Input setter or ngOnChanges  ( 针对引用的话, 修改内部是不会触发的哦, 必定要连引用都换掉才行, immuatable 概念)

local variable (refer : https://angular.cn/docs/ts/latest/guide/template-syntax.html)

ViewChild, ViewContent

依赖注入 parent component ( 若是parent是抽象或则有循环引用的话要 refer : https://angular.cn/docs/ts/latest/cookbook/dependency-injection.html )

 

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"。。

相关文章
相关标签/搜索