指令

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

基本的双向绑定

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

  1. # son 组件
  2. import { Component, OnInit, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
  3. @Component({
  4. selector: 'app-son',
  5. template: `
  6. <div>
  7. <button class="btn btn-danger" (click)="dec()" title="smaller">-</button>
  8. <button class="btn btn-primary" (click)="inc()" title="bigger">+</button>
  9. <label [style.font-size.px]="size">FontSize: {{size}}px</label>
  10. </div>
  11. `,
  12. changeDetection: ChangeDetectionStrategy.OnPush, // 这是一种 变更检测 的策略,你...无脑加上就好了
  13. styles: [
  14. ]
  15. })
  16. export class SonComponent implements OnInit {
  17. @Input() size: number | string;
  18. // 想要用双向绑定语法,output变量名就一定是输入属性名加上Change
  19. @Output() sizeChange = new EventEmitter<number>();
  20. constructor() {}
  21. ngOnInit(): void {}
  22. dec() { this.resize(-1); }
  23. inc() { this.resize(+1); }
  24. resize(delta: number) {
  25. this.size = Math.min(40, Math.max(8, +this.size + delta));
  26. this.sizeChange.emit(this.size);
  27. }
  28. }
  29. # father 组件
  30. import { Component, OnInit } from '@angular/core';
  31. @Component({
  32. selector: 'app-father',
  33. template: `
  34. <app-son [(size)] = "fontSizePx"></app-son>
  35. <button class="btn btn-primary" (click) ="getValue()" title="bigger">getValue</button>
  36. `,
  37. styles: [
  38. ]
  39. })
  40. export class FatherComponent implements OnInit {
  41. fontSizePx = 12;
  42. getValue(value:any) {
  43. console.log(this.fontSizePx);
  44. }
  45. constructor() {}
  46. ngOnInit(): void {}
  47. }

表单中的双向绑定

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

  1. # 首先 ng 中实现双向绑定 需要使用一个 叫做 “指令(准确来说 这里要使用的 内置指令)”的东西。ngModel
  2. # 前面就说过 如果一个 moduel 要使用一些 “不属于它的功能”的时候 需要引入 外部的依赖 并且放到
  3. imports 数组中,所以 第一步 需要加入一个FormsModule 这样 ngModel 这个 指令 才能生效
  4. # app.moduel.ts
  5. import { FormsModule } from '@angular/forms'
  6. ++++
  7. @NgModule({
  8. ++++
  9. imports: [
  10. BrowserModule,
  11. AppRoutingModule,
  12. FormsModule // 重点!
  13. ],
  14. ++++
  15. })
  16. export class AppModule { }
  17. # demo.compnents.ts
  18. import { Component, OnInit } from '@angular/core';
  19. @Component({
  20. selector: 'app-family',
  21. template: `
  22. <input [(ngModel)]="name" #ctrl="ngModel" required>
  23. <p>Value: {{ name }}</p>
  24. <p>Valid: {{ ctrl.valid }}</p>
  25. <button (click)="setValue()">Set value</button>
  26. `,
  27. styles: [
  28. ]
  29. })
  30. export class FamilyComponent implements OnInit {
  31. name: string = '';
  32. constructor() { }
  33. ngOnInit(): void {
  34. }
  35. setValue() {
  36. this.name = 'Nancy';
  37. }
  38. }
  39. // 实际上从本质来说 [( ngModel )] 指令是 这个方式的上层封装
  40. <input [(ngModel)]="name" />
  41. 上面这行代码相当于:
  42. <input [value]="name" (input)="name = $event.target.value" />
  43. // 如果说你想在form表单中使用 ngModel请看下面的示例代码
  44. import { Component, OnInit } from '@angular/core';
  45. import {NgForm} from '@angular/forms';
  46. @Component({
  47. selector: 'app-family',
  48. template: `
  49. <form>
  50. <input [(ngModel)]="value" name="name" />
  51. <input [(ngModel)]="value" [ngModelOptions]="{ standalone: true }" />
  52. </form>
  53. `,
  54. styles: [
  55. ]
  56. })
  57. export class FamilyComponent implements OnInit {
  58. name: string = '';
  59. constructor() { }
  60. ngOnInit(): void {
  61. }
  62. }

NgIf

  1. 我们先来看基本的使用 和语法糖 ```typescript import { Component, OnInit } from ‘@angular/core’; import {NgForm} from ‘@angular/forms’;

@Component({ selector: ‘app-family’, template: `

Content to render when condition is true.

  1. <!--语法糖 我们一般怎么写
  2. ng-template是一块内嵌模板 类型 TemplateRef-->
  3. <ng-template [ngIf]="condition">
  4. <div>Content to render when condition is true.</div>
  5. </ng-template>
  6. <!--下面的else语法 注意此处的 elseBlock 并不是某个boolean 而是ng-template
  7. 的一引用-->
  8. <div *ngIf="condition; else elseBlock">condition为真时显示</div>
  9. <ng-template #elseBlock>
  10. <p>condition为假时显示</p>
  11. </ng-template>
  12. <ng-template #thenBlock>condition为true时显示</ng-template>

`, styles: [ ] }) export class FamilyComponent implements OnInit {

condition = true;

constructor() { }

ngOnInit(): void { }

}

  1. 上面就是 一种 隐式 的使用templateRef ,下面演示怎么用组件中的变量TemplateRef类型 [请看文档](https://angular.cn/api/core/TemplateRef)
  2. ```typescript
  3. import { Component, OnInit, ChangeDetectionStrategy,
  4. ViewChild, TemplateRef, AfterViewInit } from '@angular/core';
  5. @Component({
  6. selector: 'app-family',
  7. template: `
  8. <button class="btn btn-primary" (click)="condition = !condition">toggle block</button>
  9. <p *ngIf="condition else elseBlocks">{{ condition }} === true 时显示</p>
  10. <ng-template #firstTpl>
  11. <p>{{ condition }} === false 时显示</p>
  12. </ng-template>
  13. `,
  14. styles: [
  15. ]
  16. })
  17. export class FamilyComponent implements OnInit {
  18. elseBlocks: TemplateRef<any> = null;
  19. @ViewChild('firstTpl', {static: true}) primaryBlock: TemplateRef<any> = null;
  20. // 先不要管这个语法 你照着写就是了,后续会慢慢的详细的介绍
  21. condition = false;
  22. constructor() { }
  23. ngOnInit(): void {
  24. console.log('ngOnInit', this.primaryBlock);
  25. this.elseBlocks = this.primaryBlock;
  26. }
  27. }

NgSwitch

  1. import { Component, OnInit, ChangeDetectionStrategy, ViewChild, TemplateRef, AfterViewInit } from '@angular/core';
  2. @Component({
  3. selector: 'app-family',
  4. template: `
  5. <p>
  6. <input
  7. type="radio"
  8. name="fruit"
  9. value="apple"
  10. id="apple"
  11. [(ngModel)]="fruit" />
  12. <label for="apple">🍎</label>
  13. </p>
  14. <p>
  15. <input
  16. type="radio"
  17. name="fruit"
  18. value="pear"
  19. id="pear"
  20. [(ngModel)]="fruit" />
  21. <label for="pear">🍐</label>
  22. </p>
  23. <p>
  24. <input
  25. type="radio"
  26. name="fruit"
  27. value="grape"
  28. id="grape"
  29. [(ngModel)]="fruit" />
  30. <label for="grape">🍇</label>
  31. </p>
  32. <p>
  33. <input
  34. type="radio"
  35. name="fruit"
  36. value="other"
  37. id="other"
  38. [(ngModel)]="fruit" />
  39. <label for="other">other</label>
  40. </p>
  41. selected fruit: {{ fruit }}
  42. <div class="content" [ngSwitch]="fruit">
  43. <p *ngSwitchCase="'apple'">这是 苹果</p>
  44. <p *ngSwitchCase="'pear'">这是 梨</p>
  45. <p *ngSwitchCase="'grape'">这是 葡萄</p>
  46. <p *ngSwitchDefault>啥都不是</p>
  47. </div>
  48. `,
  49. styles: [
  50. ]
  51. })
  52. export class FamilyComponent implements OnInit {
  53. fruit = '';
  54. condition = false;
  55. constructor() { }
  56. ngOnInit(): void {
  57. }
  58. }

NgFor

这个NgFor ,

最基础的使用

  1. import { Component, OnInit } from '@angular/core';
  2. const Heros = [
  3. {
  4. id: 'hero_0',
  5. name: '盖伦'
  6. },
  7. {
  8. id: 'hero_1',
  9. name: '赵信'
  10. },
  11. {
  12. id: 'hero_2',
  13. name: '嘉文'
  14. },
  15. {
  16. id: 'hero_3',
  17. name: '易大师'
  18. },
  19. {
  20. id: 'hero_3',
  21. name: '泰达米尔'
  22. }
  23. ];
  24. interface Hero {
  25. id: string;
  26. name: string;
  27. }
  28. @Component({
  29. selector: 'app-family',
  30. template: `
  31. <p>
  32. add hero:
  33. <button class="btn btn-info" (click)="reset()">reset</button>
  34. </p>
  35. <ul>
  36. <li
  37. *ngFor="let item of heros; trackBy: trackByHero"
  38. [style.color]="item.id === 'hero_2' ? 'orange' : '#333'">{{ item.name }}</li>
  39. </ul>
  40. `,
  41. styles: [
  42. ]
  43. })
  44. export class FamilyComponent implements OnInit {
  45. heros: Hero[] = Heros;
  46. constructor() { }
  47. ngOnInit(): void {
  48. }
  49. reset() {
  50. this.heros = [
  51. {
  52. id: 'hero_4',
  53. name: '盖伦4'
  54. },
  55. {
  56. id: 'hero_5',
  57. name: '赵信5'
  58. },
  59. {
  60. id: 'hero_2',
  61. name: '嘉文'
  62. },
  63. {
  64. id: 'hero_6',
  65. name: '易大师6'
  66. },
  67. {
  68. id: 'hero_7',
  69. name: '泰达米尔7'
  70. }
  71. ];
  72. }
  73. // trackBy接收一个函数,返回 NgFor 应该跟踪的值(比如id),
  74. // 这样刷新列表时,id相同的dom不会触发更新
  75. trackByHero(hero: Hero): string {
  76. return hero.id;
  77. }
  78. }

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

参数 含义
$implicit: T: 迭代目标(绑定到ngForOf)中每个条目的值。
ngForOf: NgIterable 迭代表达式的值。当表达式不局限于访问某个属性时,这会非常有用,比如在使用 async 管道时(userStreams | async)
index: number 可迭代对象中当前条目的索引。
count:number 可迭代对象的长度。
first: boolean 如果当前条目是可迭代对象中的第一个条目则为 true。
last: boolean 如果当前条目是可迭代对象中的最后一个条目则为 true。
even: boolean 如果当前条目在可迭代对象中的索引号为偶数则为 true。
odd: boolean 如果当前条目在可迭代对象中的索引号为奇数则为 true。
  1. import {Component} from '@angular/core';
  2. @Component({
  3. selector: 'app-switch',
  4. template: `
  5. <ul>
  6. <li
  7. *ngFor="
  8. let item of heros;
  9. index as i
  10. count as len;
  11. let ev = even;
  12. let od = odd;
  13. let f = first;
  14. let l = last
  15. trackBy: trackByHero"
  16. [class.even]="ev"
  17. [class.odd]="od">
  18. <p>first: {{ f }} -- last: {{ l }}</p>
  19. <p>name: {{ item.name }}</p>
  20. <p>length: {{ len }}</p>
  21. <p>index: {{ i }}</p>
  22. <hr />
  23. </li>
  24. </ul>
  25. `,
  26. styles: [`
  27. .even {
  28. color: #82fa54;
  29. }
  30. .odd {
  31. color: #698efa;
  32. }
  33. `]
  34. })
  35. export class SwitchComponent {
  36. heros: Hero[] = Heros;
  37. trackByHero(hero: Hero): string {
  38. return hero.id;
  39. }
  40. }

模板引用变量

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

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

1.如果是组件上声明 # 就是 获取组件实例
2.如果是HTML上声明 # 就是 获取引用元素
3.如果是上声明 # 就是 TemplatRef
4.如果#左边指定了一个名字,就是引用元素上具备这个名字的指令/组件

  1. #var="ngModel"
  2. 如果是TemplateRef 你可以这样获得它
  3. @ViewChild('firstTpl', {static: true}) primaryBlock: TemplateRef<any> = null;
  • 它可以和NgForm结合 ```typescript

{{ submitMessage }}

// itemForm出现了三次 // 如果没有ngForm的话,那么这个itemForm就是form这个DOM 如果有了就是这个 NgFrom指令的引用了 // 有了这个引用就能拿它做很多NgFrom上的事情了 ,比如valid

  1. - 你应该还需呀小心它的作用域
  2. ```typescript
  3. // 总的来说,如果你写成这样
  4. <input *ngIf="true" #ref2 type="text" [(ngModel)]="secondExample" />
  5. <span>Value: {{ ref2?.value }}</span> <!-- doesn't work -->
  6. // 或则这样那么就上有问题的!
  7. <ng-container *ngFor="let i of [1,2]">
  8. <input #ref type="text" [value]="i" />
  9. </ng-container>
  10. {{ ref.value }}
  11. // 第一个上ref2不一定能获取到你的ref2 因为它前面有一个*ngIf
  12. // 第二个 由于*ngForm把 这个ng-conotaiern实例化了两次,导致不知道到底是哪一个

模板运算符

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

管道Pipe基础使用

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

管道参数和串联

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

自定义管道

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

  1. // 自定义管道非常的简单
  2. @Pipe 装饰器应用到这个类上。就好了,比如下面👇 (主要是实现PipeTransform 接口和重写
  3. transform接口
  4. import { Pipe, PipeTransform } from '@angular/core';
  5. @Pipe({name: 'exponentialStrength'})
  6. export class ExponentialStrengthPipe implements PipeTransform {
  7. transform(value: number, exponent = 1): number {
  8. return Math.pow(value, exponent);
  9. }
  10. }
  11. // 使用
  12. <p>Super power boost: {{2 | exponentialStrength: 10}}</p>

关于管道的变更检测

  1. import { Component } from '@angular/core';
  2. @Component({
  3. selector: 'app-power-boost-calculator',
  4. template: `
  5. <h2>Power Boost Calculator</h2>
  6. <label for="power-input">Normal power: </label>
  7. <input id="power-input" type="text" [(ngModel)]="power">
  8. <label for="boost-input">Boost factor: </label>
  9. <input id="boost-input" type="text" [(ngModel)]="factor">
  10. <p>
  11. Super Hero Power: {{power | exponentialStrength: factor}}
  12. </p>
  13. `,
  14. styles: ['input {margin: .5rem 0;}']
  15. })
  16. export class PowerBoostCalculatorComponent {
  17. power = 5;
  18. factor = 1;
  19. }
  20. // 从上面的例子我们可以知道,如果管道有绑定,那么意味着,如果值变了 那么管道就会重新执行
  21. // 但是它是这么做到的,如果我不希望它变怎么半? 对于管道来说默认的情况下都是:”纯的pure“默
  22. // 人只能做值检测和引用检测,dep检测没有处理 也就是说push数组不会触发更新,如果你需要
  23. // 怎么做的时候请使用 [...] , or {...}
  24. // 如果我说有副作用的管道怎么办?官方知道是把 参数pure变成fasle就好了,譬如下面
  25. // 基础类 (默认纯的)
  26. import { Pipe, PipeTransform } from '@angular/core';
  27. import { Hero } from './heroes';
  28. @Pipe({ name: 'flyingHeroes' })
  29. export class FlyingHeroesPipe implements PipeTransform {
  30. transform(allHeroes: Hero[]) {
  31. return allHeroes.filter(hero => hero.canFly);
  32. }
  33. }
  34. // 派生的非纯的
  35. @Pipe({
  36. name: 'flyingHeroesImpure',
  37. pure: false
  38. })
  39. export class FlyingHeroesImpurePipe extends FlyingHeroesPipe {}
  40. // 如果你的管道依赖的值说 observe的,那么需要注意了 ,你需要做的事情是 使用 async 内置管道
  41. // 处理数据
  42. import { Component } from '@angular/core';
  43. import { Observable, interval } from 'rxjs';
  44. import { map, take } from 'rxjs/operators';
  45. @Component({
  46. selector: 'app-hero-async-message',
  47. template: `
  48. <h2>Async Hero Message and AsyncPipe</h2>
  49. <p>Message: {{ message$ | async }}</p>
  50. <button (click)="resend()">Resend</button>`,
  51. })
  52. export class HeroAsyncMessageComponent {
  53. message$: Observable<string>;
  54. private messages = [
  55. 'You are my hero!',
  56. 'You are the best hero!',
  57. 'Will you be my hero?'
  58. ];
  59. constructor() {
  60. this.message$ = this.getResendObservable();
  61. }
  62. resend() {
  63. this.message$ = this.getResendObservable();
  64. }
  65. private getResendObservable() {
  66. return interval(500).pipe(
  67. map(i => this.messages[i]),
  68. take(this.messages.length)
  69. );
  70. }
  71. }

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

  1. // 使用非纯管道可以缓存http数据
  2. // 每当组件运行变更检测时就会调用非纯管道,这可能每隔几毫秒就运行一次。为避免出现性能问题,
  3. //只有当请求的 URL 发生变化时才会调用该服务器(如下例所示)
  4. import { HttpClient } from '@angular/common/http';
  5. import { Pipe, PipeTransform } from '@angular/core';
  6. @Pipe({
  7. name: 'fetch',
  8. pure: false
  9. })
  10. export class FetchJsonPipe implements PipeTransform {
  11. private cachedData: any = null;
  12. private cachedUrl = '';
  13. constructor(private http: HttpClient) { }
  14. transform(url: string): any {
  15. if (url !== this.cachedUrl) {
  16. this.cachedData = null;
  17. this.cachedUrl = url;
  18. this.http.get(url).subscribe(result => this.cachedData = result);
  19. }
  20. return this.cachedData;
  21. }
  22. }
  23. // 使用
  24. {
  25. template: `
  26. <h2>Heroes from JSON File</h2>
  27. <div *ngFor="let hero of ('assets/heroes.json' | fetch) ">
  28. {{hero.name}}
  29. </div>
  30. <p>Heroes as JSON:
  31. {{'assets/heroes.json' | fetch | json}}
  32. </p>
  33. `
  34. }

属性型指令

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

最简单的使用

  1. // 使用非常的容易
  2. import {Directive, ElementRef, EventEmitter, HostListener, Input, Output} from '@angular/core';
  3. @Directive({
  4. selector: '[appHighlight]'
  5. })
  6. export class HighlightDirective {
  7. @Input('appHighlight') highlightColor: string; // @HostListener 能够替你进行事件监听
  8. @Output() colorChange = new EventEmitter<string>();
  9. constructor(private el: ElementRef) {
  10. console.log('appHighlight');
  11. }
  12. @HostListener('mouseenter') onMouseEnter() {
  13. this.highlight(this.highlightColor || 'yellow');
  14. }
  15. @HostListener('mouseleave') onMouseLeave() {
  16. this.highlight('');
  17. }
  18. private highlight(color: string) {
  19. this.el.nativeElement.style.backgroundColor = color;
  20. this.colorChange.emit(color);
  21. }
  22. }
  23. //
  24. <p [appHighlight]="color">Highlight me!</p>

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

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

结构型指令

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

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

TemplateRef和ViewContainerRef


前者表示 一个内嵌的模版,它的createEmbeddedVIew可以把它自己附着到一个父试图上
后者表示 可以将一个/多个 视图附着到组件中的容器,在createComponent的时候

  1. import {AfterViewInit, Component, EmbeddedViewRef, OnInit, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
  2. @Component({
  3. selector: 'app-tpl-container',
  4. templateUrl: './tpl-container.component.html'
  5. })
  6. export class TplContainerComponent implements OnInit, AfterViewInit {
  7. // 获取模板中的元素(组件、ng-template、dom)
  8. @ViewChild('box') readonly boxEl: ElementRef;
  9. @ViewChild('firstTpl', { read: TemplateRef }) readonly firstTpl: TemplateRef<any>;
  10. @ViewChild('secondTpl', { read: TemplateRef }) readonly secondTpl: TemplateRef<any>;
  11. @ViewChild('thirdTpl', { read: TemplateRef }) readonly thirdTpl: TemplateRef<any>;
  12. @ViewChild('fourthTpl', { read: TemplateRef }) readonly fourTpl: TemplateRef<any>;
  13. @ViewChild('freeTpl', { read: TemplateRef }) readonly freeTpl: TemplateRef<any>;
  14. @ViewChild('firstContainer', { read: ViewContainerRef, static: true }) readonly firstContain: ViewContainerRef;
  15. @ViewChild('secondContainer', { read: ViewContainerRef, static: true }) readonly secondContain: ViewContainerRef;
  16. private freeViewRef: EmbeddedViewRef<any>;
  17. constructor() {
  18. // console.log('constructor');
  19. }
  20. insert(tpl: TemplateRef<any>) {
  21. this.firstContain.insert(tpl.createEmbeddedView(null));
  22. }
  23. insertAll() {
  24. [this.secondTpl, this.thirdTpl, this.fourTpl].forEach(tpl => {
  25. this.firstContain.insert(tpl.createEmbeddedView(null));
  26. });
  27. }
  28. getOne() {
  29. console.log(this.firstContain.get(2));
  30. console.log(this.firstContain.indexOf(this.freeViewRef));
  31. }
  32. insertFree() {
  33. this.firstContain.insert(this.freeViewRef, 1);
  34. }
  35. move() {
  36. // 不需要事先插入也可以移动(定好位置再插入)
  37. this.firstContain.move(this.freeViewRef, 2);
  38. }
  39. move2To4() {
  40. const view = this.firstContain.detach(1);
  41. this.firstContain.insert(view, 3);
  42. }
  43. move2ToOther() {
  44. const view = this.firstContain.detach(1);
  45. this.secondContain.insert(view);
  46. }
  47. ngOnInit(): void {
  48. // console.log('onInit');
  49. }
  50. ngAfterViewInit(): void {
  51. console.log('ngAfterViewInit');
  52. this.freeViewRef = this.freeTpl.createEmbeddedView({ $implicit: 'defaultValue', free: 'aa' });
  53. // console.log(this.firstTpl);
  54. // const viewRef = this.firstTpl.createEmbeddedView(null);
  55. // console.log('viewRef', viewRef);
  56. /*this.boxEl.nativeElement.appendChild(viewRef.rootNodes[0]);*/
  57. setTimeout(() => {
  58. this.firstContain.createEmbeddedView(this.firstTpl);
  59. }, 0);
  60. }
  61. }
  1. <div class="box" #box>
  2. <button class="btn btn-primary mr-1" (click)="insert(secondTpl)">insert second</button>
  3. <button class="btn btn-primary mr-1" (click)="insert(thirdTpl)">insert third</button>
  4. <button class="btn btn-primary mr-1" (click)="insert(fourthTpl)">insert fourth</button>
  5. <button class="btn btn-primary mr-1" (click)="insertAll()">insert all</button>
  6. <button class="btn btn-secondary mr-1" (click)="insertFree()">insert free</button>
  7. <button class="btn btn-info mr-1" (click)="getOne()">get one</button>
  8. <button class="btn btn-success mr-1" (click)="move()">move free</button>
  9. <button class="btn btn-success mr-1" (click)="move2To4()">把第二个移动到第四个位置上</button>
  10. <button class="btn btn-success" (click)="move2ToOther()">把第二个移动到其他容器中</button>
  11. <p>长度:{{ firstContain?.length }}</p>
  12. </div>
  13. <ng-template #firstTpl>
  14. <p>first tpl content</p>
  15. </ng-template>
  16. <ng-template #secondTpl>
  17. <p>第二段template</p>
  18. </ng-template>
  19. <ng-template #thirdTpl>
  20. <p>第三段template</p>
  21. </ng-template>
  22. <ng-template #fourthTpl>
  23. <p>第四段template</p>
  24. </ng-template>
  25. <ng-template #freeTpl>
  26. <p>自由的template</p>
  27. </ng-template>
  28. <p>
  29. first container:
  30. <ng-container #firstContainer></ng-container>
  31. </p>
  32. <hr>
  33. <p>
  34. second container:
  35. <ng-container #secondContainer></ng-container>
  36. </p>


NgTemplateOutlet指令

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

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

调用TplOutletComponent传入自定义的dom

  1. import { Component, Input } from '@angular/core';
  2. @Component({
  3. selector: 'app-root',
  4. template: `<app-tpl-outlet [render]="render"></app-tpl-outlet>
  5. <ng-template #render let-value="value">
  6. <p><b>自定义的dom -- {{ value }}</b></p>
  7. </ng-template>
  8. `
  9. })
  10. export class ItemDetailComponent {
  11. }

组件投影

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

  1. // 最基础使用 单投影
  2. @Component({
  3. selector: 'app-zippy-basic',
  4. template: `
  5. <h2>Single-slot content projection</h2>
  6. <ng-content></ng-content>
  7. `
  8. })
  9. export class ZippyBasicComponent {}
  10. <app-zippy-basic>
  11. <p>Is content projection cool?</p> // 这个将会显示在ng-content
  12. </app-zippy-basic>
  13. // 多插槽 投影
  14. @Component({
  15. selector: 'app-zippy-multislot',
  16. template: `
  17. <h2>Multi-slot content projection</h2>
  18. Default:
  19. <ng-content></ng-content>
  20. Question:
  21. <ng-content select="[question]"></ng-content>
  22. `
  23. })
  24. export class ZippyMultislotComponent {}
  25. // 使用 question 属性的内容将投影到带有 select=[question] 属性的 <ng-content> 元素。
  26. <app-zippy-multislot>
  27. <p question>
  28. Is content projection cool?
  29. </p>
  30. <p>Let's learn about content projection!</p>
  31. </app-zippy-multislot>
  32. // 带条件的 这个比较的复杂
  33. 1.在continer中接受ng-template
  34. 2.给container包裹一层判断(既你自己的判断逻辑)
  35. 3.用template把内容拿好
  36. 4.如何关联进来?使用自定义指令
  37. 5.使用@contentChild获取此投影的模板
  38. @Component({
  39. selector: 'app-example-zippy',
  40. template: `
  41. <ng-content></ng-content>
  42. <div *ngIf="expanded" [id]="contentId">
  43. <ng-container [ngTemplateOutlet]="content.templateRef"></ng-container>
  44. </div>
  45. `,
  46. })
  47. export class ZippyComponent {
  48. contentId = `zippy-${nextId++}`;
  49. @Input() expanded = false;
  50. @ContentChild(ZippyContentDirective) content!: ZippyContentDirective;
  51. }
  52. @Directive({
  53. selector: '[appExampleZippyContent]',
  54. })
  55. export class ZippyContentDirective {
  56. constructor(public templateRef: TemplateRef<unknown>) {}
  57. }
  58. let nextId = 0;
  59. @Directive({
  60. selector: 'button[appExampleZippyToggle]',
  61. })
  62. export class ZippyToggleDirective {
  63. @HostBinding('attr.aria-expanded') ariaExpanded = this.zippy.expanded;
  64. @HostBinding('attr.aria-controls') ariaControls = this.zippy.contentId;
  65. @HostListener('click') toggleZippy() {
  66. this.zippy.expanded = !this.zippy.expanded;
  67. }
  68. constructor(public zippy: ZippyComponent) {}
  69. }
  70. //在html上的呈现
  71. <app-example-zippy>
  72. <button appExampleZippyToggle>Is content project cool?</button>
  73. <ng-template appExampleZippyContent>
  74. It depends on what you do with it.
  75. </ng-template>
  76. </app-example-zippy>

ViewChild和ViewChildren

ContentChild和ContentChildren

生命周期

变更检测

组件样式

高级内容

动态组件

分配模块