Angular Ivy,文本节点如何指向其 ng 模板

Posted

技术标签:

【中文标题】Angular Ivy,文本节点如何指向其 ng 模板【英文标题】:Angular Ivy, how a text node points to its ng-template 【发布时间】:2021-05-23 19:55:07 【问题描述】:

我需要在测试中找到tpl1 的文本节点在渲染后属于ng-template

1
<ng-template tpl>
  tpl1
</ng-template>
2

如果在渲染后找到tpl1的debugNode,那么它的父节点指向component的节点,但是我要确保tpl1的debugNode来源于ng-template

到目前为止,我在 经典 模式下能够做到这一点,但在 ivy 中无法找到做到这一点的方法

经典(作品)中 stackblitz 的链接:https://stackblitz.com/edit/github-5wg14l-b39rck?file=src%2Ftest.spec.ts

下面有一个小例子,任何解决方案(不仅使用 elDef 和 tNode)都对我有用。

import Component, DebugNode, Directive, TemplateRef, VERSION, ViewChild, ViewContainerRef from '@angular/core';
import TestBed from "@angular/core/testing";

@Directive(
  selector: '[tpl]',
)
class TplDirective 
  public constructor(public readonly tpl: TemplateRef<any>) 


@Component(
  selector: 'target',
  template: `
    1
    <ng-template tpl>
      tpl1
    </ng-template>
    2
  `,
)
class TargetComponent 
  @ViewChild(TplDirective, 
    read: TemplateRef,
  ) public readonly tpl?: TemplateRef<any>;
  @ViewChild(TplDirective, 
    read: ViewContainerRef,
  ) public readonly vcr?: ViewContainerRef;


describe('issue-289', () => 
  beforeEach(() => TestBed.configureTestingModule(
    declarations: [TplDirective, TargetComponent],
  ).compileComponents());

  fit('finds right parent in ivy', () => 
    const fixture = TestBed.createComponent(TargetComponent);
    fixture.detectChanges();
    const componentEl = fixture.debugElement;
    const component: TargetComponent = componentEl.componentInstance;

    // check that only defaults have been rendered
    expect (componentEl.childNodes.length).toEqual(3);
    const [txtEl1, tplEl, txtEl2] = componentEl.childNodes;
    expect(txtEl1.nativeNode.nodeName).toEqual('#text');
    expect(tplEl.nativeNode.nodeName).toEqual('#comment');
    expect(txtEl2.nativeNode.nodeName).toEqual('#text');

    // rendering the template
    component.vcr.createEmbeddedView(component.tpl);
    fixture.detectChanges();

    // looking for the new element
    expect (componentEl.childNodes.length).toEqual(4);
    const txtTplEl = componentEl.childNodes.find(el => el !== txtEl1 && el !== tplEl && el !== txtEl2);
    expect(txtTplEl.nativeNode.nodeName).toEqual('#text');

    // getting internal node, 1st is classic, 2nd is ivy
    const tplElNode = (tplEl.injector as any).elDef || (tplEl.injector as any)._tNode;
    expect(tplElNode).toBeDefined();

    // FIXME find how txtTplEl points to tplEl
    // in classic (not ivy) it works like that
    const txtTplNode = (txtTplEl as any)._debugContext?.view?.parentNodeDef;

    // should succeed
    expect(txtTplNode).toEqual(tplElNode);
  );
);

【问题讨论】:

【参考方案1】:

这样检查怎么样

const view = component.vcr.createEmbeddedView(component.tpl);
       /\
       ||
   assign to variable

expect(view.rootNodes.includes(txtTplEl.nativeNode)).toBeTruthy();

它不涉及私有 API,应该在两个版本中都可以使用。

Forked Stackblitz

【讨论】:

是的,这就是我现在登陆的地方,提取vcr,然后是vr,然后是rootNodes,但目前我发现了一个问题,第一个嵌套模板&lt;ng-template tpl&gt;tpl1&lt;ng-template tpl&gt;tpl2&lt;/ng-template&gt;&lt;/ng-template&gt; vr.rootNodes 两者都有,除此之外,任何对 injector.get(VCR) 的调用都会创建一个 #comment 节点而不是抛出错误,如果它之前没有创建过(例如在 div 上)。 github.com/satanTime/ng-mocks/blob/issues/289/libs/ng-mocks/src/…。但到目前为止,它是最好的。 你能用嵌套模板重新创建案例吗?

以上是关于Angular Ivy,文本节点如何指向其 ng 模板的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不应该发布用 Ivy 编译的库?

如何使用 Angular Material 构建 Ivy?

Angular:仅在 ng build --prod 上构建 ES2015

Angular 8 with Ivy 如何验证它的使用

如何使用 Angular CLI + ng-packagr + state mgmt 构建 Angular 4 组件库?

Angular零星知识点2