8

I'm using angular 5.2.2 and I'd like to know if it's possible to find a descendant component (nesting depth 2+).

For example:

  • FirstLevelComponent (Declared in my Application)
  • SecondLevelComponent (Third party component)
  • ThirdLevelComponent (Third party component)

FirstLevelComponent Template:

<first-level-component>
   <second-level-component></second-level-component>
</first-level-component>

SecondLevelComponent Template:

<third-level-component></third-level-component>

I'd like to get ThirdLevelComponent instance in the FirstLevelComponent.

  • I've tried using @ViewChildren and @ContentChildren, but no success. Both returns an empty QueryList.
  • I can't change SecondLevelComponent code to expose the ThirdLevelComponent because it's a third party component.

This plunker demonstrates what I'm trying to do: https://plnkr.co/edit/9NsYmrsJMJEi0wofQReI?p=preview


EDIT (2018/02/06)

  • One option could be inheriting SecondLevelComponent for exposing ThirdLevelComponent, but I wouldn't like to use inheritance and it would be limited to just one more depth level.

  • After some inspecting, I endend up creating a helper that searches for descendants components. It uses ViewContainerRef:

      import * as NgCore from '@angular/core';
    
    @NgCore.Injectable()
    export class FindDescendantsService {
        constructor() { }
        private findRecursive(container: any, type: any, results: any[]) {
            if (!container) { return; }
            if ('nodes' in container) {
                container.nodes.forEach(n => {
                    if ('componentView' in n && n.componentView) {
                        if (n.componentView.component instanceof type) {
                            results.push(n.componentView.component);
                        }
                        this.findRecursive(n.componentView, type, results);
                        return;
                    }
    
                    if ('viewContainer' in n && n.viewContainer && n.viewContainer._embeddedViews && n.viewContainer._embeddedViews.length) {
                        n.viewContainer._embeddedViews.forEach(v => this.findRecursive(v, type, results));
                        return;
                    }
                });
            }
        }
        find(vcr: NgCore.ViewContainerRef, type: any) {
            var view = vcr['_view'];
            var results = [];
            this.findRecursive(view, type, results);
            return results;
        }
    }
    

Then in FirstLevelComponent I can do:

ngAfterContentInit(){    
   var thirdLevelInstances = this.findDescendantsService.find(this.viewContainerRef, ThirdLevelComponent);
}

I'm not very comfortable because it uses some private variables that might change names/behaviour in future releases of angular, but this satisfy my goal for now. I'll wait some time to see if a better solution appears, if not, I think this helper might be the answer...

3 Answers 3

2

In order to do that, second component should have an instance of third component. E.g.

// First Component
@Component({
    selector: 'first-comp',
    template: `<second-comp></second-comp>`
})
export class FirstComponent {
    @ViewChild(SecondComponent) secondComponent: SecondComponent;

    doSomething() {
         this.secondComponent.thirdComponent.doStuff();
    }
}

// Second Component
@Component({
    selector: 'second-comp',
    template: `<third-comp></third-comp>`
})
export class SecondComponent{
    @ViewChild(ThirdComponent) thirdComponent: ThirdComponent;
}  

// Third Component
@Component({
    selector: 'third-comp',
    template: `<div>Hello World</div>`
})
export class ThirdComponent{
    doStuff() {
       // some logic here
    }
}    

However, I would not recommend this method. This creates tightly coupled components which is harder to test as well. You should always pass your data through @Input and @Outputs or some services.

Sign up to request clarification or add additional context in comments.

Comments

1

In angular communication between components is only father/son. If you can't use second-level-component, you could accomplish this by using a service, as is recommended in the docs.

https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service

Comments

1

With angular 13 (Maybe before but i've not tested) you can add an option in @ContentChildren descendants:true

For you example it will look like that


  @ContentChildren(ThirdLevelComponent, {descendants: true})
  thirdLevelComponents : QueryList<ThirdLevelComponent>;


Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.