关于angular:详解-Angular-动态视图-一-原生-API

5次阅读

共计 5879 个字符,预计需要花费 15 分钟才能阅读完成。

动静渲染视图是日常我的项目开发中常见的需要,特地是通用性的工具库开发。例如弹窗服务,须要让组件的用户决定传入什么内容进行渲染,可能是一个组件,可能是一个模板(ng-template)。这里先来看看通过 Angular API 如何去实现。

要动静的插入一个视图,Angular 提供了一套 API 负责创立容器,实例化组件,以及治理视图和数据。同时,它还额定提供了一些指令,不便疾速的实现。它们各有各自实用场景,本篇具体来看看它们的应用。

残缺的示例代码能够查看 GitHub 我的项目仓库,或者在线查看成果 在线示例。

ViewContainerRef 容器

动静视图的外围是视图容器,它决定了视图的插入地位,用 ViewContainerRef 类示意。要创立它很简略,假如组件视图中有:<ng-container #dynamicHost></ng-container> 通过 @ViewChild 装璜器读取即可:

@ViewChild('dynamicHost', { read: ViewContainerRef}) container!: ViewContainerRef;

这段代码的留神点有:

  • @ViewChild 用来从组件视图中获取元素的援用,能够是 DOM 对象、子组件或者指令的实例、或是某个依赖注入的 Provider。

第二个参数中的 read 批示具体获取哪个类型,例如从 <button mat-button> 能够获取到这个 button 元素对象,也能够是这个标签上增加的 Material 按钮指令。

  • 要获取一个容器,并非只能应用 <ng-container><ng-template>、组件或者别的 DOM 标签都能够。
  • 尽管是叫容器,不过插入的内容可不是在这个 <ng-container> 标签内,而是在它的下方(相似 <router-outlet>)。所以应用 ng-container 是为了不渲染多余的 DOM。

ViewContainerRef 实例能够创立、插入、移除、挪动或是销毁它其中的视图(ViewRef)。视图代表着 Angular 中可显示元素的最小分组单位,它由组件或者模板定义。多个视图结构成了 Angular 利用的视图树。

✨扼要起见,后文都将 ViewContainerRef 实例称之为“视图容器”

以代码形式插入

视图容器有两个办法(createComponent,createEmbeddedView),用来动态创建组件视图和模板视图。

动静插入组件

首先创立一个用于动静插入的组件,这个告诉组件省略了具体款式,详情能够查看源码。能够留神到,这个组件有一个输出属性,一个输入事件。以便演示动态创建的组件,如何和外界交互。

@Component({
    template: `
    <div class="alert-container mat-elevation-z2" [class]="classConfig()">
        <span class="message">{{message}}</span>
        <button mat-button (click)="emitCloseEvent()"> 敞开 </button>
    </div>`
})
export class AlertComponent {@Input() message = '空音讯提醒';
    @Input() type = 'success';
    @Output() closeAlert = new EventEmitter();
    classConfig() {
        return {
            success: this.type === 'success',
            warning: this.type === 'warning'
        };
    }

    const

    emitCloseEvent(): void {this.closeAlert.emit();
    }
}

✨留神:Angular 9 后的版本默认应用 Ivy 编译器,如果是应用老版本编译器,这个须要动静插入的告诉组件,须要在 Module 的 entryComponents 中申明,并且这个 Module 不能懒加载。

告诉组件写好后,就能够创立并动静插入,具体分为三步:

  1. 在构造函数中注入 ComponentFactoryResolver 实例:private resolver: ComponentFactoryResolver
  2. 通过 resolver.resolveComponentFactory(AlertComponent) 办法,生成这个告诉组件的工厂对象。
  3. 最初一步,将这个工厂对象传入视图容器的 createComponent 办法:
    this.const componentRef = container.createComponent(factory)

通过 createComponent 办法,就能够将这个组件插入到视图中了,并返回这个组件实例。

有了组件实例,和这个告诉组件交互也就不成问题了:

  • 输出属性传值:componentRef.instance.message = "内部传入的正告信息"
  • 绑定输入事件:

    componentRef.instance.closeAlert.subscribe(() => {const index = this.container.indexOf(componentRef.hostView);
      this.container.remove(index);
    });

从这个下面的绑定事件也能够看出,视图容器能够包容任意多个视图,依据视图对象能够查问索引,或者销毁,移除,插入,挪动任意视图程序。

动静插入模板

和 createComponent 相似的,通过 createEmbeddedView 就能够插入模板。

首先先创立一个模板示例,这个模板依据上下文对象申明了一个“param”属性:

<ng-template #templateView let-param="message">
    <section class="template-wrapper">
        <span> 来自 ng-template 的动静内容 </span>
        <span>{{param}}</span>
    </section>
</ng-template>

要插入一个带上下文数据的模板,具体分为三步:

  1. 获取模板的援用对象:

    @ViewChild('templateView', { read: TemplateRef}) template!: TemplateRef<any>;
  2. 申明上下文对象:templateContext = {message: '来自模板上下文的值'};
  3. 通过视图容器的 createEmbeddedView 办法插入模板:

    const embeddedViewRef = this.container.createEmbeddedView(this.template, this.templateContext);

办法创立一个 EmbeddedViewRef 对象,并将它放入。通过这个对象,视图容器能够查找它的索引,所以也能够和之前组件视图的援用一样,在容器内挪动程序、移除渲染、或是被销毁。

以指令形式插入

