简体   繁体   English

如何创建组合多个模板的 Angular 指令?

[英]How to create an Angular directive that combines multiple templates?

I am trying to create a custom directive that will allow me to read a template from a json / ts object and insert it in my directive template.我正在尝试创建一个自定义指令,允许我从 json / ts object 读取模板并将其插入我的指令模板中。

For example, in a config file / database / ts file, I have:例如,在配置文件/数据库/ts 文件中,我有:

{
//...
  text: '<p>Many of our tools ...AD Groups:</p><AD_GROUPS id="AD_GROUPS"></AD_GROUPS><p>To request access, ...',
  AD_GROUPS: [
    'my-team-users'
  ],
//...
} // truncated and wrapped for readability

I want to take the text(html) from the config file, replace the <AD_GROUPS> tag with the template the programmer passes in, and render it to the page.我想从配置文件中获取文本 (html),将<AD_GROUPS>标记替换为程序员传入的模板,并将其呈现到页面。 The directive is consumed like this:该指令是这样使用的:

<div *appAdGroupText="cfg">
  <mat-list>
    <mat-list-item *ngFor="let group of cfg.AD_GROUPS">{{group}}</mat-list-item>
  </mat-list>
</div>

I have created the directive:我创建了指令:

@Directive({selector: '[appAdGroupText]'})
export class AdGroupTextDirective implements OnChanges {

  _config: ConfigurationItemModel | undefined;

  constructor(
    private templateRef: TemplateRef<unknown>,
    private vcr: ViewContainerRef,
    private renderer: Renderer2
    // private resolver: ComponentFactoryResolver
  ) {
  }

  @Input() set appAdGroupText(cfg: ConfigurationItemModel) {
    this._config = cfg;
    this.render();
  }

  render() {
    const cfg = this._config; // config is set in a set method. This has the correct value
    const groupListDivElement: HTMLDivElement = this.renderer.createElement('my-group-text'); // creates a 'parent' element
    groupListDivElement.innerHTML = cfg.text; // set the inner html to the config text
    const adListDivRef = this.templateRef.createEmbeddedView(null); // create an unattached version of the template

    // loop through, and replace the "AD_GROUPS" custom tag with the generated template
    groupListDivElement.childNodes.forEach(n => {
    if (n.nodeName === 'AD_GROUPS') {
      n.replaceWith(adListDivRef.rootNodes[0]); // this is incorrect. I only get the root node, rather than the entire node with its children
    }
    // arguably, the above could be replaced with an Array.from(...) find / replace
    console.log(groupListDivElement); // SEE HTML OUTPUT
    // How do I render groupListDivElement in the VCR / renderer???


      // this works will put the mat-list ONLYin:
      // let view = this.templateRef.createEmbeddedView(null);
      // this.vcr.insert(view);
  }
}

Generated HTML from the console.log :console.log生成 HTML :

<my-group-text _ngcontent-hwh-c107="">
<p>Many of our tools are restricted to specific groups in Active Directory. In order to access the required tools, you will need access to the following AD Groups:</p>
<div _ngcontent-hwh-c107="">
   <mat-list _ngcontent-hwh-c107="" class="mat-list mat-list-base">
      <!--container-->
   </mat-list>
</div>
<p>
   To request access, <a href="https://access" target="_blank" rel="noopener noreferrer">click here</a> and fill out the form with the following information:
   <ad_form_table></ad_form_table>
</p>
<p>You will need to create a ticket for each of the above groups listed.</p>
</ad-group-text>

Issues问题

  1. How do I get adListDivRef appended to the proper parent?如何将adListDivRef附加到正确的父级? I think I can access the parent thusly: this.renderer.parentNode(this.vcr.element.nativeElement) ?我想我可以这样访问父节点: this.renderer.parentNode(this.vcr.element.nativeElement)
  2. The mat-list does not seem to find the list items from my embedded *ngFor . mat-list似乎没有从我嵌入的*ngFor中找到列表项。 How do I get that?我怎么得到它? I am thinking maybe my directive is called first and the next one is not called?我在想也许我的指令首先被调用而下一个没有被调用?

Stackblitz with minimal code: https://stackblitz.com/edit/angular-ivy-zv16cn代码最少的 Stackblitz: https://stackblitz.com/edit/angular-ivy-zv16cn

Here are two steps to achieve your goal:这里有两个步骤来实现你的目标:

  1. Put generated ag-group-text element into page将生成ag-group-text元素放入页面

    this.vcr.element.nativeElement.parentNode.appendChild(groupListDivElement);
  2. Pass proper context into dynamically generated template将适当的context传递到动态生成的模板中

    const adListDivRef: EmbeddedViewRef<any> = this.vcr.createEmbeddedView(this.templateRef, { cfg });

Note: using ViewContainerRef.createEmbeddedTemplate(templateRef) makes sure that you don't need to worry about change detection in rendered template because change detector becomes a part of Angular change detection tree.注意:使用ViewContainerRef.createEmbeddedTemplate(templateRef)确保您无需担心渲染模板中的变化检测,因为变化检测器成为 Angular 变化检测树的一部分。 In case of templateRef.create you have to handle change detection by yourself如果是templateRef.create你必须自己处理变化检测

Forked Stackblitz分叉的 Stackblitz

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM