过滤 Child 中的内容 Angular 2+

Filtering Child Content in Angular 2+

我有两个 Angular 组件(手风琴 > 扩展面板)需要在没有框架的情况下编写。我正在使用 ContentChild 来支持手风琴组件中包含的所有扩展面板。我想要做的是解析组件以检查它们的状态并从 parent 容器中切换更新 属性 上的值。

现在我在每个 expansion-panel 中都有一个事件发射器,它会在每个实例打开或关闭时发射。

我需要跟踪每个 ChildContent state$ 并在检查每个 ChildContent 状态后设置一个默认值(如果有的话),并跟踪任何更改(如果内部 child 实例被切换),它应该将事件发送到parent 手风琴,以便它可以确定要跟踪哪个索引展开。

我希望循环遍历 ChildContent 的每个实例并检查是否有任何已打开,如果没有通过更改其状态打开第一个实例。在包装器中,我订阅了每个面板并循环检查它们是否打开,如果 none 打开我希望扩展第一个 child.

每当 child 面板展开时,我希望收缩除了选定的那个之外剩余的 child 个手风琴。

@Component({
  selector: 'app-accordion',
  templateUrl: './accordion.component.html'
})
export class AccordionComponent extends Destroyable implements AfterContentInit, OnDestroy {
  @ContentChildren(ExpansionPanelComponent) expansionPanels: QueryList<ExpansionPanelComponent>;
 ...

这是内部 child 组件(扩展面板)

@Component({
  selector: 'app-expansion-panel',
  templateUrl: './expansion-panel.component.html'
})
export class ExpansionPanelComponent extends Destroyable implements OnInit, OnDestroy  {
  @Input() title!: string;
  @Input() content!: string | string[];

  /**
   * The default for this input would be overwritten if the optional
   * @Input() defaultState is provided during initialization.
   * The defaultState override occurs inside of this.setDefaultState();
   */
  @Input() defaultState: ExpansionPanelState = ExpansionPanelState.Collapsed;
  @Input() footerContent?: string;
  @Input() headerIcon?: IconName;
  @Input() toggleButton?: ViewChild;
  @Output() expanded$ = new EventEmitter<ExpansionPanelState>();

  setState(expansionPanelState: ExpansionPanelState): void {
    if (this.expanded$.observers.length > 0) {
      this.expanded$.emit(expansionPanelState);
    }

    this.state$.next(expansionPanelState);
  }

这是容器组件 (Accordion) 模板,让您了解这应该是多么简单...

{{ accordionTitle }} {{ accordionSubtitle }} /** ' * ContentChildren 将所有 child 组件呈现为已展开,不 * 不管我尝试过什么方法。我已确认该活动正在进行中 * 发送到 parent,我还看到各个面板在切换时 * 预计。拿不到的是描述的动态互斥行为 * 上面当用户展开一个面板时,它应该收缩其余部分。什么时候 *面板是单独使用的,应该可以配置使用 * 独立于手风琴(现在很好)。如何 map/mutate/set * 这一次我动态调整了 child 的值 * 组件状态? */

问题是,由于 ContentChild 被包装在一个中,所以我无法在使用 ContentChild.prototype.map 方法后将映射版本重新分配回它,我想避免手动渲染组件的开销。

扩展面板工作正常,单元测试,以及所有。手风琴和扩展面板的过滤和选择让我很困惑。我不断得到无限循环等,我知道我必须有一种更简单的方法来动态更新 ContentChildren,任何人都可以引导我朝着正确的方向前进吗?

那么如何从其包装器组件中查询然后更新 ContentChildren?

https://codesandbox.io/s/silly-river-jsbuu?file=/src/app/accordion/accordion.component.ts:0-2156

映射将在 AccordionComponent.ngAfterContentInit() 中进行,但我无法使用下面链接的交互式沙箱中的方法使其工作。

谢谢!

要完成你在问题中要求的一件事, 在你的手风琴中,初始化所有关闭的面板,可以通过迭代已经存在的值来完成 expansionPanels实例属性:

import { ExpansionPanelState } from "PATH/TO/FILE/expansion-panel.model";

ngAfterContentInit() {
  this.expansionPanels
    .forEach(panel => panel.setState(ExpansionPanelState.Collapsed));
}

作为建议: 在您的 ExpansionPanelComponent 模板中,无需将状态发送回 typescript 代码,因为您已经可以在其中访问它。所以将其更改为:

<header (click)="toggle()"...

现在让我们对 toggle 方法进行一些修改以获取 BehaviorSubject 的当前状态,而不是解构它以获取其值。我们几乎不需要做任何事情,因为 BehaviorSubject 已经将最后一个值重新发送给新订阅者。我们将使用 take 运算符来确保我们将在最后一次发射后取消订阅:

import {take} from 'rxjs/operators';
...
toggle(): void {
  this.state$.pipe(take(1)).subscribe((value: ExpansionPanelState) => {
    switch (value) {...}
  });
}