Angular:使用@ContentChildren 让 child 进入另一个组件

标签 angular

我在 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/

相关文章:

javascript - 使用 SystemJS 将 ng2-ace 库集成到新创建的 angular-cli (angular2) 项目中

css - Angular 7 SCSS 不保留 CSS 自定义属性

angular - 我的模板引用变量 .nativeElement 未定义

angular - 'functions' 类的参数 'TodosComponent' 没有合适的注入(inject) token

angular - 这种在 Angular 中创建 HTTP 服务的方法是否正确?

javascript - 如何以更简洁的方式编写嵌套订阅?

angular - 使用 Http JSON AngularJS 2 时出错

node.js - 如何用观察者替换订阅者?

JavaScript |创建更改数组中值的间隔

Angular 类不工作