Navigation
阅读进度0%
No headings found.

Angular 指令与模板深度解析

June 17, 2024 (1y ago)

Angular
Directives
Templates

指令

在ng中除去基本的语法之外 对于模板 template来说 最大一块内容就是指令了

基本的双向绑定

基本的双向绑定和Vue实际上也差不多, 它的写法如下面这样, 这里我们结合 两个父子组件来说明 如何做 双向绑定 核心要点就是** [ ( ) ] = event 至于具体的工作原理 实际上就是output input + emit 官方文档有详细的**解释

# son 组件
import { Component, OnInit, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
 
@Component({
  selector: 'app-son',
  template: `
    <div>
    <button class="btn btn-danger" (click)="dec()" title="smaller">-</button>
    <button class="btn btn-primary" (click)="inc()" title="bigger">+</button>
    <label [style.font-size.px]="size">FontSize: {{size}}px</label>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,  // 这是一种 变更检测 的策略,你...无脑加上就好了
  styles: [
  ]
})
export class SonComponent implements OnInit {
  @Input()  size: number | string;
  // 想要用双向绑定语法,output变量名就一定是输入属性名加上Change
  @Output() sizeChange = new EventEmitter<number>();
 
  constructor() {}
  ngOnInit(): void {}
 
  dec() { this.resize(-1); }
  inc() { this.resize(+1); }
  resize(delta: number) {
    this.size = Math.min(40, Math.max(8, +this.size + delta));
    this.sizeChange.emit(this.size);
  }
}
 
 
# father 组件
import { Component, OnInit } from '@angular/core';
 
@Component({
  selector: 'app-father',
  template: `
   <app-son [(size)] = "fontSizePx"></app-son>
   <button class="btn btn-primary"  (click) ="getValue()" title="bigger">getValue</button>
  `,
  styles: [
  ]
})
export class FatherComponent implements OnInit {
 
  fontSizePx = 12;
 
  getValue(value:any) {
    console.log(this.fontSizePx);
  }
 
  constructor() {}
 
  ngOnInit(): void {}
 
 
}
 

表单中的双向绑定

话不多说 直接看 代码Code 官方文档

# 首先 ng 中实现双向绑定 需要使用一个 叫做 “指令(准确来说 这里要使用的 内置指令)”的东西。ngModel
# 前面就说过 如果一个 moduel 要使用一些 “不属于它的功能”的时候 需要引入 外部的依赖 并且放到 
imports 数组中,所以 第一步 需要加入一个FormsModule 这样 ngModel 这个 指令 才能生效
 
# app.moduel.ts
import { FormsModule } from '@angular/forms'
++++
@NgModule({
++++
  imports: [
    BrowserModule,  
    AppRoutingModule,
    FormsModule // 重点!
  ], 
++++
})
export class AppModule { }
 
# demo.compnents.ts
import { Component, OnInit } from '@angular/core';
 
@Component({
  selector: 'app-family',
  template: `
    <input [(ngModel)]="name" #ctrl="ngModel" required>
      
    <p>Value: {{ name }}</p>
    <p>Valid: {{ ctrl.valid }}</p>
    
    <button (click)="setValue()">Set value</button>
  `,
  styles: [
  ]
})
export class FamilyComponent implements OnInit {
  
  name: string = '';
 
  constructor() { }
 
  ngOnInit(): void {
  }
 
  setValue() {
    this.name = 'Nancy';
  }
}
 
// 实际上从本质来说 [( ngModel )] 指令是 这个方式的上层封装
<input [(ngModel)]="name" /> 
上面这行代码相当于: 
<input [value]="name" (input)="name = $event.target.value" />
 
// 如果说你想在form表单中使用 ngModel请看下面的示例代码
  
import { Component, OnInit } from '@angular/core';
import {NgForm} from '@angular/forms';
 
@Component({
  selector: 'app-family',
  template: `
  <form>
    <input [(ngModel)]="value" name="name" />
    <input [(ngModel)]="value" [ngModelOptions]="{ standalone: true }" />
  </form>
  `,
  styles: [
  ]
})
export class FamilyComponent implements OnInit {
  
  name: string = '';
 
  constructor() { }
 
  ngOnInit(): void {
  }
 
}
 

NgIf

  1. 我们先来看基本的使用 和语法糖
import { Component, OnInit } from '@angular/core';
import {NgForm} from '@angular/forms';
 
@Component({
  selector: 'app-family',
  template: `
    <div *ngIf="condition">Content to render when condition is true.</div>  
    <button class="btn btn-primary" (click)="condition = !condition" title="bigger">change</button>
  
    <!--语法糖 我们一般怎么写
      ng-template是一块内嵌模板 类型 TemplateRef-->
    <ng-template [ngIf]="condition">
      <div>Content to render when condition is true.</div>
    </ng-template>
 
    <!--下面的else语法 注意此处的 elseBlock 并不是某个boolean 而是ng-template
    的一引用-->
    <div *ngIf="condition; else elseBlock">condition为真时显示</div>
    <ng-template #elseBlock>
      <p>condition为假时显示</p>
    </ng-template>
    <ng-template #thenBlock>condition为true时显示</ng-template>
  `,
  styles: [
  ]
})
export class FamilyComponent implements OnInit {
  
  condition = true;
 
  constructor() { }
 
  ngOnInit(): void {
  }
 
}
 

上面就是 一种 隐式 的使用templateRef ,下面演示怎么用组件中的变量TemplateRef类型 请看文档

import { Component, OnInit, ChangeDetectionStrategy, 
        ViewChild, TemplateRef, AfterViewInit } from '@angular/core';
 
@Component({
  selector: 'app-family',
  template: `
  <button class="btn btn-primary" (click)="condition = !condition">toggle block</button>
  <p *ngIf="condition else elseBlocks">{{ condition }} === true 时显示</p>
  <ng-template #firstTpl>
    <p>{{ condition }} === false 时显示</p>
  </ng-template>
  `,
  styles: [
  ]
})
export class FamilyComponent implements OnInit {
  elseBlocks: TemplateRef<any> = null;
  @ViewChild('firstTpl', {static: true}) primaryBlock: TemplateRef<any> = null;
  // 先不要管这个语法 你照着写就是了,后续会慢慢的详细的介绍
  condition = false;
 
  constructor() { }
 
  ngOnInit(): void {
    console.log('ngOnInit', this.primaryBlock);
    this.elseBlocks = this.primaryBlock;
  }
 
}
 

NgSwitch

import { Component, OnInit, ChangeDetectionStrategy, ViewChild, TemplateRef, AfterViewInit } from '@angular/core';
 
@Component({
  selector: 'app-family',
  template: `
      <p>
      <input 
        type="radio" 
        name="fruit" 
        value="apple" 
        id="apple" 
        [(ngModel)]="fruit" />
      <label for="apple">🍎</label>
    </p>
    <p>
      <input 
        type="radio" 
        name="fruit" 
        value="pear" 
        id="pear" 
        [(ngModel)]="fruit" />
      <label for="pear">🍐</label>
    </p>
    <p>
      <input 
        type="radio" 
        name="fruit" 
        value="grape" 
        id="grape" 
        [(ngModel)]="fruit" />
      <label for="grape">🍇</label>
    </p>
    <p>
      <input 
        type="radio" 
        name="fruit" 
        value="other" 
        id="other"
         [(ngModel)]="fruit" />
      <label for="other">other</label>
    </p>
 
    selected fruit: {{ fruit }}
 
    <div class="content" [ngSwitch]="fruit">
      <p *ngSwitchCase="'apple'">这是 苹果</p>
      <p *ngSwitchCase="'pear'">这是 梨</p>
      <p *ngSwitchCase="'grape'">这是 葡萄</p>
      <p *ngSwitchDefault>啥都不是</p>
    </div>
  `,
  styles: [
  ]
})
export class FamilyComponent implements OnInit {
  fruit = '';
  condition = false;
 
  constructor() { }
 
  ngOnInit(): void {
  }
 
}
 

NgFor

这个NgFor ,

最基础的使用

import { Component, OnInit } from '@angular/core';
const Heros = [
  {
    id: 'hero_0',
    name: '盖伦'
  },
  {
    id: 'hero_1',
    name: '赵信'
  },
  {
    id: 'hero_2',
    name: '嘉文'
  },
  {
    id: 'hero_3',
    name: '易大师'
  },
  {
    id: 'hero_3',
    name: '泰达米尔'
  }
];
interface Hero {
  id: string;
  name: string;
}
 
@Component({
  selector: 'app-family',
  template: `
    <p>
      add hero:
      <button class="btn btn-info" (click)="reset()">reset</button>
    </p>
    <ul>
      <li 
        *ngFor="let item of heros;  trackBy: trackByHero" 
        [style.color]="item.id === 'hero_2' ? 'orange' : '#333'">{{ item.name }}</li>
    </ul>
  `,
  styles: [
  ]
})
export class FamilyComponent implements OnInit {
  heros: Hero[] = Heros;
  constructor() { }
  ngOnInit(): void {
  }
  reset() {
    this.heros = [
      {
        id: 'hero_4',
        name: '盖伦4'
      },
      {
        id: 'hero_5',
        name: '赵信5'
      },
      {
        id: 'hero_2',
        name: '嘉文'
      },
      {
        id: 'hero_6',
        name: '易大师6'
      },
      {
        id: 'hero_7',
        name: '泰达米尔7'
      }
    ];
  }
 
  // trackBy接收一个函数,返回 NgFor 应该跟踪的值(比如id),
  // 这样刷新列表时,id相同的dom不会触发更新
  trackByHero(hero: Hero): string {
    return hero.id;
  }
}
 

我们来看看更多的扩展字段

参数 含义
$implicit: T: 迭代目标(绑定到ngForOf)中每个条目的值。
ngForOf: NgIterable 迭代表达式的值。当表达式不局限于访问某个属性时,这会非常有用,比如在使用 async 管道时(userStreams
index: number 可迭代对象中当前条目的索引。
count:number 可迭代对象的长度。
first: boolean 如果当前条目是可迭代对象中的第一个条目则为 true。
last: boolean 如果当前条目是可迭代对象中的最后一个条目则为 true。
even: boolean 如果当前条目在可迭代对象中的索引号为偶数则为 true。
odd: boolean 如果当前条目在可迭代对象中的索引号为奇数则为 true。
import {Component} from '@angular/core';
@Component({
  selector: 'app-switch',
  template: `
    <ul>
      <li
        *ngFor="
        let item of heros; 
        index as i 
				count as len;
        let ev = even; 
        let od = odd; 
        let f = first; 
        let l = last 
        trackBy: trackByHero"
        [class.even]="ev"
      	[class.odd]="od">
        
        <p>first: {{ f }} -- last: {{ l }}</p>
        <p>name: {{ item.name }}</p>
        <p>length: {{ len }}</p>
        <p>index: {{ i }}</p>
        <hr />
      </li>
    </ul>
  `,
    styles: [`
    .even {
      color: #82fa54;
    }
 
    .odd {
      color: #698efa;
    }
  `]
})
export class SwitchComponent {
  heros: Hero[] = Heros;
  trackByHero(hero: Hero): string {
    return hero.id;
  }
}

模板引用变量

所谓的模板引用变量实际上有点类似,React中的额Ref(如果你不懂React请忽略),它的作用是可以拿到一些东西的引用,比如dom 、指令、组件、元素等。它的写法非常的简单

<input #phone placeholder="phone number" /> 
  // 这样也是可以的
<input ref-phone placeholder="phone number" /> 
  
<button (click)="callPhone(phone.value)">Call</button>
 
// 这的callPhone(phone.value)里的phone 就是上面的input这个DOM原素
  • 它有下面的一些规则

1.如果是组件上声明 # 就是 获取组件实例

2.如果是HTML上声明 # 就是 获取引用元素

3.如果是上声明 # 就是 TemplatRef

4.如果#左边指定了一个名字,就是引用元素上具备这个名字的指令/组件

#var="ngModel" 
 
如果是TemplateRef 你可以这样获得它
@ViewChild('firstTpl', {static: true}) primaryBlock: TemplateRef<any> = null;
  • 它可以和NgForm结合
<form #itemForm="ngForm" (ngSubmit)="onSubmit(itemForm)">
  <label for="name">Name</label>
  <input type="text" id="name" class="form-control" name="name" ngModel required />
  <button type="submit">Submit</button>
</form>
 
<div [hidden]="!itemForm.form.valid">
  <p>{{ submitMessage }}</p>
</div>
 
// itemForm出现了三次
// 如果没有ngForm的话,那么这个itemForm就是form这个DOM 如果有了就是这个 NgFrom指令的引用了
// 有了这个引用就能拿它做很多NgFrom上的事情了 ,比如valid
 
 
 
 
  • 你应该还需呀小心它的作用域
// 总的来说,如果你写成这样
<input *ngIf="true" #ref2 type="text" [(ngModel)]="secondExample" />
<span>Value: {{ ref2?.value }}</span> <!-- doesn't work -->
 
// 或则这样那么就上有问题的!
<ng-container *ngFor="let i of [1,2]">
  <input #ref type="text" [value]="i" />
</ng-container>
{{ ref.value }}
 
// 第一个上ref2不一定能获取到你的ref2 因为它前面有一个*ngIf
// 第二个 由于*ngForm把 这个ng-conotaiern实例化了两次,导致不知道到底是哪一个
 

模板运算符

所谓的模板运算符就是一些在模板上的特殊语法,他们具备特殊的功能,比如pipe

管道Pipe基础使用

// 比如下面的这个最简单的例子 它格式化了日期和事件
<p>The hero's birthday is {{ birthday | date }}</p>
 
// 
 birthday = new Date(1988, 3, 15); 
 
 

管道参数和串联

 这里是原数据 | 这里是处理管道
 
 // 参数在处理管道这里加,譬如
{{ amount | currency:'EUR' }}  // 会把 amount 转换成欧元。
{{ amount | currency:'EUR':'Euros '}} 
// 会把第二个参数(字符串 'Euros ')添加到输出字符串中
// 下面的例子很简单易懂 
<p>The hero's birthday is {{ birthday | date:"MM/dd/yy" }} </p>
// 参数也可以从 组件中通过函数返回比如
  <p>The hero's birthday is {{ birthday | date:format }}</p>
  get format()   { return this.toggle ? 'shortDate' : 'fullDate'; }
 
// 受到FP编程方式的影响管道可以 克里化 一个一个串联起来用
{{  birthday | date:'fullDate' | uppercase}}  // 从左到右的执行
 
 

自定义管道

ng generate pipe 命令会自动注册该管道。

// 自定义管道非常的简单
 @Pipe 装饰器应用到这个类上。就好了,比如下面👇 (主要是实现PipeTransform 接口和重写
transform接口

 
 
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({name: 'exponentialStrength'})
export class ExponentialStrengthPipe implements PipeTransform {
  transform(value: number, exponent = 1): number {
    return Math.pow(value, exponent);
  }
}
 
// 使用 
<p>Super power boost: {{2 | exponentialStrength: 10}}</p>
 

关于管道的变更检测

import { Component } from '@angular/core';
 
@Component({
  selector: 'app-power-boost-calculator',
  template: `
    <h2>Power Boost Calculator</h2>
    <label for="power-input">Normal power: </label>
    <input id="power-input" type="text" [(ngModel)]="power">
    <label for="boost-input">Boost factor: </label>
    <input id="boost-input" type="text" [(ngModel)]="factor">
    <p>
      Super Hero Power: {{power | exponentialStrength: factor}}
    </p>
  `,
  styles: ['input {margin: .5rem 0;}']
})
export class PowerBoostCalculatorComponent {
  power = 5;
  factor = 1;
}
 
// 从上面的例子我们可以知道,如果管道有绑定,那么意味着,如果值变了 那么管道就会重新执行
 
 
// 但是它是这么做到的,如果我不希望它变怎么半? 对于管道来说默认的情况下都是:”纯的pure“默
// 人只能做值检测和引用检测,dep检测没有处理 也就是说push数组不会触发更新,如果你需要
// 怎么做的时候请使用 [...] , or {...}
 
// 如果我说有副作用的管道怎么办?官方知道是把 参数pure变成fasle就好了,譬如下面
// 基础类 (默认纯的)
import { Pipe, PipeTransform } from '@angular/core';
 
import { Hero } from './heroes';
 
@Pipe({ name: 'flyingHeroes' })
export class FlyingHeroesPipe implements PipeTransform {
  transform(allHeroes: Hero[]) {
    return allHeroes.filter(hero => hero.canFly);
  }
}
 
// 派生的非纯的
@Pipe({
  name: 'flyingHeroesImpure',
  pure: false
})
export class FlyingHeroesImpurePipe extends FlyingHeroesPipe {}
 
 
// 如果你的管道依赖的值说 observe的,那么需要注意了 ,你需要做的事情是 使用 async 内置管道
// 处理数据
 
import { Component } from '@angular/core';
 
import { Observable, interval } from 'rxjs';
import { map, take } from 'rxjs/operators';
 
@Component({
  selector: 'app-hero-async-message',
  template: `
    <h2>Async Hero Message and AsyncPipe</h2>
    <p>Message: {{ message$ | async }}</p>
    <button (click)="resend()">Resend</button>`,
})
export class HeroAsyncMessageComponent {
  message$: Observable<string>;
 
  private messages = [
    'You are my hero!',
    'You are the best hero!',
    'Will you be my hero?'
  ];
 
  constructor() {
    this.message$ = this.getResendObservable();
  }
 
  resend() {
    this.message$ = this.getResendObservable();
  }
 
  private getResendObservable() {
    return interval(500).pipe(
      map(i => this.messages[i]),
      take(this.messages.length)
    );
  }
}

使用非纯管道缓存Http数据( 每个管道都将会是一个实例 )

// 使用非纯管道可以缓存http数据
// 每当组件运行变更检测时就会调用非纯管道,这可能每隔几毫秒就运行一次。为避免出现性能问题,
//只有当请求的 URL 发生变化时才会调用该服务器(如下例所示) 
 
import { HttpClient } from '@angular/common/http';
import { Pipe, PipeTransform } from '@angular/core';
 
@Pipe({
  name: 'fetch',
  pure: false
})
export class FetchJsonPipe implements PipeTransform {
  private cachedData: any = null;
  private cachedUrl = '';
 
  constructor(private http: HttpClient) { }
 
  transform(url: string): any {
    if (url !== this.cachedUrl) {
      this.cachedData = null;
      this.cachedUrl = url;
      this.http.get(url).subscribe(result => this.cachedData = result);
    }
 
    return this.cachedData;
  }
}
 
// 使用
 
{
template: `
    <h2>Heroes from JSON File</h2>
 
    <div *ngFor="let hero of ('assets/heroes.json' | fetch) ">
      {{hero.name}}
    </div>
 
    <p>Heroes as JSON:
      {{'assets/heroes.json' | fetch | json}}
    </p>
`
}
 

属性型指令

属性型指令,可以用来改变DOM外观和行为

最简单的使用

// 使用非常的容易
import {Directive, ElementRef, EventEmitter, HostListener, Input, Output} from '@angular/core';
 
@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  @Input('appHighlight') highlightColor: string;  // @HostListener 能够替你进行事件监听
  @Output() colorChange = new EventEmitter<string>();
  constructor(private el: ElementRef) {
    console.log('appHighlight');
  }
  @HostListener('mouseenter') onMouseEnter() {
    this.highlight(this.highlightColor || 'yellow');
  }
 
  @HostListener('mouseleave') onMouseLeave() {
    this.highlight('');
  }
 
  private highlight(color: string) {
    this.el.nativeElement.style.backgroundColor = color;
    this.colorChange.emit(color);
  }
}
 
//
<p [appHighlight]="color">Highlight me!</p>
 

我希望展示 {{ 1 + 1 }} 怎么办?

<p ngNonBindable>This should not evaluate: {{ 1 + 1 }}</p>
// 请使用系统内置的 ngNonBindable 指令

结构型指令

结构顾名思义它可以更改dom的结构,专门用来干这件事的,比如ngIf ,ngFor ,ngSwitch 都是结构性指令

// 创建一个结构性指令 这个功能是对NgIf的取反操作 
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
 
 
@Directive({ selector: '[appUnless]'})
export class UnlessDirective {
  private hasView = false;
 
  constructor(
    private templateRef: TemplateRef<any>,  // 这个可以帮你获取ng-templaye引用
    private viewContainer: ViewContainerRef) { } // 这个也是可以帮你获取视图容器
// 前面的文章中,我们有指出 NgIf的完整写法是就是外面有一层template
 
  @Input() set appUnless(condition: boolean) {
    if (!condition && !this.hasView) {
      this.viewContainer.createEmbeddedView(this.templateRef);
      this.hasView = true;
    } else if (condition && this.hasView) {
      this.viewContainer.clear();
      this.hasView = false;
    }
  }
}
// 使用
<p *appUnless="condition">Show this sentence unless the condition is true.</p>

TemplateRef和ViewContainerRef

前者表示 一个内嵌的模版,它的createEmbeddedVIew可以把它自己附着到一个父试图上

后者表示 可以将一个/多个 视图附着到组件中的容器,在createComponent的时候

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() {
    [this.secondTpl, this.thirdTpl, this.fourTpl].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[0]);*/
    setTimeout(() => {
      this.firstContain.createEmbeddedView(this.firstTpl);
    }, 0);
  }
 
}
<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>

NgTemplateOutlet指令

这个东西可以“根据一个提前准备好的templateRef插入内嵌视图”

import { Component, Input, TemplateRef } from '@angular/core';
@Component({
  selector: 'app-tpl-outlet',
  template: `<div>
               <ng-container *ngTemplateOutlet="render || defaultTpl; context: myContext"></ng-container>
               <!--      <ng-container [ngTemplateOutlet]="render || defaultTpl" [ngTemplateOutletContext]="myContext"></ng-container>-->
               
               <!--   用在ng-template上也可以   -->
               <!--      <ng-template *ngTemplateOutlet="render || defaultTpl; context: myContext"></ng-template>-->
               <!--      <ng-template [ngTemplateOutlet]="render || defaultTpl" [ngTemplateOutletContext]="myContext"></ng-template>-->
             </div>
            <ng-template #customTpl let-def let-val="value">
              <b>customTpl rendered! def: {{ def }}; value: {{ val }}</b>
            </ng-template>`
})
export class TplOutletComponent  {
  @Input () render: TemplateRef<any>;
  myContext = {$implicit: 'World', value: 'Svet'};
 
}
 
//  在上下文对象中使用 $implicit 这个 key 会把对应的值设置为默认值。

调用TplOutletComponent传入自定义的dom

import { Component, Input } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `<app-tpl-outlet [render]="render"></app-tpl-outlet>
             <ng-template #render let-value="value">
               <p><b>自定义的dom -- {{ value }}</b></p>
             </ng-template>
          `
})
export class ItemDetailComponent  {
 
}

组件投影

这个东西就像Vue中的插槽 或则React中的Chilend一样, 它有下面几种变体

 
// 最基础使用  单投影
@Component({
  selector: 'app-zippy-basic',
  template: `
    <h2>Single-slot content projection</h2>
    <ng-content></ng-content>
  `
})
export class ZippyBasicComponent {}
 
<app-zippy-basic>
  <p>Is content projection cool?</p>  // 这个将会显示在ng-content 中
</app-zippy-basic>
 
// 多插槽 投影
@Component({
selector: 'app-zippy-multislot',
template: `
  <h2>Multi-slot content projection</h2>
 
  Default:
  <ng-content></ng-content>
 
  Question:
  <ng-content select="[question]"></ng-content>
`
})
export class ZippyMultislotComponent {}
 
// 使用 question 属性的内容将投影到带有 select=[question] 属性的 <ng-content> 元素。
<app-zippy-multislot>
  <p question>
    Is content projection cool?
  </p>
  <p>Let's learn about content projection!</p>
</app-zippy-multislot>
 
// 带条件的 这个比较的复杂
1.在continer中接受ng-template
2.给container包裹一层判断(既你自己的判断逻辑)
3.用template把内容拿好
4.如何关联进来?使用自定义指令 
5.使用@contentChild获取此投影的模板
 
 
@Component({
  selector: 'app-example-zippy',
  template: `
  <ng-content></ng-content>
  <div *ngIf="expanded" [id]="contentId">
      <ng-container [ngTemplateOutlet]="content.templateRef"></ng-container>
  </div>
  `,
})
export class ZippyComponent {
  contentId = `zippy-${nextId++}`;
  @Input() expanded = false;
  @ContentChild(ZippyContentDirective) content!: ZippyContentDirective;
}
 
 
@Directive({
  selector: '[appExampleZippyContent]',
})
export class ZippyContentDirective {
  constructor(public templateRef: TemplateRef<unknown>) {}
}
 
let nextId = 0;
 
@Directive({
  selector: 'button[appExampleZippyToggle]',
})
export class ZippyToggleDirective {
  @HostBinding('attr.aria-expanded') ariaExpanded = this.zippy.expanded;
  @HostBinding('attr.aria-controls') ariaControls = this.zippy.contentId;
  @HostListener('click') toggleZippy() {
    this.zippy.expanded = !this.zippy.expanded;
  }
  constructor(public zippy: ZippyComponent) {}
}
 
 
//在html上的呈现
 
<app-example-zippy>
  <button appExampleZippyToggle>Is content project cool?</button>
  <ng-template appExampleZippyContent>
    It depends on what you do with it.
  </ng-template>
</app-example-zippy>
 
 
 

ViewChild和ViewChildren

ContentChild和ContentChildren

生命周期

变更检测

组件样式

高级内容

动态组件

分配模块