笑对丶人生 發表於 2020-11-25 17:37:00

Angular(2)

<h2 id="1-templateref">1. TemplateRef</h2>
<h3 id="就是-template-的类型">就是 Template 的类型</h3>
<p>https://segmentfault.com/a/1190000008672478</p>
<pre><code class="language-ts">
&lt;h1&gt;hello world&lt;/h1&gt;
&lt;div class="box" #box&gt;box&lt;/div&gt;
&lt;ng-template #tpl&gt;
    &lt;span&gt;hello&lt;/span&gt;
&lt;/ng-template&gt;

import { AfterViewInit, Component, ElementRef, OnInit, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit,AfterViewInit{
@ViewChild("tpl",{read:TemplateRef}) readonly tpl:TemplateRef&lt;any&gt;
@ViewChild("box") readonly box:ElementRef
constructor(
) {
    //console.log("constructor",this.tpl)
}
ngOnInit(): void {
    //console.log("ngOnInit",this.tpl)
}
ngAfterViewInit(): void {
    const viewRef = this.tpl.createEmbeddedView(null)
    console.log("tpl",this.tpl)
    console.log("box",this.box)
    console.log("ngAfterViewInit",this.tpl.createEmbeddedView(null))
    this.box.nativeElement.appendChild(viewRef.rootNodes)
}

}

</code></pre>
<p>我们发现 @Component template 中定义的 &lt;template&gt; 模板元素,渲染后被替换成 comment 元素(comment是注释节点),其内容为 "template bindings={}" 。此外我们通过 @ViewChild 获取的模板元素,是 TemplateRef_ 类的实例,接下来我们来研究一下 TemplateRef类:</p>
<h2 id="2-embeddedviewref">2. EmbeddedViewRef</h2>
<h3 id="embeddedview-的类型">EmbeddedView 的类型</h3>
<pre><code class="language-js">
import { AfterViewInit, Component, ElementRef, OnInit, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit,AfterViewInit{
@ViewChild("tpl",{read:TemplateRef}) readonly tpl:TemplateRef&lt;any&gt;
@ViewChild("box") readonly box:ElementRef
@ViewChild("first",{read:ViewContainerRef}) readonly first:ViewContainerRef
constructor(
) {
    //console.log("constructor",this.tpl)
}
ngOnInit(): void {
    //console.log("ngOnInit",this.tpl)
}
ngAfterViewInit(): void {
    const viewRef = this.tpl.createEmbeddedView(null)
    console.log("tpl",this.tpl)
    console.log("box",this.box)
    console.log("first",this.first)
    //console.log("ngAfterViewInit",this.tpl.createEmbeddedView(null))
    // this.box.nativeElement.appendChild(viewRef.rootNodes)
    this.first.createEmbeddedView(this.tpl)
}

}

</code></pre>
<h2 id="3-viewcontainerref">3. ViewContainerRef</h2>
<h3 id="就是container-视图容器-的类型">就是Container (视图容器) 的类型</h3>
<pre><code class="language-ts">import {AfterViewInit, Component, EmbeddedViewRef, OnInit, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';

@Component({
selector: 'app-tpl-container',
templateUrl: './tpl-container.component.html'
})
export class TplContainerComponent implements OnInit, AfterViewInit {
// 获取模板中的元素(组件、ng-template、dom)
@ViewChild('box') readonly boxEl: ElementRef;
@ViewChild('firstTpl', { read: TemplateRef }) readonly firstTpl: TemplateRef&lt;any&gt;;
@ViewChild('secondTpl', { read: TemplateRef }) readonly secondTpl: TemplateRef&lt;any&gt;;
@ViewChild('thirdTpl', { read: TemplateRef }) readonly thirdTpl: TemplateRef&lt;any&gt;;
@ViewChild('fourthTpl', { read: TemplateRef }) readonly fourTpl: TemplateRef&lt;any&gt;;
@ViewChild('freeTpl', { read: TemplateRef }) readonly freeTpl: TemplateRef&lt;any&gt;;
@ViewChild('firstContainer', { read: ViewContainerRef, static: true }) readonly firstContain: ViewContainerRef;
@ViewChild('secondContainer', { read: ViewContainerRef, static: true }) readonly secondContain: ViewContainerRef;
private freeViewRef: EmbeddedViewRef&lt;any&gt;;
constructor() {
    // console.log('constructor');
}

insert(tpl: TemplateRef&lt;any&gt;) {
    this.firstContain.insert(tpl.createEmbeddedView(null));
}

insertAll() {
    .forEach(tpl =&gt; {
      this.firstContain.insert(tpl.createEmbeddedView(null));
    });
}

getOne() {
    console.log(this.firstContain.get(2));
    console.log(this.firstContain.indexOf(this.freeViewRef));
}

insertFree() {
    this.firstContain.insert(this.freeViewRef, 1);
}

move() {
    // 不需要事先插入也可以移动(定好位置再插入)
    this.firstContain.move(this.freeViewRef, 2);
}

move2To4() {
    const view = this.firstContain.detach(1);
    this.firstContain.insert(view, 3);
}

move2ToOther() {
    const view = this.firstContain.detach(1);
    this.secondContain.insert(view);
}

ngOnInit(): void {
    // console.log('onInit');
}

ngAfterViewInit(): void {
    console.log('ngAfterViewInit');
    this.freeViewRef = this.freeTpl.createEmbeddedView({ $implicit: 'defaultValue', free: 'aa' });
    // console.log(this.firstTpl);
    // const viewRef = this.firstTpl.createEmbeddedView(null);
    // console.log('viewRef', viewRef);
    /*this.boxEl.nativeElement.appendChild(viewRef.rootNodes);*/
    setTimeout(() =&gt; {
      this.firstContain.createEmbeddedView(this.firstTpl);
    }, 0);
}

}
</code></pre>
<pre><code class="language-html">&lt;div class="box" #box&gt;
&lt;button class="btn btn-primary mr-1" (click)="insert(secondTpl)"&gt;insert second&lt;/button&gt;
&lt;button class="btn btn-primary mr-1" (click)="insert(thirdTpl)"&gt;insert third&lt;/button&gt;
&lt;button class="btn btn-primary mr-1" (click)="insert(fourthTpl)"&gt;insert fourth&lt;/button&gt;
&lt;button class="btn btn-primary mr-1" (click)="insertAll()"&gt;insert all&lt;/button&gt;
&lt;button class="btn btn-secondary mr-1" (click)="insertFree()"&gt;insert free&lt;/button&gt;
&lt;button class="btn btn-info mr-1" (click)="getOne()"&gt;get one&lt;/button&gt;
&lt;button class="btn btn-success mr-1" (click)="move()"&gt;move free&lt;/button&gt;
&lt;button class="btn btn-success mr-1" (click)="move2To4()"&gt;把第二个移动到第四个位置上&lt;/button&gt;
&lt;button class="btn btn-success" (click)="move2ToOther()"&gt;把第二个移动到其他容器中&lt;/button&gt;
&lt;p&gt;长度:{{ firstContain?.length }}&lt;/p&gt;
&lt;/div&gt;

&lt;ng-template #firstTpl&gt;
&lt;p&gt;first tpl content&lt;/p&gt;
&lt;/ng-template&gt;

&lt;ng-template #secondTpl&gt;
&lt;p&gt;第二段template&lt;/p&gt;
&lt;/ng-template&gt;

&lt;ng-template #thirdTpl&gt;
&lt;p&gt;第三段template&lt;/p&gt;
&lt;/ng-template&gt;

&lt;ng-template #fourthTpl&gt;
&lt;p&gt;第四段template&lt;/p&gt;
&lt;/ng-template&gt;

&lt;ng-template #freeTpl&gt;
&lt;p&gt;自由的template&lt;/p&gt;
&lt;/ng-template&gt;

&lt;p&gt;first container:
    &lt;ng-container #firstContainer&gt;&lt;/ng-container&gt;
&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt; second container:&lt;ng-container #secondContainer&gt;&lt;/ng-container&gt;
&lt;/p&gt;
</code></pre>
<h2 id="4-ngtemplateoutlet指令">4. NgTemplateOutlet指令</h2>
<p>父组件调用TplOutletComponent传入自定义的dom</p>
<pre><code class="language-ts">import { Component, Input } from '@angular/core';
@Component({
selector: 'app-root',
template: `
       &lt;app-tpl-outlet ="customTpl"&gt;&lt;/app-tpl-outlet&gt;

       &lt;ng-template #customTpl let-def let-val="value"&gt;
         &lt;b&gt;{{def}} ----- {{val}}&lt;/b&gt;
       &lt;/ng-template&gt;
          `
})
export class ItemDetailComponent { }
</code></pre>
<pre><code class="language-html">import { Component, Input, TemplateRef } from '@angular/core';
@Component({
selector: 'app-tpl-outlet',
template: `
&lt;div&gt;
   &lt;ng-container="render || defaultTpl"         ="myContext"&gt;&lt;/ng-container&gt;
   &lt;ng-container *ngTemplateOutlet="render || defaultTpl; context: myContext"&gt;&lt;/ng-container&gt;
                  
   &lt;!-- 用在ng-template上也可以   --&gt;
   &lt;ng-template ="render || defaultTpl" ="myContext"&gt;&lt;/ng-template&gt;
   &lt;ng-template *ngTemplateOutlet="render || defaultTpl; context: myContext"&gt;&lt;/ng-template&gt;
      
&lt;/div&gt;

&lt;ng-template #defaultTpl&gt;
   &lt;b&gt;这是默认的内容&lt;/b&gt;
&lt;/ng-template&gt;
`
})
export class TplOutletComponent{
@Input () render: TemplateRef&lt;any&gt;;
myContext = {$implicit: 'World', value: 'Svet'};
}
</code></pre>
<pre><code class="language-js">import { Component, Input, TemplateRef } from '@angular/core';
@Component({
selector: 'app-tpl-outlet',
template: `
        &lt;div&gt;
                   &lt;ng-container ="render || defaultTpl"&gt;&lt;/ng-container&gt;
   
                &lt;ng-template #defaultTpl&gt;
                       &lt;b&gt;这是默认的内容&lt;/b&gt;
                &lt;/ng-template&gt;
        &lt;/div&gt;
`
})

export class TplOutletComponent{
@Input () render: TemplateRef&lt;any&gt;;
}
</code></pre>
<pre><code class="language-js">import { Component, Input } from '@angular/core';
@Component({
selector: 'app-root',
template: `
       &lt;app-tpl-outlet ="customTpl"&gt;&lt;/app-tpl-outlet&gt;

       &lt;ng-template #customTpl&gt;
         &lt;p&gt;这是自定义的内容&lt;/p&gt;
       &lt;/ng-template&gt;
   `
})
export class ItemDetailComponent { }
</code></pre>
<h2 id="5-组件投影插槽">5. 组件投影(插槽)</h2>
<h3 id="具名插槽">具名插槽</h3>
<p>子</p>
<pre><code class="language-html">&lt;div class="shadow"&gt;
    &lt;h3&gt;hello shadow&lt;/h3&gt;

    &lt;div class="head bg-primary"&gt;
            我是头
         &lt;ng-content select=".head"&gt;&lt;/ng-content&gt;
    &lt;/div&gt;

    &lt;div class="body"&gt;
            我是身体
      &lt;ng-content select=".body"&gt;&lt;/ng-content&gt;
    &lt;/div&gt;

    &lt;div class="foot bg-danger"&gt;
            我是尾巴
            &lt;ng-content select=".foot"&gt;&lt;/ng-content&gt;
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>父</p>
<pre><code class="language-html">&lt;app-structural&gt;
    &lt;div class="head"&gt;
      头部content
    &lt;/div&gt;
    &lt;div class="body"&gt;
      默认主体内容content
    &lt;/div&gt;
    &lt;div class="foot"&gt;
      尾部content
    &lt;/div&gt;
&lt;/app-structural&gt;
</code></pre>
<hr>
<p>子</p>
<pre><code class="language-html">import { Component, Input, TemplateRef } from '@angular/core';
@Component({
selector: 'app-shadow',
template: `
            &lt;div class="shadow"&gt;
            &lt;div class="head"&gt;
                &lt;ng-content select=".head"&gt;&lt;/ng-content&gt;   //具名插槽
            &lt;/div&gt;
            &lt;div class="body"&gt;
                &lt;ng-content select=""&gt;&lt;/ng-content&gt;
                &lt;ng-content select="article"&gt;&lt;/ng-content&gt;//
                &lt;ng-content&gt;&lt;/ng-content&gt;//普通插槽
            &lt;/div&gt;
            &lt;div class="foot"&gt;
                &lt;ng-content select=".foot"&gt;&lt;/ng-content&gt;   //具名插槽
            &lt;/div&gt;
            &lt;/div&gt;`
})ts
export class ShadowComponent{}
</code></pre>
<p>父</p>
<p>调用ShadowComponent:</p>
<ul>
<li>select=".head"</li>
<li>select="article" 元素选择器</li>
<li>select=""</li>
<li>直接<code>&lt;ng-content&gt;&lt;/ng-content&gt;</code></li>
</ul>
<pre><code class="language-html">import { Component, Input } from '@angular/core';
@Component({
selector: 'app-root',
template: `
            &lt;app-shadow ="true"&gt;
            &lt;div class="head"&gt;这是head的投影&lt;/div&gt;
               
            &lt;div attr&gt;这是attr的投影内容&lt;/div&gt;
            &lt;article&gt;这是article的投影内容&lt;/article&gt;
               
            &lt;b style="color: #007bff"&gt;这是默认的投影内容&lt;/b&gt;
            &lt;div class="foot"&gt;这是foot的投影&lt;/div&gt;
            &lt;/app-shadow&gt;
          `
})
export class AppComponent{}
</code></pre>
<h2 id="6-viewchild">6. ViewChild</h2>
<p><strong>最好在ngAfterViewInit之后,获取模版上的内容</strong></p>
<p><strong>元数据属性</strong>:</p>
<ul>
<li><strong>selector</strong> - 用于查询的指令类型或名字。</li>
<li><strong>read</strong> - 从查询到的元素中读取另一个令牌。</li>
<li><strong>static</strong> - 在变更检测之前解析查询结果为true,在变更检测之后解析为false。默认为false。</li>
</ul>
<h4 id="获取普通元素dom">获取普通元素dom</h4>
<pre><code class="language-ts">import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';

@Component({
selector: 'app-view-child',
template: `
      &lt;section&gt;
      &lt;h3&gt;获取dom&lt;/h3&gt;
      &lt;div class="box" #box&gt;
         &lt;p&gt;box&lt;/p&gt;
      &lt;/div&gt;
      &lt;/section&gt;
`,
styles: []
})
export class ViewChildComponent implements OnInit, AfterViewInit {
@ViewChild('box') private boxEl: ElementRef;
constructor() {
    // TypeError: Cannot read property 'nativeElement' of undefined
    console.log('0', this.boxEl.nativeElement);
}

ngOnInit(): void {
    // TypeError: Cannot read property 'nativeElement' of undefined
    console.log('1', this.boxEl.nativeElement);
}
   
ngAfterViewInit(): void {
    console.log('2', this.boxEl.nativeElement); // 正确
}
}
</code></pre>
<p>上面例子中的boxEl,默认在变更检测之后才会获取到元素,而ngAfterViewInit就是在变更检测之后才会调研</p>
<h4 id="static属性">static属性</h4>
<blockquote>
<p>默认在变更检测之后才会获取到目标元素,可开启static,这样组件初始化的时候,变更检测前就能获取到目标</p>
</blockquote>
<pre><code class="language-ts">export class ViewChildComponent implements OnInit, AfterViewInit {
@ViewChild('box', { static: true }) private boxEl: ElementRef;
constructor() {
    // TypeError: Cannot read property 'nativeElement' of undefined
    console.log('0', this.boxEl.nativeElement);
}

ngOnInit(): void {
    console.log('1', this.boxEl.nativeElement); // 正确
}
ngAfterViewInit(): void {
    console.log('2', this.boxEl.nativeElement); // 正确
}
}
</code></pre>
<p><strong>可以看到在constructor里是拿不到模板元素的,建议如果目标从一开始就显示在模版上</strong><br>
<strong>即没有被ngIf等指令操控,就开启static</strong></p>
<h4 id="获取子组件指令">获取子组件(指令)</h4>
<blockquote>
<p>以组件为例,获取到组件实例后可以访问子组件到属性和方法,指令用法和组件一摸一样</p>
</blockquote>
<pre><code class="language-ts">import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
@Component({
selector: 'app-view-child-panel',
templateUrl: './view-child-panel.component.html'
})
export class ViewChildPanelComponent implements OnInit {
readonly name = 'panel';
constructor() { }
ngOnInit(): void {}
}

//======================================================================//

@Component({
selector: 'app-view-child',
template: `
      &lt;section&gt;
      &lt;h3&gt;获取子组件&lt;/h3&gt;
      &lt;app-view-child-panel&gt;&lt;/app-view-child-panel&gt;
      &lt;/section&gt;
`,
styles: []
})
export class ViewChildComponent implements OnInit, AfterViewInit {
@ViewChild(ViewChildPanelComponent, { static: true }) private panel: ViewChildPanelComponent;
   
    constructor() {}
    ngOnInit(): void {}
    ngAfterViewInit(): void {
      // console.log(2, this.boxEl.nativeElement);
      console.log(this.panel.name);
    }
}
</code></pre>
<h4 id="获取子组件指令-写法2">获取子组件(指令) 写法2</h4>
<blockquote>
<p>也可以通过模版引用变量获取子组件</p>
</blockquote>
<pre><code class="language-ts">import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';

@Component({
selector: 'app-view-child',
template: `
      &lt;section&gt;
      &lt;h3&gt;获取子组件&lt;/h3&gt;
      &lt;app-view-child-panel #myPanel&gt;&lt;/app-view-child-panel&gt;
      &lt;/section&gt;
`,
styles: []
})
export class ViewChildComponent implements OnInit, AfterViewInit {
@ViewChild('myPanel', { read: ViewChildPanelComponent, static: true }) private panel: ViewChildPanelComponent;
    constructor() {}
    ngOnInit(): void {}
    ngAfterViewInit(): void {
      // console.log(2, this.boxEl.nativeElement);
      console.log(this.panel.name);
    }
}
</code></pre>
<h4 id="获取宿主app-panel">获取宿主<code>&lt;app-panel&gt;</code></h4>
<pre><code class="language-js">import { Component, ElementRef, OnInit } from '@angular/core';

@Component({
selector: 'app-panel',
templateUrl: './panel.component.html',
styleUrls: ['./panel.component.scss']
})
export class PanelComponent implements OnInit {
readonly name = "张三"
constructor(readonly el: ElementRef) { }
ngOnInit(): void {
}
}

@Component({
selector: 'app-view-child',
templateUrl: './view-child.component.html',
styles: [
]
})
export class ViewChildComponent implements OnInit,AfterViewInit {
@ViewChild('panel',{static:true}) private panelInstance:PanelComponent;
constructor() { }
ngOnInit(): void {}
ngAfterViewInit(): void {
    // console.log(this.panelInstance.name)
    // console.log(this.panelInstance)
    console.log(this.panelInstance.el.nativeElement)
}
}
</code></pre>
<h4 id="获取template">获取template</h4>
<p><strong>上节(TemplateRef和ViewContainerRef)已经演示过了</strong></p>
<h2 id="7-viewchildren">7. ViewChildren</h2>
<blockquote>
<p>与ViewChild类似,它可以批量获取模板上相同选择器的元素并存放到QueryList类中</p>
<p>ViewChildren没有static属性</p>
</blockquote>
<h3 id="批量获取子组件和dom元素">批量获取子组件和dom元素</h3>
<pre><code class="language-js">import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';

@Component({
selector: 'app-view-child',
template: `
      &lt;section&gt;
      &lt;h3&gt;获取dom&lt;/h3&gt;
      &lt;div class="box" #box&gt;
          &lt;p&gt;box&lt;/p&gt;
      &lt;/div&gt;
                &lt;div class="box" #box&gt;
          &lt;p&gt;box&lt;/p&gt;
      &lt;/div&gt;
      &lt;/section&gt;
      
      &lt;section #box&gt;
      &lt;h3&gt;获取子组件&lt;/h3&gt;
      &lt;app-view-child-panel #myPanel&gt;&lt;/app-view-child-panel&gt;
      &lt;app-view-child-panel #myPanel&gt;&lt;/app-view-child-panel&gt;
      &lt;app-view-child-panel #myPanel&gt;&lt;/app-view-child-panel&gt;
      &lt;/section&gt;
`,
styles: []
})
export class ViewChildComponent implements OnInit, AfterViewInit {
@ViewChild('box', { static: true }) private boxEl: ElementRef;
@ViewChildren('box') private boxEls: QueryList&lt;ElementRef&gt;;

@ViewChild(ViewChildPanelComponent, { static: true }) private panel: ViewChildPanelComponent;
@ViewChildren(ViewChildPanelComponent) private panels: QueryList&lt;ViewChildPanelComponent&gt;;

constructor() {}
    ngOnInit(): void {}
    ngAfterViewInit(): void {
      console.log(this.panels);
      console.log(this.boxEls);
    }
}
</code></pre>
<h3 id="querylist">QueryList</h3>
<blockquote>
<p>模板元素集合,详细用法参考文档</p>
</blockquote>
<pre><code class="language-js">import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';

@Component({
selector: 'app-view-child',
template: `   
      &lt;section&gt;
      &lt;h3&gt;获取子组件&lt;/h3&gt;
      &lt;button class="btn btn-primary" (click)="showMidPanel = !showMidPanel"&gt;toggle mid&lt;/button&gt;
      &lt;app-view-child-panel #myPanel&gt;&lt;/app-view-child-panel&gt;
      &lt;app-view-child-panel #myPanel *ngIf="showMidPanel"&gt;&lt;/app-view-child-panel&gt;
      &lt;app-view-child-panel #myPanel&gt;&lt;/app-view-child-panel&gt;
      &lt;/section&gt;
`,
styles: [ ]
})
export class ViewChildComponent implements OnInit, AfterViewInit {
   
@ViewChildren(ViewChildPanelComponent) private panels: QueryList&lt;ViewChildPanelComponent&gt;;
constructor() {}
    ngOnInit(): void {}
    ngAfterViewInit(): void {
      this.panels.changes.subscribe(changes =&gt; {
      console.log('changes', changes);
      });
    }
}
</code></pre>
<h2 id="8-contentchild">8. ContentChild</h2>
<p><strong>用法类似ViewChild, 获取投影中的组件或指令还有元素dom等</strong></p>
<h3 id="获取投影中的组件">获取投影中的组件</h3>
<p>父</p>
<pre><code class="language-ts">import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';

@Component({
selector: 'app-content-child-panel',
template: `
           &lt;app-content-child&gt;
                        &lt;div class="head"&gt;
                          这是头部
                        &lt;/div&gt;
                        &lt;app-content-child-panel&gt;&lt;/app-content-child-panel&gt;
                        &lt;ul #list&gt;
                            &lt;li&gt;aaa&lt;/li&gt;
                             &lt;li&gt;bbb&lt;/li&gt;
                        &lt;/ul&gt;
                &lt;/app-content-child&gt;
`,
styles: []
})
export class ContentChildPanelComponent implements OnInit {
constructor() { }
ngOnInit(): void {}

alert() {
    alert('aa');
}
}
</code></pre>
<p>子</p>
<pre><code class="language-ts">@Component({
selector: 'app-content-child',
template: `
      &lt;div class="content-child-box"&gt;
      &lt;h2&gt;这是content child组件&lt;/h2&gt;

      &lt;div class="head" style="border: 1px solid; margin: 10px 0;"&gt;
            &lt;ng-content select=".head"&gt;&lt;/ng-content&gt;
      &lt;/div&gt;

      &lt;ng-content&gt;&lt;/ng-content&gt;

      &lt;/div&gt;
`,
styles: []
})
export class ContentChildComponent implements AfterViewInit {
// 无法获取dom元素
// @ContentChild('.head', { static: true }) private headEl: ElementRef;
// @ContentChild('list', { static: true }) private listEl: ElementRef;
@ContentChild(ContentChildPanelComponent, { static: true }) private panel: ContentChildPanelComponent;
constructor() { }

ngAfterViewInit(): void {
    this.panel.alert();
}
}
</code></pre>
<h2 id="9-contentchildren">9. ContentChildren</h2>
<p><strong>用法类似ViewChildren, 批量获取投影中到组件或指令</strong></p>
<pre><code class="language-js">&lt;app-content-child&gt;
&lt;div class="head"&gt;
    这是头部
&lt;app-content-child-panel&gt;&lt;/app-content-child-panel&gt;
&lt;/div&gt;
&lt;app-content-child-panel&gt;&lt;/app-content-child-panel&gt;
&lt;app-content-child-panel&gt;&lt;/app-content-child-panel&gt;
&lt;ul #list&gt;
    &lt;li&gt;aaa&lt;/li&gt;
    &lt;li&gt;bbb&lt;/li&gt;
&lt;/ul&gt;
&lt;/app-content-child&gt;

export class ContentChildComponent implements AfterViewInit {
@ContentChildren(ContentChildPanelComponent) private panels: QueryList&lt;ContentChildPanelComponent&gt;;
constructor() { }

ngAfterViewInit(): void {
    console.log(this.panels); // 只有两个结果
}
}
</code></pre>
<h3 id="descendants属性">descendants属性</h3>
<blockquote>
<p>这是ContentChildren特有的属性,上个例子少拿类一个panel组件,原因是默认只寻找直属子panel 而.head里但panel组件,并非直属,所以拿不到,想要寻找到所有层级的panel组件,就开启descendants</p>
</blockquote>
<p><strong>@ContentChildren(ContentChildPanelComponent, { descendants: true }) private panels: QueryList;</strong></p>
<h2 id="10-管道">10. 管道</h2>
<h3 id="内置管道">内置管道</h3>
<p><strong>之前讲模板表达式时,已经用过管道了几个内置管道,下面是常用的内置管道:</strong></p>
<ul>
<li>AsyncPipe -- 自动订阅模板中的Observable或Promise</li>
<li>DatePipe -- 格式化日期</li>
<li>DecimalPipe -- 数字转字符串,并可以指定格式</li>
<li>KeyValuePipe -- 使ngFor可以循环Object或Map对象</li>
<li>JsonPipe -- 将值转成json</li>
<li>TitleCasePipe -- 把首字母大写,其它小写</li>
<li>SlicePipe -- 截取Array或String</li>
<li>PercentPipe -- 数字转百分比</li>
<li>LowerCasePipe和UpperCasePipe -- 转化小写或大写</li>
</ul>
<h3 id="自定义管道">自定义管道</h3>
<p>在Angular中可以自定义带参数的管道;只要遵循下面三条规则:</p>
<ol>
<li>
<p>利用@pipe装饰器声明管道的名字</p>
</li>
<li>
<p>实现PipeTransform接口</p>
</li>
<li>
<p>如果是全局使用,则 include your pipe in the <code>declarations</code> array of the AppModule;</p>
<p>如果想要局部使用, 则 provide it in the providers array of your NgModule.</p>
</li>
</ol>
<pre><code class="language-js">ng g pipe xxx
</code></pre>
<blockquote>
<p>下面定义一个将数字指数化的管道</p>
</blockquote>
<pre><code class="language-ts">import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
name: 'exponentialStrength'
})
export class ExponentialStrengthPipe implements PipeTransform {
transform(value: number, exponent?: number): number {
    return Math.pow(value, isNaN(exponent) ? 1 : exponent);
}
}
</code></pre>
<p>调用:</p>
<pre><code class="language-ts">import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
   &lt;h2&gt;Power Boost Calculator&lt;/h2&gt;
   &lt;div&gt;Normal power: &lt;input [(ngModel)]="power"&gt;&lt;/div&gt;
   &lt;div&gt;Boost factor: &lt;input [(ngModel)]="factor"&gt;&lt;/div&gt;
   &lt;!--&lt;p&gt;Super power boost: {{ 2 | exponentialStrength }}&lt;/p&gt;--&gt;
   &lt;p&gt;Super power boost: {{ power | exponentialStrength: factor }}&lt;/p&gt;
`,
})
export class AppComponent {
   power = 5;
   factor = 1;
}
</code></pre>
<h3 id="非纯管道">非纯管道</h3>
<p>https://www.cnblogs.com/juliazhang/p/11228988.html</p>
<blockquote>
<p>默认的管道都是纯的,Angular 会忽略复合对象中的变化,即管道只会检查原始值或对象引用</p>
</blockquote>
<p>可如果数组中的元素变化,增删改,由于引用没有变化,所以不会执行管道的逻辑</p>
<pre><code class="language-js">@Pipe({
name: 'flyingHeroesImpure',
pure: false
})
</code></pre>
<pre><code class="language-js">import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {ExponentialStrengthPipe} from './exponential-strength.pipe';
import {FlyingHeroesImpurePipe} from './flying-heroes-impure.pipe';

@NgModule({
declarations: [
    ExponentialStrengthPipe,
    FlyingHeroesImpurePipe
],
imports: [
    CommonModule
],
exports: [
    ExponentialStrengthPipe,
    FlyingHeroesImpurePipe
]
})
export class PipeModule { }
</code></pre>
<pre><code class="language-js">import { Pipe, PipeTransform } from '@angular/core';
import {Hero} from '../types';

@Pipe({
name: 'flyingHeroesImpure',
pure: false
})
export class FlyingHeroesImpurePipe implements PipeTransform {
transform(allHeroes: Hero[]) {
    console.log('transform');
    return allHeroes.filter(hero =&gt; hero.canFly);
}
}
</code></pre>
<p>code</p>
<pre><code class="language-ts">import { Component } from '@angular/core';
interface Hero {
id: string;
name: string;
canFly?: boolean;
}

const HEROES = [
            {
                id: 'hero_0',
                name: '盖伦',
                canFly: false
            },
            {
                id: 'hero_1',
                name: '赵信',
                canFly: false
            },
            {
                id: 'hero_2',
                name: '嘉文',
                canFly: false
            },
            {
                id: 'hero_3',
                name: '易大师',
                canFly: false
            },
            {
                id: 'hero_3',
                name: '泰达米尔',
                canFly: true
            }
          ];
@Component({
selector: 'app-root',
template: `
   &lt;input type="text" #box (keyup.enter)="addHero(box.value)" placeholder="hero name" /&gt;
   &lt;button (click)="reset()"&gt;Reset&lt;/button&gt;
   &lt;div *ngFor="let hero of (heroes | flyingHeroes)"&gt;
       {{hero.name}}
   &lt;/div&gt;
`,
})
export class AppComponent {
   heroes: Hero[] = [];
   canFly = true;
   constructor() { this.reset(); }
   
   ngOnInit(): void { }
   addHero(name: string) {
       name = name.trim();
       if (name) {
         // 不改变引用没有用
         this.heroes.push({ id: 'flier_' + Date.now(), name, canFly: this.canFly });
       }
   }
   
   reset() { this.heroes = HEROES.slice(); }
}
</code></pre>
<p>上面往数组里push值,由于数组引用不变,管道逻辑不会再次执行,两个方法:</p>
<ol>
<li>改变引用</li>
</ol>
<pre><code>this.heroes = [
   ...this.heroes,
   { id: 'flier_' + Date.now(), name, canFly: this.canFly }
];
</code></pre>
<ol start="2">
<li>将管道标记为非纯的</li>
</ol>
<pre><code>@Pipe({
name: 'flyingHeroes',
pure: false
})
</code></pre>
<h2 id="11-生命周期">11. 生命周期</h2>
<p><strong>从实例化组件,渲染组件模板时,各声明周期就已开始</strong></p>
<h3 id="ngonchanges">ngOnChanges</h3>
<blockquote>
<p>输入属性发生变化是触发,但组件内部改变输入属性是不会触发的</p>
</blockquote>
<pre><code class="language-ts">import { Component, Input, OnInit, OnChanges } from '@angular/core';

@Component({
selector: 'app-life-cycle',
templateUrl: `
    点击按钮不会触发ngOnChanges
    &lt;button (click)="title = 'self title'"&gt;set title self&lt;/button&gt;
    &lt;p&gt;title: {{ title }}&lt;/p&gt;
`
})
export class LifeCycleComponent implements OnInit, OnChanges {
@Input() title: string;
constructor() {
    console.log('constructor', this.title); // undefined
    console.log('constructor', this.content); // 一段content
}
ngOnChanges(changes: SimpleChanges): void {
    console.log('changes', changes);
}

ngOnInit(): void {
    console.log('ngOnInit', this.title);
}
}
</code></pre>
<h3 id="ngoninit">ngOnInit</h3>
<blockquote>
<p>只在组件/指令初始化调用一次,在它之前调用的顺序是 constructor -&gt; ngOnChanges -&gt; ngOnInit 官方建议在这个钩子中获取组件初始化的数据,而构造函数中只适合写简单的逻辑,比如初始化局部变量</p>
</blockquote>
<p>constructor在设置输入属性前调用,也就是说,在constructor里拿不到未初始化的输入属性</p>
<pre><code class="language-ts">import { Component, Input, OnInit } from '@angular/core';
export class LifeCycleComponent implements OnInit {
@Input() title: string;
@Input() content = '一段content';
constructor() {
    console.log('constructor', this.title); // undefined
    console.log('constructor', this.content); // 一段content
}

ngOnInit(): void {
    console.log('ngOnInit', this.title);
}
}
</code></pre>
<h3 id="ngondestroy">ngOnDestroy</h3>
<blockquote>
<p>组件销毁时触发一次,在这里应该清理一些残存的状态(事件、定时器等)</p>
</blockquote>
<pre><code class="language-html">&lt;button class="btn btn-primary" (click)="show = !show"&gt;toggle&lt;/button&gt;
&lt;app-life-cycle title="aaa" *ngIf="show"&gt;&lt;/app-life-cycle&gt;
</code></pre>
<h3 id="所有钩子的触发顺序">所有钩子的触发顺序</h3>
<pre><code class="language-ts">export class LifeCycleComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy {
@Input() title: string;
constructor() {
    console.log('constructor', this.title);
}

ngOnChanges(changes: SimpleChanges): void {
    console.log('ngOnChanges');
}

ngOnInit(): void {
    console.log('ngOnInit', this.title);
}

ngDoCheck(): void {
    console.log('ngDoCheck');
}

ngAfterContentInit(): void {
    console.log('ngAfterContentInit');
}

ngAfterContentChecked(): void {
    console.log('ngAfterContentChecked');
}


ngAfterViewInit(): void {
    console.log('ngAfterViewInit');
}

ngAfterViewChecked(): void {
    console.log('ngAfterViewChecked');
}

ngOnDestroy(): void {
    console.log('ngOnDestroy');
}
}
</code></pre>
<h3 id="aftercontent">AfterContent</h3>
<h3 id="aftercontentinit">AfterContentInit</h3>
<blockquote>
<p>组件投影初始化后调用一次</p>
</blockquote>
<h3 id="aftercontentinit-1">AfterContentInit</h3>
<blockquote>
<p>AfterContentInit后以及每次投影内容改变后调用</p>
</blockquote>
<p>当组件或父组件发生变更检测后都会调用下面三个钩子 ngDoCheck ngAfterContentChecked ngAfterViewChecked</p>
<h2 id="12-变更检测">12. 变更检测</h2>
<h3 id="触发变更检测的时机">触发变更检测的时机</h3>
<ul>
<li>事件:页面 click、submit、mouse down……</li>
<li>XHR:从后端服务器拿到数据</li>
<li>Timers:setTimeout()、setInterval()</li>
</ul>
<h3 id="单向数据流">单向数据流</h3>
<p>假设其中某个组件中触发的变更检测,就会从根组件开始,从上至下,挨个检测一遍,直到最后一级组件全部检测完毕<br>
已经检测完的组件,不允许在被子组件修改,这就是单向数据流</p>
<h3 id="onpush策略下触发变更检测的时机">onPush策略下触发变更检测的时机</h3>
<blockquote>
<p>定时器已无法触发变更检测了</p>
</blockquote>
<ul>
<li>组件的@Input引用发生变化。</li>
<li>组件的 DOM 事件,包括它子组件的 DOM 事件,比如 click、submit、mouse down。</li>
<li>Observable 订阅事件,同时设置 Async pipe。</li>
<li>手动使用ChangeDetectorRef.detectChanges()、ChangeDetectorRef.markForCheck()、ApplicationRef.tick()方法</li>
</ul>
<p>阻断了一个组件的变更检测后,他和他的子组件都不会检测了</p>
<h6 id="apichangedetectorref">api:ChangeDetectorRef</h6>
<p>change.component</p>
<pre><code class="language-js">&lt;div class="change"&gt;
&lt;h2&gt;这是change组件&lt;/h2&gt;
&lt;p&gt; heroName: {{ heroName }} &lt;/p&gt;
&lt;div class="sec"&gt;
    &lt;button class="btn btn-primary" (click)="arms ='长剑'"&gt;改变武器&lt;/button&gt;
    &lt;app-change-child ="arms" (childInit)="heroName = '龙归'"&gt;&lt;/app-change-child&gt;
&lt;/div&gt;
&lt;/div&gt;
//========================================================
//child组件改变change组件 heroName值,触发变更检测报错
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-change',
templateUrl: './change.component.html',
styles: [`
      .change { width: 800px;height: 400px;background-color: #6f42c1;}
      .change h2 {color: #fff;}
      .change p {color: #fff; border: 1px solid #17a2b8;}`
]
})
export class ChangeComponent implements OnInit {
heroName = '卡特';
arms = '多兰剑';
constructor() { }
ngOnInit(): void {}
}
</code></pre>
<p>change-child.component</p>
<pre><code class="language-js">&lt;div class="change-child"&gt;
&lt;h3&gt;这是child组件&lt;/h3&gt;
&lt;p&gt;childName: {{ childName }}&lt;/p&gt;
//从父组件按钮点击改变武器arms,默认多兰剑,点击大剑,剥离组件后组件退出检测群,不能改变且不显示武器
//但OnChanges钩子依然可用,在里面重新附着child组件到组间树
&lt;p&gt;武器: {{ arms }}&lt;/p&gt;
&lt;button class="btn btn-danger" (click)="position = '上'"&gt;改变位置&lt;/button&gt;
&lt;app-change-grandson ="position"&gt;&lt;/app-change-grandson&gt;
&lt;/div&gt;
//===================================================
//childName = 'VN',3秒后改为EZ
//注入ChangeDetectorRef,从检测树剥离child组件
import {ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges} from '@angular/core';

@Component({
selector: 'app-change-child',
templateUrl: './change-child.component.html',
styles: [`
   .change-child {width: 600px;height: 350px;background-color: #c16a56;}
   .change-child h2 {color: #9cff61;}`
]
})
export class ChangeChildComponent implements OnInit, OnChanges {
childName = 'VN';
position = '下';
@Input() arms = '多兰剑';
@Output() childInit = new EventEmitter&lt;void&gt;();
constructor(private cdr: ChangeDetectorRef) {
    this.cdr.detach(); //从检测树剥离child组件
}

ngOnChanges(changes: SimpleChanges): void {
    console.log('changes', changes);
    this.cdr.reattach();//重新附着child组件到组间树
    setTimeout(() =&gt; {
      // this.cdr.detach();
    }, 0);
}

ngOnInit(): void {
    // this.childInit.emit();
    setTimeout(() =&gt; {
      this.childName = 'EZ';
    }, 3000);
}
}
</code></pre>
<p>change-grandson.component</p>
<pre><code class="language-js">&lt;div class="change-grand-son"&gt;
&lt;h4&gt;这是孙子组件&lt;/h4&gt;
&lt;button class="btn btn-success" (click)="grandSonName = '蛤蟆'"&gt;change grandson name&lt;/button&gt;
&lt;p&gt;孙子名称: {{ grandSonName }}&lt;/p&gt;
&lt;p&gt;位置: {{ position }}&lt;/p&gt;
&lt;/div&gt;
//=============================================================
//默认grandSonName = '河蟹',position: '上' | '下',父组件点击改变位置
//3秒后改grandSonName = 'f6',使用onpush策略后无法改变,markForCheck()手动变更检测,或者强制变更检测detectChanges()
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnInit, SimpleChanges} from '@angular/core';

