我已经在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
代码中,我想确定是否有子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,这看起来不错。但是仔细检查发现,它实际上是找到了这个父轮播本身,而不是它的孩子。为了真正找到儿童轮播,我必须订阅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
事件,这有点奇怪,但这是一个很容易的解决方法。
真正的麻烦始于在另一个组件内找到儿童轮播。 CarouselComponent
是项目中许多其他组件所使用的共享组件,所以我经常可以有这种用法...
<!-- parent carousel -->
<mpx-carousel>
<mpx-component-A></mpx-component-A>
<mpx-component-B></mpx-component-B>
</mpx-carousel>
...其中<mpx-component-A>
是另一个内部使用其自身的mpx轮播的组件。我仍然希望此处的父轮播能够检测mpx-component-A视图内使用的轮播。但是,当我使用上述技术时,@ ContentChildren找不到在ComponentA中定义的CarouselComponent实例。在ngAfterContentInit中,childCarousels再次仅包含一个项目,这是对父项本身的引用。在这种情况下,childCarousels.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
// 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()语句,以及注释显示了预期输出与实际输出。 (请注意:在现实世界中,轮播会每隔几秒钟对它的项目进行动画处理。但是为简单起见,我在这里删除了所有内容。)
答案 0 :(得分:1)
这是一个虽然,据我的研究,没有自动神奇的方法来查询另一个组件内的所有嵌套类型的组件。
<块引用>内容儿童:
不检索其他组件模板中的元素或指令,因为组件的模板对于其祖先来说始终是一个黑盒子。
引用自 Angular docs
过去我有类似的用例,我的解决方案是为嵌套的所有组件部分创建一个通用接口
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
。这在我的实现中是需要的,以便我们可以在同一视图中区分不同的轮播组件。
这里是 StackBlitz
中概念的基本实现这种实现的好处是,通过访问所有轮播,您可以在轮播组件本身内添加自定义轮播相关逻辑,并且它将被锁定,远离组件的使用者。 例如,这种逻辑可能是重新启动单个轮播组内的所有轮播。
答案 1 :(得分:0)
对不起,我无法想象您要实现什么,但是
如果您有类似的组件
<hello id="myId" name="{{ name }}">
<div #my></div>
<div #my></div>
<other>
<div #my></div>
<div #my></div>
<div #my></div>
</other>
</hello>
其他组件类似
@Component({
selector: 'other',
template: `
<ng-content></ng-content>`
})
export class OtherComponent{}
Hello组件
@Component({
selector: 'hello',
template: `<h1>Hello {{name}}!</h1>
<ng-content></ng-content>`
})
export class HelloComponent implements OnInit,AfterViewInit {
@Input() name: string;
@ContentChildren('my') divs:QueryList<any>
constructor(private el:ElementRef){}
ngOnInit()
{
console.log(this.el.nativeElement.getAttribute('id'));
}
ngAfterViewInit()
{
console.log(this.divs.length)
}
}
您看到有5个div称为“ my”,而hello的“ id”为myId,请参见https://stackblitz.com/edit/angular-yrcknx?file=src%2Fapp%2Fapp.component.html
答案 2 :(得分:0)
app.componet
<!--see that hello has a new @Input
Use a "reference variable"
-->
<hello id="myId" name="{{ name }}" [component]="other">
<div #my></div>
<div #my></div>
<other #other>
</other>
</hello>
<p>
你好组件
@Input() name: string;
@Input() component: any; //<---component, really is "other"
@ContentChildren('my') divs:QueryList<any>
constructor(private el:ElementRef){}
ngOnInit()
{
console.log(this.el.nativeElement.getAttribute('id'));
}
ngAfterViewInit()
{
console.log(this.divs.length);
console.log(this.component.divs.length)
}
其他组件
@Component({
selector: 'other',
template: `
<div>
<div #my></div>
<div #my></div>
<div #my></div>
</div>`
})
export class OtherComponent{
@Input() name: string;
//I use ViewChildren, not ContentChildren because is not in a <ng-content>
@ViewChildren('my') divs:QueryList<any> //<--has his own "divs"
}
答案 3 :(得分:0)
如果不是让 <div>
封装您的 <mpx-carousel>
组件,而是将它们直接放在另一个组件中,就像这样,它会起作用
<mpx-carousel name="Parent Carousel">
<div *mpxCarouselItem>Item 1</div>
<div *mpxCarouselItem>Item 2</div>
<mpx-carousel name="2nd level Carousel">
<div *mpxCarouselItem>Sub-item A</div>
<div *mpxCarouselItem>Sub-item B</div>
<mpx-carousel name="3rd level Carousel">
<div *mpxCarouselItem>Sub-item A</div>
<div *mpxCarouselItem>Sub-item B</div>
</mpx-carousel>
</mpx-carousel>
<mpx-carousel name="Brother level Carousel">
</mpx-carousel>
</mpx-carousel>
使用这个辅助方法向我展示了预期的信息
ngAfterContentInit() {
//.... existing code
this.showArrayObjects('childCarousels', this.childCarousels);
//.... existing code
}
public showArrayObjects(msg: string, array: any) {
console.log(`${msg} - length:${array.length}..`);
array.forEach(cc => {
console.log(cc);
});
}
我已经分叉了你的 stackblitz https://stackblitz.com/edit/angular-s3kqep?file=src%2Fapp%2Fapp.component.html