Skip to content

Implement @ObserveChildren or similar API #5467

Closed

Description

@robwormald

Previously discussed with @vsavkin and @jeffbcross

Background - programmatically listening to events propagated by children is difficult currently.

Consider:
You have a <accordion> component and n <accordion-item> components:

<accordion>
  <accordion-item></accordion-item>
  <accordion-item></accordion-item>
  <accordion-item></accordion-item>
</accordion>

An accordion-item component might emit a select event/output, used to set it as the currently active item.

Currently the best way to achieve this is the accordion-item component would inject the parent accordion, and have the accordion-item register itself with the parent, and call a method to close the others

@Component({selector: 'accordion' })
class Accordion {
  _items: []AccordionItem = [];
  register(item: AccordionItem){
    this._items.push(item);
  }
  toggleActive(selectedItem:AccordionItem){
    this._items.forEach(item => {
      if(item !== selectedItem){
        item.active = false;
      }
    });
    selectedItem.active = true;
  }
}

@Component({selector: 'accordion-item' , inputs: ['active'] })
class AccordionItem {
  active: boolean;
  constructor(private accordion: Accordion){
    accordion.register(this);
  }
  setActive(){
    this.accordion.toggleActive(this);
  }
}

Ideally, a child component would simply expose a 'select' event:

@Component({selector: 'accordion-item' })
class AccordionItem {
  @Input() active: boolean;
  @Output selected:EventEmitter<AccordionItem> = new EventEmitter();
  active: boolean;
  setActive(){
    this.selected.emit(this);
  }
}

The parent accordion can currently easily get ahold of the children via @ViewChildren() (or whatever) but there is a not a clean way to listen to events emitted from the children.

Proposed Syntax:

Implement a @ObserveChildren decorator (or similar) that would enable Observation of events emitted by child components:

@Component({selector: 'accordion' })
class Accordion {
  //specify type and desired output to Observe.
  @ObserveChildren(AccordionItem, 'select') childSelections: Observable<AccordionItem>
  @ViewChildren(AccordionItem) children: Observable<AccordionItem>
  afterViewInit(){
    this.updates = Observable
      .combineLatest(this.children, this.childSelections, (children, selected) => ({children, selected}))
      .do(({children, selected) => children.map(child => { //update state }))
      .subscribe();
  }
  onDestroy(){
    this.updates.unsubscribe();
  }
}

cc @pkozlowski-opensource @Foxandxss

Activity

  1. Foxandxss commented on Nov 25, 2015

    @Foxandxss
    Member

    This idea is awesome.

    The problem with the current pattern is that it lacks functionality.

    With QueryList you can say: Hey give me all my children. That is nice, that simplifies a lot of patterns like accordion, carousel or tabs.

    The problem with the QueryList is that it is a bit useless by itself. You have access to first item, last item, all items or subscribe to get add / removal of children. Useful stuff, but ... no way of doing concrete stuff with children.

    In angular 1, children requires the parent and in collaboration with the parent, you couple both together to do stuff.

    In angular 2, the idea idea is to move the responsibility to the parent so the children is more of a dumb component that outputs its changes.

    I really like this idea, but currently as @robwormald pointed out, the paren't can't take any responsibility because they don't know what their children do. We need a way to observe those children, listen to what we need from them so we can trigger the needed logic. That way we have simple components and reusable children.

  2. fenduru commented on May 7, 2016

    @fenduru

    I work on a UI library for the rest of my company, so I am also very interested in patterns like this.

    I completely agree with @Foxandxss that it is really easy to access the children but not easy to do anything concrete with them. For components that I have worked on in Angular 1, the kinds of things I have wanted to do with the child components fall into one of three categories:

    1. Manipulate the child's input
    2. Listen to the child's output
    3. Put the child somewhere in your template

    For something like a <checklist> that contains <checkbox>s, then the categories above become:

    1. Programmatically check/uncheck the child <checkbox>s
    2. Update the <checklist>'s output based on the output of the <checkbox>s
    3. Wrap each <checkbox> in an extra DOM element

    Case 1 theoretically works except the change detector complains (Should I file a bug for this?).

    For case 2, I think a decorator that creates an Observable would work very nicely. Hopefully this feature would work with both @ViewChildren and @ContentChildren since that could also close #4062

    I'm not sure if case 3 has been discussed at all, but this would basically be a more flexible method of content projection than ng-content. I think it would be great if {{ someElementRef }} would stick the element there, though I'm sure there are some corner cases with that. This would allow a component like:

    @Component({
      selector: 'my-parent',
      template: `
        <div *ngFor="let child of children">
          This is a child: {{ child }}
         </div>
        `
    })
    class Parent {
      @ContentChildren(Child) children;
    }

    (I know that example doesn't make complete sense because children is QueryList<Child> not QueryList<ElementRef>, but that is the general idea I'm trying to get at).

    If these 3 concepts were supported, I would be in heaven because in Angular 1 I have always dreamed about shifting responsibility out of the child components.

    P.S. thanks @IgorMinar for chatting with me at the end of ngConf and pointing me toward this issue

  3. pkozlowski-opensource commented on Jun 6, 2016

    @pkozlowski-opensource
    Member

    What is described here has many common points with #4062 and as such both issues could / should be considered together. Adding a comment to have cross-reference.

  4. added
    featureLabel used to distinguish feature request from other issues
    on Oct 7, 2016
  5. DzmitryShylovich commented on Feb 4, 2017

    @DzmitryShylovich
    Contributor

    @robwormald close in favor of #13248 ?

  6. added this to the Backlog milestone on Jan 23, 2018
  7. splincode commented on Jul 26, 2018

    @splincode
    Contributor

    @robwormald Is the issue relevant?

  8. added and removed
    featureLabel used to distinguish feature request from other issues
    on Apr 27, 2020
  9. removed this from the Backlog milestone on Apr 27, 2020
  10. 1 remaining item

  11. modified the milestones: needsTriage, Backlog on Sep 22, 2020
  12. added
    featureLabel used to distinguish feature request from other issues
    and removed on Oct 20, 2020
  13. thePunderWoman commented on Oct 17, 2025

    @thePunderWoman
    Contributor

    Closing since we now have OutputRef, which has .subscribe making this technically possible now.

  14. angular-automatic-lock-bot commented on Nov 17, 2025

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  15. locked and limited conversation to collaborators on Nov 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: coreIssues related to the framework runtimecore: queriesdesign complexity: majorfeatureLabel used to distinguish feature request from other issuesfeature: under considerationFeature request for which voting has completed and the request is now under considerationhotlist: components teamRelated to Angular CDK or Angular Material

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions