Angular:使用@ContentChildren 将子组件放入另一个组件中

Ben*_*min 7 angular

我在 Angular 6 中创建了一个自定义轮播组件。简单的用法是这样的:

<mpx-carousel>
  <div *mpxCarouselItem>Slide 1</div>
  <div *mpxCarouselItem>Slide 2</div>
  <div *mpxCarouselItem>Slide 3</div>   
</mpx-carousel>
Run Code Online (Sandbox Code Playgroud)

但它也支持嵌套,如下所示:

<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>
Run Code Online (Sandbox Code Playgroud)

在父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
}
Run Code Online (Sandbox Code Playgroud)

在父轮播的 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
    });
}
Run Code Online (Sandbox Code Playgroud)

所以@ContentChildren 包含父级本身有点奇怪changes,而且在检测子级之前必须等待事件有点奇怪,但这是一个足够简单的解决方法。

当在另一个组件中发现子轮播时,真正的麻烦就开始了。这CarouselComponent是项目中许多其他组件使用的共享组件,所以我可以经常这样使用它......

<!-- parent carousel -->
<mpx-carousel>
  <mpx-component-A></mpx-component-A>
  <mpx-component-B></mpx-component-B>
</mpx-carousel>
Run Code Online (Sandbox Code Playgroud)

...哪里<mpx-component-A>是另一个在内部使用自己的 mpx-carousel 的组件。我仍然希望这里的父轮播能够检测在 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
    });
}
Run Code Online (Sandbox Code Playgroud)

我可以提供完整的代码CarouselComponentCarouselItemDirective如果这有帮助的话。但我的问题更普遍:父组件有没有办法获取对包含另一个组件(ComponentA)中的某种类型(CarouselComponent)的后代组件的引用?

通过使用@ContentChildren,我可能完全找错了树,所以我对完全不同的方法持开放态度。

提前致谢!

编辑: 这是一个 StackBlitz,它使我想要做的事情更清楚。请注意中的 console.log() 语句carousel.component.ts和显示预期与实际输出的注释。(注意:在现实世界中,旋转木马每隔几秒钟就会旋转其项目。但为了简单起见,我在这里删除了所有内容)。

Хри*_*тов 5

这是一个尽管如此,据我所做的研究,没有自动神奇的方法来查询另一个组件内类型的所有嵌套组件。

ContentChildren:
不检索其他组件模板中的元素或指令,因为组件的模板对其祖先始终是一个黑匣子。

引用自Angular 文档

过去我有类似的用例,我的解决方案是为嵌套的所有组件部分创建一个通用接口

export interface CommonQueryInterface {
  commonField: QueryList<any>
}

Run Code Online (Sandbox Code Playgroud)

因此,逻辑位于最顶层(最高的父组件),具有遍历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];
    }
  }
}

Run Code Online (Sandbox Code Playgroud)

我的解决方案建议在轮播组件内将轮播元素本身添加到服务中

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();
  }
}

Run Code Online (Sandbox Code Playgroud)

完成此操作后,您将可以随时访问活动的(屏幕上)轮播。这种方法的缺点是您需要在旁边name @Input再添加一个 groupName @Input。这是我的实现中所需要的,以便我们可以在同一视图中区分不同的轮播组件。

这是StackBlitz中该概念的基本实现

此实现的好处是,通过访问所有轮播,您可以在轮播组件本身内部添加与轮播相关的自定义逻辑,并且它将被锁定,远离组件的使用者。例如,这样的逻辑可能是重新启动单个轮播组内的所有轮播。