angular动态组件与内容投影二

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zgrbsbf/article/details/81838533

不是我的原创,也不是不是我的原创,是在理解原文的基础上修改而来的,还对某些api进行了升级

可以使用容器的createComponent方法来创建组件,但是这个方法还可以有甚多别的参数,比如传递服务参数和内容投影参数,这次就学习下。

如我们有一个组件AdvComponent,希望当鼠标放上的时候动态创建一个组件TooltipComponent,TooltipComponent中还包括一个内容投影的部分即组件ForTooltipComponent。

AdvComponent组件

@Component({
  selector: 'app-root',
  template: `
    <div class="container">
      <p tooltipp [tooltipInput]="component">来来来</p>
    </div>
  `
})
export class AdvComponent {
  component = ForTooltipComponent;
}

为了实现鼠标放上之后的效果,添加了一个指令:tooltip,指令上有一个输入属性:tooltipInput,把ForTooltipComponent组件传递了过去,ForTooltipComponent是这样子的:

@Component({
  selector: 'for',
  template: 'for--{{content}}'
})
export class ForTooltipComponent implements OnInit, OnChanges {
  content = Math.random();
  constructor(public cd: ChangeDetectorRef) { //ChangeDetectorRef很有用
  }
}

ChangeDetectorRef很有用,先不解释。

TooltipDirective指令

@Directive({
  selector: '[tooltipp]'
})
export class TooltipDirective implements OnDestroy {
  @Input('tooltipInput') content: any;

  private componentRef: ComponentRef<TooltipComponent>;

  constructor(private element: ElementRef,
              private renderer: Renderer2,
              private injector: Injector,
              private resolver: ComponentFactoryResolver,
              private vcr: ViewContainerRef,
  ) {}
  。。。
}

在指令中给元素添加事件,就用到了HostListener了,我们还要在这个回调函数中动态创建一个组件:

@HostListener('mouseenter')
  mouseenter() {
    const factory = this.resolver.resolveComponentFactory(TooltipComponent);
    const injector = Injector.create([
      {
        provide: 'tooltipConfig',
        useValue: {
          random: Math.random()  // p元素
        }
      }
    ]);
    this.componentRef = this.vcr.createComponent(factory, undefined, injector, this.getContentProjection());
  }

其中最关键的就是createComponent方法的调用了,这次传递了四个参数,从官网文档中可以查看这个方法的参数说明https://angular.io/api/core/ViewContainerRef#createComponent
1. 第一个参数是组件工厂,和上一篇用法一样
2. 第二个参数是index,指明新建的组件的在容器中的index
3. 第三个参数定义了新创建的组件的一个服务,这里的话就给传递了一个随机数而已
4. 第四个参数是新创建的组件里面的内容投影

TooltipComponent组件

这个组件要可以接收内容投射,

@Component({
  template: `
    <div>
      服务来的random:{{random}},,,
      后面是投影:
      <ng-content></ng-content>
    </div>
  `,
})
export class TooltipComponent implements OnInit, AfterViewInit {
  random: '';
  constructor(@Inject('tooltipConfig') private config) {
    // tooltipConfig 是自己配置的Injector
  }

  ngAfterViewInit() {
    setTimeout(() => {
      console.log();
      this.random = this.config.random;
    });
  }
}

对random的赋值放到了AfterViewInit里面的setTimeout里面。其实对于这个场景完全可以放到别的地方。放到这里是为了演示一个error:
当放到AfterViewInit钩子中的时候,如果不放到setTimeout中会报错:

TooltipComponent.html:2 ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.
这个error的原因比较大,一两句话说不清楚,关键我自己也没完全明白,等我明白了再写一篇解释吧。

内容投影部分

这个内容投影想要投一个组件过去,那么这个组件要动态创建。
不过和之前不同的是,并不是直接用容器来创建,而是使用组件工厂的create方法。

扫描二维码关注公众号,回复: 6167129 查看本文章
getContentProjection() {
    const factory = this.resolver.resolveComponentFactory(this.content);
    const componentRef = factory.create(this.injector);
    // 这个地方先省略一行代码   --------1
    // return [[componentRef._viewRef.rootNodes[0]]]
    return [[componentRef.location.nativeElement]];
  }

create方法至少需要一个参数,所以这里传了this.injector过去。也可以根据情况传递一个Injector.create()的返回的injector实例过去。
投影的内容必须是Node类型,不然会报错:TooltipComponent.html:2 ERROR TypeError: Failed to execute ‘appendChild’ on ‘Node’: parameter 1 is not of type ‘Node’.

现在看看效果:
这里写图片描述

是不是很诡异???
ForTooltipComponent的模板中写的明明是for–{{content}},content也赋值成了随机数了啊,为什么没显示出来呢???
其实这涉及到angular中的动态创建组件和变更检测的问题,我还没完全明白,等我明白了再写写,反正解决方法是这样的:在故意省略代码的地方(1)加上:

 (<ForTooltipComponent>componentRef.instance).cd.detectChanges(); // 不然模板中 -- 后面的内容显示不出来

这里写图片描述

参考文献:
https://netbasal.com/create-advanced-components-in-angular-e0655df5dde6

猜你喜欢

转载自blog.csdn.net/zgrbsbf/article/details/81838533