简体   繁体   English

过滤 Angular 2+ 中的子内容

[英]Filtering Child Content in Angular 2+

I have two Angular components (accordion > expansion panels) that I need to write without a framework.我有两个 Angular 组件(手风琴 > 扩展面板),我需要在没有框架的情况下编写它们。 I am using ContentChild to support all of the expansion panels contained within the accordion component.我正在使用 ContentChild 来支持手风琴组件中包含的所有扩展面板。 What I want to do is parse the components to check their state and switch update a value on a property from within the parent container.我想要做的是解析组件以检查它们的 state 并从父容器中切换更新属性的值。

Right now I have an event emitter in each expansion-panel which emits if each instance is open or closed.现在我在每个扩展面板中都有一个事件发射器,如果每个实例是打开或关闭的,它就会发出。

I need to track each ChildContent state$ and after each has been checked set a default if there is one, and track any changes if an inner child instance is toggled, it should emit the event to the parent accordion so it can determine which index to track as expanded.我需要跟踪每个 ChildContent state$ 并在每个检查后设置一个默认值(如果有),并跟踪内部子实例切换时的任何更改,它应该将事件发送到父手风琴,以便它可以确定哪个索引跟踪扩展。

I wish to loop through each instance of ChildContent and check if any have been opened, if not open the first instance by changing its state.我希望遍历 ChildContent 的每个实例并检查是否已打开任何实例,如果没有通过更改其 state 打开第一个实例。 In the wrapper, I subscribe to each panel and check in a loop if they have are open, if none are open I wish to expand the first child.在包装器中,我订阅每个面板并在循环中检查它们是否打开,如果没有打开,我希望展开第一个孩子。

Whenever a child panel expands, I wish to contract the remaining child accordions aside from the one selected.每当子面板展开时,我希望将剩余的子手风琴收缩到选定的手风琴之外。

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

Here is the inner child component (the expansion panel)这是内部子组件(扩展面板)

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

Here is the container component (Accordion) template to give an idea of how simple this should be...这是容器组件(Accordion)模板,让您了解这应该是多么简单......

{{ accordionTitle }} {{ accordionSubtitle }} /** ' * The ContentChildren renders ALL of the child components as expanded, no * matter what approach I have tried. {{accordionTitle }} {{ AccordionSubtitle }} /** ' * ContentChildren 将所有子组件呈现为展开状态,无论我尝试过何种方法。 I have verified that the event is being * dispatched to the parent, I also see the individual panels toggling as I * expect. 我已经确认事件正在被 * 分派给父级,我还看到各个面板按照我的预期切换。 What cant get is the dynamic mutually exclusive behavior described * above when the user expands one panel, it should contract the rest. 不能得到的是上面描述的动态互斥行为当用户展开一个面板时,它应该收缩rest。 When * the panels are used alone, they should be able to be configured and used * independently of the accordion (this is fine now). * 当面板单独使用时,它们应该能够独立于手风琴进行配置和使用(现在很好)。 How to map/mutate/set * this once I have dynamically adjusted the values of the child * components state? 一旦我动态调整了子组件 state 的值,如何映射/变异/设置 * 这个? */ */

The problem is that since ContentChild is wrapped in a I cannot reassign a mapped version back to it once I use the ContentChild.prototype.map method, and I want to avoid the overhead of manually rendering the component.问题是,由于 ContentChild 被包装在 a 中,一旦我使用 ContentChild.prototype.map 方法,我就无法将映射版本重新分配给它,并且我想避免手动渲染组件的开销。

The expansion panel works fine, unit tested, and all.扩展面板工作正常,经过单元测试,等等。 The accordion and expansion panel filtering and selection have me stumped.手风琴和扩展面板过滤和选择让我很难过。 I keep getting infinite loops etc, I know I there must be an easier way to dynamically update ContentChildren, can anyone steer me in the right direction?我不断收到无限循环等,我知道我必须有一种更简单的方法来动态更新 ContentChildren,任何人都可以引导我朝着正确的方向前进吗?

So how do I query and then update the ContentChildren from within its wrapper component?那么如何从其包装组件中查询并更新 ContentChildren 呢?

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

The mapping would happen in AccordionComponent.ngAfterContentInit() but I cant make it work with the approach I have in the interactive sandbox linked below.映射将在 AccordionComponent.ngAfterContentInit() 中进行,但我无法使用下面链接的交互式沙箱中的方法使其工作。

Thanks!谢谢!

To do the one thing you asked for in your question, in your accordion, initializing all the panels closed, can be done by iterating over values of the already existing expansionPanels instance attribute:要完成您在问题中要求的一件事,在您的手风琴中,初始化所有关闭的面板,可以通过迭代已经存在的expansionPanels实例属性的值来完成:

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

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

As a suggestion: in your ExpansionPanelComponent 's template there's no need to send the state back to typescript code because you already have access to it in there.作为建议:在您的ExpansionPanelComponent模板中,无需将 state 发送回 typescript 代码,因为您已经可以在其中访问它。 So change it to:所以改成:

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

Now let's do some modifications in the toggle method to get the current state of your BehaviorSubject , instead of destructuring it to get its value.现在让我们对toggle方法进行一些修改,以获取BehaviorSubject的当前 state ,而不是对其进行解构以获取其值。 We don't need to do almost anything because the BehaviorSubject already reemits the last value to new subscribers.我们几乎不需要做任何事情,因为BehaviorSubject已经将最后一个值重新发送给新订阅者。 We'll use the take operator to make sure we'll unsubscribe after taking the last emission:我们将使用take操作符来确保我们在最后一次发射后取消订阅:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM