Angular总结系列之—动态组件

时间:2021-1-8 作者:admin

学习angular吃力篇之一——动态组件

前言

最近公司项目要求用angular+typescript,对于在这两方面零基础的我也只好硬着头皮学呀!都知道angular在国内不太火,所以参考的资料也很少,学习的时候经常遇到一些问题。今天就总结一下让我不太能理解的动态组件,也方便自己以后查阅和复习。文章中如有不足之处,希望大家多多指出。

我理解的动态组件

一个页面中某一区域的内容不是固定不变的。有时候需要根据我们的一些条件,动态的展示里面的内容。这时候我们可能会写几个子组件,当在不同的条件显示相应的子组件内容。

实现

理一下思路

  • 我们需要定义一个锚点,将我们的组件插入到固定位置。
  • 将我们准备的组件解析成具体的组件实例
  • 需要将这个组件添加到之前的“锚点”模板中

看看angular中如何实现

前提知识(这里有好多个xxxxxref)

  • ViewContainerRef 官方文档

    作用:可以将一个或者多个视图附着到组件中的容器中。(创建视图和管理视图)

  • ApplicationRef 官方文档

    作用:包含对根视图的引用。他的tick()方法来全局性调用变化检测。他的attachView()方法将视图包含到变化检测中,他的detachView()方法将视图移除变化检测。

  • ComponentRef 官方文档

    作用:表示由componentFactory创建的组件。提供对组件实例和相关对象的访问。并提供销毁实例的方法。

  • TemplateRef 官方文档

    作用:表示一个内嵌模板。它可用于实例化的内嵌视图。要想根据模板实例化内嵌的视图,请使用 ViewContainerRef 的 createEmbeddedView() 方法。

  • ElementRef 官方文档

    作用:对视图中某个原生属性的包装器。ElementRef 的背后是一个可渲染的具体元素。在浏览器中,它通常是一个 DOM 元素。

  • ViewRef 官方文档

    作用:表示一个angular视图,特别是由组件定义的宿主视图。

看看angular中的动态组件实现方式

准备一个指令
import { Directive, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[ad-host]',
})
export class AdDirective {
  constructor(public viewContainerRef: ViewContainerRef) { }
}

[ad-host]指令插入到某个模板上就表示动态组件的内容将会插入到该模板中

创建一系列动态组件

这是一个创建一系列动态组件的类

import { Type } from '@angular/core';
export class AdItem {
  constructor(public component: Type<any>, public data: any) {}
}

用上面的类构造出动态组件实例

 getAds() {
    return [
      new AdItem(HeroProfileComponent, {name: 'Bombasto', bio: 'Brave as they come'}),
      new AdItem(HeroProfileComponent, {name: 'Dr IQ', bio: 'Smart as they come'}),
      new AdItem(HeroJobAdComponent,   {headline: 'Hiring for several positions',
                                        body: 'Submit your resume today!'}),
      new AdItem(HeroJobAdComponent,   {headline: 'Openings in all departments',
                                        body: 'Apply today'}),
    ];
  }
简单的注册表

上面步骤创建好了组件实例之后并不能直接用来使用。
需要用componentFactoryResolver方法提供对该组件实例和相关对象的访问。

 loadComponent() {
    this.currentAdIndex = (this.currentAdIndex + 1) % this.ads.length;
    const adItem = this.ads[this.currentAdIndex];
    const viewContainerRef = this.adHost.viewContainerRef;
    viewContainerRef.clear();

    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(adItem.component);
    const componentRef = viewContainerRef.createComponent(componentFactory);
    (<AdComponent>componentRef.instance).data = adItem.data;
  }
  • 通过viewContainerRef获取到组件视图容器,将里面的内容先清空.
  • 它使用 ComponentFactoryResolver 来为每个具体的组件解析出一个 ComponentFactory。 然后 ComponentFactory 会为每一个组件创建一个实例。
  • 调用 ViewContainerRef 的 createComponent()。实例化一个 Component 并把它的宿主视图插入到本容器的指定 index 处。

我以为我懂了,但是看了同事的代码之后……

  • 同事的代码如下
loadComponent(name: nameForSele, metaData: IObj<any>): IDynamicRes {
        let dyId = this.setProId();//前端生成动态组件id
        Object.assign(metaData, { dyId })
        let selector = SELECTORDATA[name];//选择显示的动态组件
        let target = ComPoolService.getCom(selector); 
        let compFac = this.componentFactoryResolver.resolveComponentFactory(target);//来为每个具体的组件解析出一个 ComponentFactory。 然后 ComponentFactory 会为每一个组件创建一个实例。
        let propArr = ComPoolService.getProp(selector);
        let compRef = compFac.create(this.injector);
        propArr.forEach(item => {
            compRef.instance[item.key] = metaData[item.key];
        })
        this.applicationRef.attachView(compRef.hostView);
        return {
            ele: (compRef.hostView as EmbeddedViewRef<any>).rootNodes[0],
            destory: () => {
                this.applicationRef.detachView(compRef.hostView);
                compRef.destroy();
            },
            type: name,
            dyId
        }
    }

他的思路其实是利用applicationRef,创建element元素。利用attachView()包含到变化检测中。组件销毁的时候利用detachView()将视图移除变化检测

  open(name: nameForSele, metaData: IObj<any>): string {
        let { ele, destory, type, dyId } = this.loadComponent(name, metaData);
        debugger
        document.body.appendChild(ele);
        let obj: any = {
            dyId,
            destory,
            type
        }
        switch (name) {
            case 'contentMenu':
                Object.assign(obj, { active: true })
                break;
            case 'loadCom':
                break
            default:
        }
        this.dynamicData.push(obj);
        return dyId
    }

不利用viewContainerRef,利用applicationRef,动态插入一个组件到指定的DOM节点中

 addDynamicComponent() {
        let factory = this.resolver.resolveComponentFactory(SimpleComponent);

        let newNode = document.createElement('div');
        newNode.id = 'placeholder';
        document.getElementById('container').appendChild(newNode);

        const ref = factory.create(this.injector, [], newNode);
        this.app.attachView(ref.hostView);
    }
声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。