Angular 组件动态编译 - select 的 ng-content 不工作
Angular Component Dynamic Compile - ng-content with select not working
我有一些使用 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-content
select 属性似乎不适用于动态编译的内容。
知道我做错了什么或者这是一个错误吗?
如果有帮助,我正在使用 Angular8。
更新:
我在 stackBlitz 中重现了这个问题:https://stackblitz.com/edit/angular-ivy-givxx6
我还没有真正弄清楚与 ng-select
相结合的可投影节点参数。
但是如果你检查我的 stackblitz 你可以看到它在工作..有点.
我发现发生了什么:
把ng-select
倒过来处理(我觉得)。这意味着它首先处理模板中的最后一个 select 并检查二维投影节点数组中的最后一个数组
- 当你指定一个select查询时,它在相应的数组中找不到它,它只是显示数组中的所有项目
- 第一个
ng-select
对应二维数组中的第一个数组
- 如果您有一个节点在 select 查询中,并且还将其添加到下一个数组中,则不会在第一个 select 中找到它并添加到下一个出于某种原因。反过来也没问题。
因此,如果您将其设为动态 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>
和 'another-title'。我发现了这个 ,它有一些赞成票,但它并没有让我很清楚这个机制是如何工作的,或者你可以通过什么方式使它动态化。
我希望这能给您一些启发,也许您可以想出一个动态的解决方案和 post 您自己的答案。或者这可能只是您需要的所有信息,我实际上帮助了您 :D 谁知道
仔细查看源代码,您会发现元素中 ng-content
的数量。这是由 factory.ngContentSelectors
定义的。在我的 stackblitz 中,这是:
0: ".title"
1: ".another-title"
2: "*"
然后您可以使用 Element.matches()
方法 select 该内容,并将其余内容传递给通配符。然后您可以使用以下逻辑使其动态化:
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
的相对动态的组件
谢谢波尔。你的回复对我帮助很大。我写了下面的内容,我的单元测试确认它现在都按预期工作了。
const projectableNodes = this.getNgContent( element, factory.ngContentSelectors );
private getNgContent( element: Element, ngSelectors: string[] ) {
// select the content
const elements = ngSelectors
.map ( s => {
if ( s !== '*' ) {
const els = element.querySelectorAll( s );
els.forEach( e => e.remove() );
return Array.from( els );
}
return s;
});
// where content has not been selected return remaining
return elements.map( s => {
if ( s === '*' ) {
return Array.from( element.childNodes )
}
return s;
});
}
我有一些使用 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-content
select 属性似乎不适用于动态编译的内容。
知道我做错了什么或者这是一个错误吗?
如果有帮助,我正在使用 Angular8。
更新: 我在 stackBlitz 中重现了这个问题:https://stackblitz.com/edit/angular-ivy-givxx6
我还没有真正弄清楚与 ng-select
相结合的可投影节点参数。
但是如果你检查我的 stackblitz 你可以看到它在工作..有点.
我发现发生了什么:
把ng-select
倒过来处理(我觉得)。这意味着它首先处理模板中的最后一个 select 并检查二维投影节点数组中的最后一个数组- 当你指定一个select查询时,它在相应的数组中找不到它,它只是显示数组中的所有项目
- 第一个
ng-select
对应二维数组中的第一个数组 - 如果您有一个节点在 select 查询中,并且还将其添加到下一个数组中,则不会在第一个 select 中找到它并添加到下一个出于某种原因。反过来也没问题。
因此,如果您将其设为动态 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>
和 'another-title'。我发现了这个
我希望这能给您一些启发,也许您可以想出一个动态的解决方案和 post 您自己的答案。或者这可能只是您需要的所有信息,我实际上帮助了您 :D 谁知道
仔细查看源代码,您会发现元素中 ng-content
的数量。这是由 factory.ngContentSelectors
定义的。在我的 stackblitz 中,这是:
0: ".title"
1: ".another-title"
2: "*"
然后您可以使用 Element.matches()
方法 select 该内容,并将其余内容传递给通配符。然后您可以使用以下逻辑使其动态化:
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
谢谢波尔。你的回复对我帮助很大。我写了下面的内容,我的单元测试确认它现在都按预期工作了。
const projectableNodes = this.getNgContent( element, factory.ngContentSelectors );
private getNgContent( element: Element, ngSelectors: string[] ) {
// select the content
const elements = ngSelectors
.map ( s => {
if ( s !== '*' ) {
const els = element.querySelectorAll( s );
els.forEach( e => e.remove() );
return Array.from( els );
}
return s;
});
// where content has not been selected return remaining
return elements.map( s => {
if ( s === '*' ) {
return Array.from( element.childNodes )
}
return s;
});
}