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">
<h1>hello world</h1>
<div class="box" #box>box</div>
<ng-template #tpl>
<span>hello</span>
</ng-template>
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<any>
@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 中定义的 <template> 模板元素,渲染后被替换成 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<any>
@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<any>;
@ViewChild('secondTpl', { read: TemplateRef }) readonly secondTpl: TemplateRef<any>;
@ViewChild('thirdTpl', { read: TemplateRef }) readonly thirdTpl: TemplateRef<any>;
@ViewChild('fourthTpl', { read: TemplateRef }) readonly fourTpl: TemplateRef<any>;
@ViewChild('freeTpl', { read: TemplateRef }) readonly freeTpl: TemplateRef<any>;
@ViewChild('firstContainer', { read: ViewContainerRef, static: true }) readonly firstContain: ViewContainerRef;
@ViewChild('secondContainer', { read: ViewContainerRef, static: true }) readonly secondContain: ViewContainerRef;
private freeViewRef: EmbeddedViewRef<any>;
constructor() {
// console.log('constructor');
}
insert(tpl: TemplateRef<any>) {
this.firstContain.insert(tpl.createEmbeddedView(null));
}
insertAll() {
.forEach(tpl => {
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(() => {
this.firstContain.createEmbeddedView(this.firstTpl);
}, 0);
}
}
</code></pre>
<pre><code class="language-html"><div class="box" #box>
<button class="btn btn-primary mr-1" (click)="insert(secondTpl)">insert second</button>
<button class="btn btn-primary mr-1" (click)="insert(thirdTpl)">insert third</button>
<button class="btn btn-primary mr-1" (click)="insert(fourthTpl)">insert fourth</button>
<button class="btn btn-primary mr-1" (click)="insertAll()">insert all</button>
<button class="btn btn-secondary mr-1" (click)="insertFree()">insert free</button>
<button class="btn btn-info mr-1" (click)="getOne()">get one</button>
<button class="btn btn-success mr-1" (click)="move()">move free</button>
<button class="btn btn-success mr-1" (click)="move2To4()">把第二个移动到第四个位置上</button>
<button class="btn btn-success" (click)="move2ToOther()">把第二个移动到其他容器中</button>
<p>长度:{{ firstContain?.length }}</p>
</div>
<ng-template #firstTpl>
<p>first tpl content</p>
</ng-template>
<ng-template #secondTpl>
<p>第二段template</p>
</ng-template>
<ng-template #thirdTpl>
<p>第三段template</p>
</ng-template>
<ng-template #fourthTpl>
<p>第四段template</p>
</ng-template>
<ng-template #freeTpl>
<p>自由的template</p>
</ng-template>
<p>first container:
<ng-container #firstContainer></ng-container>
</p>
<hr>
<p> second container:<ng-container #secondContainer></ng-container>
</p>
</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: `
<app-tpl-outlet ="customTpl"></app-tpl-outlet>
<ng-template #customTpl let-def let-val="value">
<b>{{def}} ----- {{val}}</b>
</ng-template>
`
})
export class ItemDetailComponent { }
</code></pre>
<pre><code class="language-html">import { Component, Input, TemplateRef } from '@angular/core';
@Component({
selector: 'app-tpl-outlet',
template: `
<div>
<ng-container="render || defaultTpl" ="myContext"></ng-container>
<ng-container *ngTemplateOutlet="render || defaultTpl; context: myContext"></ng-container>
<!-- 用在ng-template上也可以 -->
<ng-template ="render || defaultTpl" ="myContext"></ng-template>
<ng-template *ngTemplateOutlet="render || defaultTpl; context: myContext"></ng-template>
</div>
<ng-template #defaultTpl>
<b>这是默认的内容</b>
</ng-template>
`
})
export class TplOutletComponent{
@Input () render: TemplateRef<any>;
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: `
<div>
<ng-container ="render || defaultTpl"></ng-container>
<ng-template #defaultTpl>
<b>这是默认的内容</b>
</ng-template>
</div>
`
})
export class TplOutletComponent{
@Input () render: TemplateRef<any>;
}
</code></pre>
<pre><code class="language-js">import { Component, Input } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<app-tpl-outlet ="customTpl"></app-tpl-outlet>
<ng-template #customTpl>
<p>这是自定义的内容</p>
</ng-template>
`
})
export class ItemDetailComponent { }
</code></pre>
<h2 id="5-组件投影插槽">5. 组件投影(插槽)</h2>
<h3 id="具名插槽">具名插槽</h3>
<p>子</p>
<pre><code class="language-html"><div class="shadow">
<h3>hello shadow</h3>
<div class="head bg-primary">
我是头
<ng-content select=".head"></ng-content>
</div>
<div class="body">
我是身体
<ng-content select=".body"></ng-content>
</div>
<div class="foot bg-danger">
我是尾巴
<ng-content select=".foot"></ng-content>
</div>
</div>
</code></pre>
<p>父</p>
<pre><code class="language-html"><app-structural>
<div class="head">
头部content
</div>
<div class="body">
默认主体内容content
</div>
<div class="foot">
尾部content
</div>
</app-structural>
</code></pre>
<hr>
<p>子</p>
<pre><code class="language-html">import { Component, Input, TemplateRef } from '@angular/core';
@Component({
selector: 'app-shadow',
template: `
<div class="shadow">
<div class="head">
<ng-content select=".head"></ng-content> //具名插槽
</div>
<div class="body">
<ng-content select=""></ng-content>
<ng-content select="article"></ng-content>//
<ng-content></ng-content>//普通插槽
</div>
<div class="foot">
<ng-content select=".foot"></ng-content> //具名插槽
</div>
</div>`
})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><ng-content></ng-content></code></li>
</ul>
<pre><code class="language-html">import { Component, Input } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<app-shadow ="true">
<div class="head">这是head的投影</div>
<div attr>这是attr的投影内容</div>
<article>这是article的投影内容</article>
<b style="color: #007bff">这是默认的投影内容</b>
<div class="foot">这是foot的投影</div>
</app-shadow>
`
})
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: `
<section>
<h3>获取dom</h3>
<div class="box" #box>
<p>box</p>
</div>
</section>
`,
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: `
<section>
<h3>获取子组件</h3>
<app-view-child-panel></app-view-child-panel>
</section>
`,
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: `
<section>
<h3>获取子组件</h3>
<app-view-child-panel #myPanel></app-view-child-panel>
</section>
`,
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><app-panel></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: `
<section>
<h3>获取dom</h3>
<div class="box" #box>
<p>box</p>
</div>
<div class="box" #box>
<p>box</p>
</div>
</section>
<section #box>
<h3>获取子组件</h3>
<app-view-child-panel #myPanel></app-view-child-panel>
<app-view-child-panel #myPanel></app-view-child-panel>
<app-view-child-panel #myPanel></app-view-child-panel>
</section>
`,
styles: []
})
export class ViewChildComponent implements OnInit, AfterViewInit {
@ViewChild('box', { static: true }) private boxEl: ElementRef;
@ViewChildren('box') private boxEls: QueryList<ElementRef>;
@ViewChild(ViewChildPanelComponent, { static: true }) private panel: ViewChildPanelComponent;
@ViewChildren(ViewChildPanelComponent) private panels: QueryList<ViewChildPanelComponent>;
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: `
<section>
<h3>获取子组件</h3>
<button class="btn btn-primary" (click)="showMidPanel = !showMidPanel">toggle mid</button>
<app-view-child-panel #myPanel></app-view-child-panel>
<app-view-child-panel #myPanel *ngIf="showMidPanel"></app-view-child-panel>
<app-view-child-panel #myPanel></app-view-child-panel>
</section>
`,
styles: [ ]
})
export class ViewChildComponent implements OnInit, AfterViewInit {
@ViewChildren(ViewChildPanelComponent) private panels: QueryList<ViewChildPanelComponent>;
constructor() {}
ngOnInit(): void {}
ngAfterViewInit(): void {
this.panels.changes.subscribe(changes => {
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: `
<app-content-child>
<div class="head">
这是头部
</div>
<app-content-child-panel></app-content-child-panel>
<ul #list>
<li>aaa</li>
<li>bbb</li>
</ul>
</app-content-child>
`,
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: `
<div class="content-child-box">
<h2>这是content child组件</h2>
<div class="head" style="border: 1px solid; margin: 10px 0;">
<ng-content select=".head"></ng-content>
</div>
<ng-content></ng-content>
</div>
`,
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"><app-content-child>
<div class="head">
这是头部
<app-content-child-panel></app-content-child-panel>
</div>
<app-content-child-panel></app-content-child-panel>
<app-content-child-panel></app-content-child-panel>
<ul #list>
<li>aaa</li>
<li>bbb</li>
</ul>
</app-content-child>
export class ContentChildComponent implements AfterViewInit {
@ContentChildren(ContentChildPanelComponent) private panels: QueryList<ContentChildPanelComponent>;
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: `
<h2>Power Boost Calculator</h2>
<div>Normal power: <input [(ngModel)]="power"></div>
<div>Boost factor: <input [(ngModel)]="factor"></div>
<!--<p>Super power boost: {{ 2 | exponentialStrength }}</p>-->
<p>Super power boost: {{ power | exponentialStrength: factor }}</p>
`,
})
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 => 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: `
<input type="text" #box (keyup.enter)="addHero(box.value)" placeholder="hero name" />
<button (click)="reset()">Reset</button>
<div *ngFor="let hero of (heroes | flyingHeroes)">
{{hero.name}}
</div>
`,
})
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
<button (click)="title = 'self title'">set title self</button>
<p>title: {{ title }}</p>
`
})
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 -> ngOnChanges -> 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"><button class="btn btn-primary" (click)="show = !show">toggle</button>
<app-life-cycle title="aaa" *ngIf="show"></app-life-cycle>
</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"><div class="change">
<h2>这是change组件</h2>
<p> heroName: {{ heroName }} </p>
<div class="sec">
<button class="btn btn-primary" (click)="arms ='长剑'">改变武器</button>
<app-change-child ="arms" (childInit)="heroName = '龙归'"></app-change-child>
</div>
</div>
//========================================================
//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"><div class="change-child">
<h3>这是child组件</h3>
<p>childName: {{ childName }}</p>
//从父组件按钮点击改变武器arms,默认多兰剑,点击大剑,剥离组件后组件退出检测群,不能改变且不显示武器
//但OnChanges钩子依然可用,在里面重新附着child组件到组间树
<p>武器: {{ arms }}</p>
<button class="btn btn-danger" (click)="position = '上'">改变位置</button>
<app-change-grandson ="position"></app-change-grandson>
</div>
//===================================================
//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<void>();
constructor(private cdr: ChangeDetectorRef) {
this.cdr.detach(); //从检测树剥离child组件
}
ngOnChanges(changes: SimpleChanges): void {
console.log('changes', changes);
this.cdr.reattach();//重新附着child组件到组间树
setTimeout(() => {
// this.cdr.detach();
}, 0);
}
ngOnInit(): void {
// this.childInit.emit();
setTimeout(() => {
this.childName = 'EZ';
}, 3000);
}
}
</code></pre>
<p>change-grandson.component</p>
<pre><code class="language-js"><div class="change-grand-son">
<h4>这是孙子组件</h4>
<button class="btn btn-success" (click)="grandSonName = '蛤蟆'">change grandson name</button>
<p>孙子名称: {{ grandSonName }}</p>
<p>位置: {{ position }}</p>
</div>
//=============================================================
//默认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(() => {
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选择宿主
<app-style></app-style>
:host {
display: block;
border: 1px solid black;
}
//选择有active的宿主
<app-style class="active"></app-style>
: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"><div ="wrapCls" role="alert">
<span class="content">{{ options.content }}</span>
<i class="close" (click)="closed.emit()">&times;</i>
</div>
</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<AlertOption> = {
content: '',
theme: 'primary'
}
@Output() readonly closed = new EventEmitter<void>();
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"><div class="demo">
<section class="m-auto">
<button class="btn btn-primary" (click)="showAlart()">show Alert</button>
</section>
</div>
<!--<app-alert></app-alert>-->
</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<AlertComponent>;
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<AlertComponent>(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<{}>).rootNodes as HTMLElement);
this.componentRef.onDestroy(() => {
console.log('已经销毁');
});
const { instance } = this.componentRef;
instance.closed.subscribe(() => {
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]