3

I would like to get a reference from a component defined in the template. Let's consider I have the following component:

@Component({
    selector: 'a-component'
})
@View({
    template: '<another-component #anotherComponent></another-component>',
    directives: [AnotherComponent]
})
class AComponent {
    
    callAnotherComponent() {
        anotherComponent.doSomething();
    }

}

Obviously, this doesn't work, anotherComponent in my class is not defined. But is there any easy way to do this? In my case, AnotherComponent is supposed to be a popup component and I would like to simply call something like myPopupCmp.showMessage("message").

I could, of course, use data binding and use it like this in my template: <another-component [text]="popupText" [visible]="isPopupVisible"></another-component>, and use these two members to show it by first setting up the text, and then making it visible. In most cases data binding makes sense, but here I would like to encapsulate the attributes and avoid having to take care of the behavior of the popup from where it's being used.

edit:

@EricMartinez I have updated your plnkr after some research, here is a link to the fork : plnkr. The trick is to use @ViewQuery which is used to query directives and dom elements in the View of the Component. I found this in this issue : angular2 github.

Now the problem is that _results seem to come empty, even though I can see my component if I expand the view in the console, and I can even see its properties, so the data is there somehow but I can't access it. Is there a special way of using QueryLists? Using _results doesn't seem very clean as the _ indicates that it should be private or internal. I also tried to use first but it always brings a null reference.

7
  • What if you let both components at the same level instead of making them parent-child? As far as I know a Component creates Shadow DOM so it won't have direct access to its children like you propose in your example. But if you put them at same level and pass the another-component as a property of a-component you'll have access to doSomething. See this example Commented Aug 9, 2015 at 16:48
  • I believe it would work, but It doesn't look very clean somehow. I wanted to play with @Query and @Host to get it injected directly but I can't seem to make it work. @Query always gives me an empty QueryList. In the case above I have something like this : constructor(@Query(AnotherComponent) query : QueryList<AnotherComponent>) { console.log(query); // gives an empty list } Commented Aug 9, 2015 at 18:29
  • Also the problem is that my component is basically containing a text and an Ok button, that will emit an event when clicked so that the component that uses it can perform an action such as resetting form fields or redirecting etc. Therefore I need the component to be in the template where it is used since event binding only goes up the hierarchy I believe. Commented Aug 9, 2015 at 19:47
  • 1
    Hey @ArnaudBoeglin about your edit : nice finding. I've found this IQueryList, here you can see how they iterate over the QueryList. Another thing I know is that in the constructor you don't have access to the results, but you can do it in onInit. See the plnkr edited. Commented Aug 10, 2015 at 14:57
  • 1
    Here is the issue. Thanks for the suggestion @JesseGood Commented Aug 10, 2015 at 23:25

3 Answers 3

3

As of https://github.com/angular/angular/commit/b0e2ebda708cf25287a211a30ef63d84cdb92a7f you should be able to do:

class AComponent {
    constructor(@Query('#anotherComponent') query:QueryList<AnotherComponent>) {

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

5 Comments

Hey there @PascalPrecht, I'm not the OP, but I'm interested in your answer (I didn't know about this way of querying) but I'm not able to make it work in this plnkr. Could you please see it? (Note : in plnkr QueryList<Component> does not work, either you specify QueryList or the Component's name)
So you are saying that it should be #anotherComponent in a string instead of AnotherComponent ?
Whatever I try the QueryList comes empty, I'll have a look at the code of the commit tomorrow to try to understand what is happening. But I suspect that the problem comes from the fact that the component we try to get a reference to is in the template. It should definitely work though ..
@EricMartinez I tried to play with the plnkr and the QueryList behaves in a weird way indeed. On my local version I don't have a problem using QueryList<AnotherComponent>, but the List still comes empty though .. In the documentation of DirectiveAnnotation Host and Query are pretty well explained : DirectiveAnnotation, and it should work with Components as they are a Directive with a View.
I did some progress and updated my original post, see the edit for more information.
2

So, to sum things up, I had to use the decorator @ViewQuery :

class AComponent {

    private _anotherComponentQuery : QueryList<AnotherComponent>;

    constructor(@ViewQuery('anotherComponent') query : QueryList<AnotherComponent>) {
    this._anotherComponentQuery = query;
}

    onInit() {
        for(component of this._anotherComponentQuery){
            //component.instance.callMethod()
        }
    }

}

Two important things to note here, the component returned by the ViewQuery is a ComponentRef here, since in my template it is a component. It would otherwise be an ElementRef I believe in the case of a simple HTML tag. The other thing is that in the constructor of my component, the query is empty as I believe the template has not been parsed yet at this stage. And QueryList is dynamic and will be updated whenever my view changes ( say I add elements that satisfy the query ). So the trick is either to access the query only later, for example in an event handler, or to do it in the onInit which is part of the lifecycle of components. Weirdly enough, if I would define the lifecyle onInit and import onInit, the code would never go through my method onInit(), and it worked fine without importing anything .. go figure !

1 Comment

Thanks! Just a note, in onInit I can't get the component, I have to request it in afterViewInit().
0

Please see this Execute method on child component, seems like the same approach as in answer but without iterating through the results of the query, if you know the name of your referenced child component.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.