我在 Angular 6 中创建了一个自定义轮播组件。简单的用法是这样的:
<mpx-carousel>
<div *mpxCarouselItem>Slide 1</div>
<div *mpxCarouselItem>Slide 2</div>
<div *mpxCarouselItem>Slide 3</div>
</mpx-carousel>
但是它也支持嵌套,像这样:
<mpx-carousel>
<div *mpxCarouselItem>Slide 1</div>
<div *mpxCarouselItem>
<mpx-carousel>
<div *mpxCarouselItem>Sub-slide A</div>
<div *mpxCarouselItem>Sub-slide B</div>
</mpx-carousel>
</div>
<div *mpxCarouselItem>Slide 3</div>
</mpx-carousel>
在父CarouselComponent
代码,我想确定是否有 child CarouselComponents
,并访问它们的属性。所以我使用@ContentChildren:
@ContentChildren(CarouselComponent, { descendants: true })
childCarousels: QueryList<CarouselComponent>;
ngAfterContentInit() {
console.log(this.childCarousels.length); // 1, BUT it's a reference to itself, not the child
}
在父轮播的 ngAfterContentInit 中,我看到 @ContentChildren 找到了 1 个子 CarouselComponent,这看起来不错。但是仔细检查会发现它实际上找到了这个父轮播本身,而不是它的 child 。为了真正找到子轮播,我必须订阅 childCarousel.changes:
@ContentChildren(CarouselComponent, { descendants: true })
childCarousels: QueryList<CarouselComponent>;
ngAfterContentInit() {
console.log(this.childCarousels.length); // 1, BUT it's a reference to itself, not the child
this.childCarousels.changes.subscribe(() => {
console.log(this.childCarousels.length); // 2, now it includes itself and its child
});
}
所以@ContentChildren 包含父项本身有点奇怪,而且您必须等待 changes
有点奇怪在检测到 child 之前发生事件,但这是一个足够简单的解决方法。
当在另一个组件中发现子轮播时,真正的麻烦就开始了。 CarouselComponent
是项目中许多其他组件使用的共享组件,所以我经常可以像这样使用它......
<!-- parent carousel -->
<mpx-carousel>
<mpx-component-A></mpx-component-A>
<mpx-component-B></mpx-component-B>
</mpx-carousel>
...哪里<mpx-component-A>
是另一个在内部使用自己的 mpx-carousel 的组件。我仍然希望此处的父轮播能够检测到 mpx-component-A View 内使用的轮播。但是当我使用上面的技术时,@ContentChildren 找不到在 ComponentA 中定义的 CarouselComponent 实例。在 ngAfterContentInit 中,childCarousels 再次只包含一个项目,它是对父级本身的引用。在这种情况下,childCarousels.changes 甚至从未触发过,所以我们也没有得到埋藏的 child 旋转木马:
@ContentChildren(CarouselComponent, { descendants: true })
childCarousels: QueryList<CarouselComponent>;
ngAfterContentInit() {
console.log(this.childCarousels.length); // 1, BUT it's a reference to itself, not the child
// The changes event never even fires
this.childCarousels.changes.subscribe(() => {
console.log(this.childCarousels.length); // N/A
});
}
我可以提供 CarouselComponent
的完整代码和 CarouselItemDirective
如果那会有所帮助。但我的问题更笼统:父组件是否有任何方法可以获取对包含在另一个组件 (ComponentA) 中的特定类型 (CarouselComponent) 的后代组件的引用?
我可能完全用@ContentChildren 找错了树,所以我对完全不同的方法持开放态度。
提前致谢!
编辑:
Here is a StackBlitz这让我更清楚我想做什么。注意 carousel.component.ts
中的 console.log() 语句以及显示预期与实际输出的评论。 (注意:在现实世界中,轮播会每隔几秒动画一次旋转它的项目。但为了简单起见,我在这里删除了所有内容)。
最佳答案
这只是一个问题,就我所做的研究而言,没有自动神奇的方法来查询另一个组件内的所有嵌套类型的组件。
ContentChildren:
Does not retrieve elements or directives that are in other components' templates, since a component's template is always a black box to its ancestors.
引自 Angular docs
过去我有类似的用例,我的解决方案是为嵌套的所有组件创建一个通用接口(interface)
export interface CommonQueryInterface {
commonField: QueryList<any>
}
所以逻辑在最顶层(最高父组件)有一个遍历所有子组件的逻辑,这些子组件内部有一个字段commonField
。但我认为这种方法与您想要的结果不同。
因此,在我看来,为了访问任何轮播内的所有轮播组件,可能需要创建一个轮播服务来管理这些轮播元素
该服务可能如下所示:
@Injectable({ providedIn: "root" })
export class CarouselService {
carousells = {};
addCarousel(c: CarouselComponent) {
if (!this.carousells[c.groupName]) {
this.carousells[c.groupName] = [];
}
this.carousells[c.groupName].push(c);
}
getCarousels() {
return this.carousells;
}
clear(c: CarouselComponent) {
if (c.topLevel) {
delete this.carousells[c.groupName];
}
}
}
我的解决方案建议在轮播组件内将轮播元素本身添加到服务中
export class CarouselComponent implements AfterContentInit, OnDestroy {
@ContentChildren(CarouselItemDirective) items: QueryList<
CarouselItemDirective
>;
@ContentChildren(CarouselComponent, { descendants: true })
childCarousels: QueryList<CarouselComponent>;
@Input() name;
@Input() groupName;
@Input() topLevel = false;
@ViewChildren(CarouselComponent) childCarousels2: QueryList<
CarouselComponent
>;
constructor(private cs: CarouselService) {}
getActiveCarousels() {
return this.cs;
}
ngAfterContentInit() {
if (this.name) {
this.cs.addCarousel(this);
}
}
ngOnDestroy(){
this.cs.clear(this)
}
queryCarousells() {
return this.cs.getCarousels();
}
}
这样做之后,您将能够随时访问事件的(屏幕上的)轮播。这种方法的缺点是您需要在 name
@Input
旁边再添加一个 groupName @Input
。这在我的实现中是必需的,以便我们可以在同一 View 中区分不同的轮播组件。
这是 StackBlitz 中概念的基本实现。
此实现的好处是,通过访问所有轮播,您可以在轮播组件本身内添加自定义轮播相关逻辑,并且它将被锁定,远离组件的使用者。 例如,这样的逻辑可能是重启单个轮播组内的所有轮播。
关于Angular:使用@ContentChildren 让 child 进入另一个组件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52653376/