除了应用视图容器的两个办法来创立和插入视图,Angular 还提供了两个指令来简化工作。

ngComponentOutlet

首先,再创立另一个示例组件,和后面的哪个告诉组件不同,它没有输入输出属性,然而多了一个须要注入的依赖项,以便演示带依赖注入的组件插入:

@Component({
    template: `
    <section class="template-wrapper">
        <span> 来自另一个动静组件:{{param.message}}</span>
    </section>`
})
export class AnotherComponent {constructor(public param: ExampleService) {}}

应用指令的形式创立组件就简略多了,只须要两步:

  1. 引入这个组件类,并赋值给一个属性:

    import {AnotherComponent} from '../shared/another-component';
    export class ViewContainerExampleComponent implements OnInit, OnDestroy {public anotherComponent = AnotherComponent;}
  2. 在视图中申明即可:

    <ng-container *ngComponentOutlet="anotherComponent"></ng-container>

传入依赖注入器

失常状况下,这样就把组件插入指定地位了,不过如果动静组件所申明的依赖项,须要由这个组件自身提供呢?

这里就要再给这个组件传入注入对象:
<ng-container *ngComponentOutlet="anotherComponent;injector:costumeInjector">

这个 costumeInjector 能够通过 Injector 类的静态方法创立:

constructor(injector: Injector) {
    this.costumeInjector = Injector.create({providers: [{ provide: ExampleService, deps: [] }], 
        parent: injector 
    });
}

这样一来,每个动态创建的组件,都会领有一个独立的 ExampleService 实例。

✨ 视图容器的 createComponent 办法同样能够指定依赖注入器,成果是一样的,后面只是为了扼要而省略。

当然,常见的状况仍旧是给 ExampleService 的装璜器申明为全局服务:@Injectable({providedIn:'root'})

传入内容映射

除了能够指定注入器,还能够传入内容映射。

先给组件做一点小批改,新增一个 <ng-content> 标记,使得这个组件能够接管内部内容映射:

<section class="template-wrapper">
    <span> 来自另一个动静组件:{{param.message}}</span>
    <ng-content></ng-content>
</section>`

要插入映射的 DOM 内容,只须要额定给指令的表达式再传一个参数:<ng-container *ngComponentOutlet="anotherComponent;content:costumeContent">

这个 costumeContent 是一个数组,因为组件内能够有多个 ng-content。数组内每项也是一个数组,因为每个 ng-content 地位,能够插入多个 DOM 内容块。

const spanContent = document.createElement('span');
const divContent = document.createElement('div');
spanContent.innerHTML = 'hello, world';
divContent.innerHTML = '<span>locotor</span>';
this.costumeContent = [[spanContent, divContent]];

ngTemplateOutlet

模板的指令只有两个输出属性:模板的援用对象、模板的上下文对象。

所以要插入一个带上下文数据的模板,具体步骤如下:

  1. 给模板增加援用名:

    <ng-template #templateView let-param="message">
         <!-- 省略内容 -->
    </ng-template>
  2. 申明上下文对象:templateContext = {message: '来自模板上下文的值'};
  3. 传入 ngTemplateOutlet 指令中:

    <ng-container *ngTemplateOutlet="templateView; context: templateContext"></ng-container>

比照一下

后面介绍了两种插入视图的形式,成果都是相似的,然而也有些许不同之处。理解它们的差别,能力依据场景应用适合的实现形式。

指令和 ViewContainerRef 对象实例的差别次要有两个:

  • 多视图:相比指令的形式来插入视图,通过 ViewContainerRef 的创立办法,在一个视图容器中,能够创立任意多个视图。也因而,通过视图容器还具备对视图的治理能力,例如将某个视图移到容器的顶部,或是销毁某一个视图及其相干数据。
  • 组件实例:通过 createComponent 办法插入组件视图的同时,还能够失去这个组件类的实例。有了它,就能够给组件传参,或是注册它的输入事件。

除了上述差别,其余的中央都是雷同的。比照一下插入组件时,应用指令的形式:

<ng-container *ngComponentOutlet="componentTypeExpression;
                                  injector: injectorExpression;
                                  content: contentNodesExpression;
                                  ngModuleFactory: moduleFactory;">
</ng-container>

能够指定要插入组件类,组件的注入器,映射内容,以及模块工厂对象(容许动静加载其余模块)。

所对应的代码形式插入:

createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][], ngModule?: NgModuleRef<any>): ComponentRef<C>

除了第二个参数是指定插入到容器的程序序号以外,其余的参数都是一一对应的。

和组件的状况相似,模板插入除了视图容器反对多个模板以外,能够反对指定插入序号外,和指令的形式齐全一样的。都是两个参数,一个是模板援用对象,一个是模板上下文对象。

总结

本篇总结了 Angular API 实现动静视图插入的形式。ViewContainerRef 能够反对任意多个视图的插入,对它们进行治理。它插入的组件能够拿到组件实例,可能执行输入输出交互。指令的形式能够便捷的插入视图,然而只能在一个容器内插入一个视图,对组件的输入输出交互反对有余。

能够看到,通过 Angular 原生 API 曾经能够实现动静视图性能,不过如果能联合指令式的便捷,再兼顾组件交互就好了。好在 Material 开发组还提供了一套 Angular CDK(组件开发套件),它的 Portal 模块,封装了原生 API,能够更不便的实现动静视图,咱们下篇见!😎

正文完
 0