首页 > 解决方案 > Angular Component Dynamic Compile - ng-content with select 不起作用

问题描述

我有一些使用resolveComponentFactory.

我这样做是这样的......

const embeddedComponentElements = this.hostElement.querySelectorAll( selector );

const element = embeddedComponentElements[ 0 ];

// convert NodeList into an array, since Angular doesn't like having a NodeList passed
const projectableNodes = [
  Array.prototype.slice.call(element.childNodes),
];

const factory = componentFactoryResolver.resolveComponentFactory( Component );

const embeddedComponent = factory.create(
 this.injector,
 projectableNodes,
 element,
);

但是我有一个我正在努力解决的问题......

这按预期工作

<div>
 <ng-content></ng-content>
</div>

这不能按预期工作:

<div>
 <!-- I get all the content here -->
 <ng-content select="h1"></ng-content>
</div>
<div>
 <!-- I don't get any content here -->
 <ng-content></ng-content>
</div>

似乎ng-contentselect 属性不适用于动态编译的内容。

有什么想法我做错了什么还是这是一个错误?

如果有帮助,我正在使用 Angular 8。

更新:我在这里的 stackBlitz 中重现了这个问题:https ://stackblitz.com/edit/angular-ivy-givxx6

标签: angularng-content

解决方案


我还没有真正弄清楚 projectable nodes 参数与ng-select.

但是,如果您查看我的stackblitz,您会发现它正在工作.. 有点。

我发现会发生什么:

  1. ng-select颠倒处理(我认为)。这意味着它首先处理模板中的最后一个选择并检查 2d 投影节点数组中的最后一个数组
  2. 当您指定一个选择查询,并且在相应的数组中找不到它时,它只显示数组中的所有项目
  3. 第一个ng-select对应于二维数组中的第一个数组。
  4. 如果您在选择查询中有一个节点,并且还将其添加到下一个数组中,则不会在第一个选择中找到它,并且由于某种原因被添加到下一个选择中。反过来也没什么问题。

因此,如果您将其作为动态 html:

<hello>
  <span class="title">
    Title
  </span>
  <span class="another-title">
    Another title
  </span>
  The rest here of the content......
  <div>Something else here</div>
</hello>

您应该使您的节点如下

const projectableNodes = [
  [ nodes[1] ], // span class="title"
  [ nodes[3] ], // span class="another-title"
  [ nodes[0], nodes[2], nodes[4], nodes[5]]
];

对应这样的模板:

<h1 class="hello-title">
  <ng-content select=".title"></ng-content>
</h1>
<h2 class="hello-title">
  <ng-content select=".another-title"></ng-content>
</h2>
<div class="hello-content">
  <ng-content></ng-content>
</div>

为了解释第 4 点(也许还有第 1 点),如果你这样做:

const projectableNodes = [
  [ nodes[1], nodes[3] ],
  [ nodes[3] ],
  [ nodes[0], nodes[2], nodes[4], nodes[5]]
];

没有问题。如果你反过来做:

const projectableNodes = [
  [ nodes[1] ],
  [ nodes[3], nodes[1] ],
  [ nodes[0], nodes[2], nodes[4], nodes[5]]
];

它永远不会将节点 [1] 投影在正确的位置,但它会以某种方式以<ng-content>“另一个标题”附加到第二个。我找到了这个答案,它有一些赞成,但它并没有让我很清楚这个机制是如何工作的,或者你可以通过什么方式来实现这个动态。

我希望这能给您一些见解,也许您可​​以提出一个动态的解决方案并发布您自己的答案。或者也许这只是您需要的所有信息,我实际上帮助了您:D 谁知道


更多地查看源代码,您可以了解ng-content元素中的数量。这是由factory.ngContentSelectors. 在我的堆栈闪电战中,这是:

0: ".title"
1: ".another-title"
2: "*"

然后,您可以使用该Element.matches()方法选择该内容,并将其余内容传递给通配符。然后,您可以使用以下逻辑使其动态化:

const nodes = Array.from(element.childNodes);
const selectors = this.factory.ngContentSelectors;
const projectableNodes = selectors.map(() => []);
const wildcardIdx = selectors.findIndex((selector) => selector === '*');

nodes.forEach((node) => {
  if (node instanceof Element) {
    // get element node that matches a select which is not the wildcard
    const projectedToIdx = selectors.findIndex(
      (selector, i) => i !== wildcardIdx && node.matches(selector)
    );

    if (projectedToIdx !== -1) {
      // add it to the corresponding projectableNodes and return
      projectableNodes[projectedToIdx].push(node);
      return;
    }
  } 

  if (wildcardIdx > -1) {
    // when there is a wildcard and it's not an Element or it cannot be,
    // matched to the selection up, add it to the global ng-content
    projectableNodes[wildcardIdx].push(node);
  }
});

console.log('projectableNodes', projectableNodes);

const embeddedComponent = this.factory.create(
  this.injector,
  projectableNodes,
  element
);

我想这可以优化,或者这里可能有一些错误,但我希望你能明白。使用这样的逻辑或类似的逻辑,您可以拥有相对动态的组件和动态ng-content


推荐阅读