0

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

现在我在每个扩展面板中都有一个事件发射器,如果每个实例是打开或关闭的,它就会发出。

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

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

每当子面板展开时,我希望将剩余的子手风琴收缩到选定的手风琴之外。

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

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

@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 将所有子组件呈现为展开状态,无论我尝试过何种方法。我已经确认事件正在被 * 分派给父级,我还看到各个面板按照我的预期切换。不能得到的是上面描述的动态互斥行为,当用户展开一个面板时,它应该收缩其余的。* 当面板单独使用时,它们应该能够独立于手风琴进行配置和使用(现在很好)。一旦我动态调整了子 * 组件状态的值,如何映射/变异/设置 * this?*/

问题是,由于 ContentChild 包装在 a 中,因此一旦使用 ContentChild.prototype.map 方法,我就无法将映射版本重新分配给它,并且我想避免手动渲染组件的开销。

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

那么如何从其包装组件中查询并更新 ContentChildren 呢?

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

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

谢谢!

4

1 回答 1

0

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

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

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

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

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

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

import {take} from 'rxjs/operators';
...
toggle(): void {
  this.state$.pipe(take(1)).subscribe((value: ExpansionPanelState) => {
    switch (value) {...}
  });
}
于 2020-05-15T09:47:47.247 回答