@Component({
selector: 'app-change-grandson',
templateUrl: './change-grandson.component.html',
styles: [`
                  .change-grand-son {width: 600px;height: 350px;background-color: #3a35c1;}
          .change-grand-son h2 {color: #e44b51;}
          .change-grand-son p { color: #fa6993;}`
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChangeGrandsonComponent implements OnInit, OnChanges {
@Input() position: '上' | '下';
grandSonName = '河蟹';
constructor(private cdr: ChangeDetectorRef) { }

ngOnChanges(changes: SimpleChanges): void {
    console.log('changes position', changes);
}

ngOnInit(): void {
    setTimeout(() =&gt; {
      this.grandSonName = 'f6';
      this.cdr.markForCheck();
      // this.cdr.detectChanges();
    }, 3000);
}
}
</code></pre>
<h2 id="13-组件样式">13. 组件样式</h2>
<h3 id="宿主选择器">宿主选择器</h3>
<blockquote>
<p>在 <code>@Component</code> 的元数据中指定的样式只会对该组件的模板生效。</p>
</blockquote>
<h3 id="宿主选择器-1">宿主选择器</h3>
<blockquote>
<p>:host 选择是是把宿主元素作为目标的唯一方式</p>
</blockquote>
<p>它选中的是组件模板标签,比如,相当于在父组件的style中选择 app-user {}</p>
<p>当宿主标签上有 active class时生效</p>
<pre><code class="language-js">//子组件可通过:host选择宿主
&lt;app-style&gt;&lt;/app-style&gt;
:host {
display: block;
border: 1px solid black;
}
//选择有active的宿主
&lt;app-style class="active"&gt;&lt;/app-style&gt;
:host(.active) {
border-width: 3px;
}
</code></pre>
<h3 id="祖先选择器">祖先选择器</h3>
<p>当某个祖先元素有 CSS 类 theme-light 时,才会把 background-color 样式应用到组件内部的所有 .title 元素中<br>
找到根元素(html标签)为止</p>
<pre><code class="language-js">//选择宿主元素有.theme-light类的祖先
:host-context(.theme-light) .title {
background-color: #95f04c;
}
</code></pre>
<h3 id="样式模块化">样式模块化</h3>
<ul>
<li>在 @Component 的元数据中指定的样式只会对该组件的模板生效</li>
<li>组件的样式不会影响到子组件中的模板</li>
<li>组件的样式不会影响到投影内容</li>
</ul>
<h3 id="视图封装模式">视图封装模式</h3>
<ul>
<li>ShadowDom -- 不进不出,没有样式能进来,组件样式出不去, 就自己玩</li>
<li>Emulated -- 默认选项,只进不出,全局样式能进来,组件样式出不去</li>
<li>None -- 能进能出,此时组件的样式是全局生效的,注意与其他组件发生样式冲突</li>
</ul>
<pre><code class="language-javascript">@component({
        selector:'app-example'
        ...
        ...
        //encapsulation:ViewEncapsulation.ShadowDom
        encapsulation:ViewEncapsulation.None
})
</code></pre>
<h2 id="14-动态组件">14. 动态组件</h2>
<h3 id="概念">概念</h3>
<p>如果说,之前在模版中调用的组件为静态组件(比如:app-xxx)<br>
那么不用在模版里声明,而是通过ts动态插入到dom中到组件,可以视为动态组件</p>
<h3 id="example">example</h3>
<p>下面是一个弹层组件: alert.component.ts:</p>
<pre><code class="language-html">&lt;div ="wrapCls" role="alert"&gt;
&lt;span class="content"&gt;{{ options.content }}&lt;/span&gt;
&lt;i class="close" (click)="closed.emit()"&gt;&amp;times;&lt;/i&gt;
&lt;/div&gt;
</code></pre>
<p>c</p>
<pre><code class="language-javascript">import {Component, OnInit, ChangeDetectionStrategy, Output, EventEmitter} from '@angular/core';
type AlertTheme = 'primary' | 'warning' | 'danger';

export interface AlertOption {
content: string;
theme?: AlertTheme;
}

@Component({
selector: 'app-alert',
templateUrl: './alert.component.html',
styles: [`
    .close {
      display: block;
      width: 20px;
      height: 20px;
      position: absolute;
      right: 10px;
      top: 50%;
      margin-top: -10px;
      cursor: pointer;
    }
`],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AlertComponent implements OnInit {
options: Required&lt;AlertOption&gt; = {
    content: '',
    theme: 'primary'
}
@Output() readonly closed = new EventEmitter&lt;void&gt;();
constructor() { }

get wrapCls(): string {
    return 'alert alert-' + this.options.theme + ' fixed-top';
}

ngOnInit(): void {}
setOptions(options: AlertOption) {
    // console.log('options', options);
    this.options = { ...this.options, ...options };
}
}
</code></pre>
<p>c</p>
<pre><code class="language-html">&lt;div class="demo"&gt;
&lt;section class="m-auto"&gt;
    &lt;button class="btn btn-primary" (click)="showAlart()"&gt;show Alert&lt;/button&gt;
&lt;/section&gt;
&lt;/div&gt;
&lt;!--&lt;app-alert&gt;&lt;/app-alert&gt;--&gt;
</code></pre>
<p>c</p>
<pre><code class="language-javascript">import {
ApplicationRef,
Component,
ComponentFactoryResolver,
ComponentRef,
Injector,
OnInit,
ViewEncapsulation
} from '@angular/core';
import {AlertComponent} from '../components/alert/alert.component';

@Component({
selector: 'app-example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class ExampleComponent implements OnInit {
private container: AlertComponent;
private componentRef: ComponentRef&lt;AlertComponent&gt;;
constructor(
    private cfr: ComponentFactoryResolver,
    private inject: Injector,
    private appRef: ApplicationRef
) {}

ngOnInit(): void {}

showAlart() {
    if (!this.container) {
      this.container = this.getContainer();
    }
    this.container.setOptions({ content: '一段提示', theme: 'danger' });
}

private getContainer(): AlertComponent {
    // 创建指定类型的组件工厂(生产指定类型的组件)
    const factory = this.cfr.resolveComponentFactory&lt;AlertComponent&gt;(AlertComponent);

    // 根据指定的类型,创建组件的实例
    this.componentRef = factory.create(this.inject);
    // console.log('componentRef location', this.componentRef.location);
    // console.log('componentRef hostview', this.componentRef.hostView);

    // 将组件视图添加到视图树中,以激活变更检测
    this.appRef.attachView(this.componentRef.hostView);

    // 将组件到模版(包括app-alert标签),添加到body最后
    document.body.appendChild(this.componentRef.location.nativeElement);
    // document.body.appendChild((this.componentRef.hostView as EmbeddedViewRef&lt;{}&gt;).rootNodes as HTMLElement);

    this.componentRef.onDestroy(() =&gt; {
      console.log('已经销毁');
    });
    const { instance } = this.componentRef;
    instance.closed.subscribe(() =&gt; {
      this.componentRef.destroy();
      this.container = null;
    });
    return instance;
}
}
</code></pre>
<h3 id="entrycomponents-">entryComponents ?</h3>
<p>v9和v10,动态组件都不需要entryComponents了,当然写了也没有问题 从v11开始,entryComponents可能被删除 v8及以前,动态组件一定要声明entryComponents</p><br><br>
来源:https://www.cnblogs.com/dingdc/p/14037404.html
頁: [1]
查看完整版本: Angular(2)