1

I'm trying to retrieve elements using document.getElementsByClassName("") within the afterRender function. This works as expected with normal elements. However, I can't seem to retrieve elements being created inside of my aura:iteration tags.

Is there a way to retrieve aura:iteration elements that I'm missing? Has aura:iteration not finished at the time that the afterRender function is called?

3

This could depend on a lot of things.

For example, if your <aura:iteration> is looping over data that you're retrieving via an Apex controller, then the first time it is rendered there will be no components because it hasn't got the data from the server yet. One of the later renderings will include the data.

As a general rule, you would be better advised to use component.find('myAuraId') instead of document.getElementsByClassName(""). The framework prefers you to access things via component rather than mess around with the DOM directly. Messing with the DOM can lead you into strange difficulties as Locker Service stops you from doing things you might outside of Lightning. So, better to just go with the grain and use component.find('myAuraId').

Note that aura:id does not have to be unique and component.find('myAuraId') will return an array if it matches multiple elements. Annoyingly, if there is only one match, it does not return an array with one component, it just returns a single component. So, to treat it simply, you tend to have to write

var findResults = component.find('myAuraId');
if($A.util.isEmpty(findResults)) { 
  findResults = [];
}
if(!$A.util.isArray(findResults)) {
  findResults = [ findResults ];
}

Once you have a reference to the component, you can call getElement() if you really must interact directly with the DOM. But, again, I'd strongly advise against it if you can possibly avoid it. If you call getElement() before the component is rendered, you will get null. If you call it after, you will get a Locker Service proxy for the element.

| improve this answer | |
  • 1
    Thank you for the response. I assumed than aura:id was meant to be used as a unique value which is why I was trying to retrieve by class name. I will definitely be switching to aura:id. My problem was stemming from the afterRender firing before aura:iterate dumped out all of my elements. – Aubrey Walts Jan 16 '18 at 8:54
  • @AubreyWalts Another reason to use component.find is that the components matched will only be the children of the current component. But for document.getElementsByClassName, if you have multiple instances of your component (e.g. in the console view) all the elements of all the instances will be matched AFAIK. – Keith C Jan 16 '18 at 8:58
  • Wow that seems silly. Thanks for saving me a lot of frustration up front. – Aubrey Walts Jan 16 '18 at 9:04
  • @AubreyWalts Don't miss the second paragraph of my answer. Render gets called multiple times as your component starts up, so don't assume that it means everything is rendered as it will be in the final rendering. Even in a render event, you still need to check if the thing you want is there yet. And, in general, we try not to use render unless we really have to. Often you can achieve the same with by using expressions in the markup. – Aidan Jan 16 '18 at 9:07
  • Haha. I actually read it and hadn't processed it - 4am on stackexchange, ya' know. This is my first time working with a lightning component and sussing out how all the pieces fit together and the series in which they are loaded and executed has my mind teetering on the edge. I appreciate your help, really. – Aubrey Walts Jan 16 '18 at 9:14
1

Yes, afterRender is called only after the DOM is fully constructed. Note that document.getElementsByClassName is filtered to only return DOM elements in your component's namespace. This means you can't casually inspect the DOM of a lightning:input, for example, because Lightning Locker Service prevents this access by way of a Proxy class. If you're having trouble getting the elements you expect, try using aura:id and component.find to get the elements.

Here's a basic example that works with a simple iteration:


<aura:application >
    <aura:attribute name="itemList" type="Object[]" default="[{label:'Value 1'},{label:'Value 2'}]" />
    <aura:iteration items="{!v.itemList}" var="item">
        <span class="demo">{!item.label}</span>
    </aura:iteration>
</aura:application>

({
    // Your renderer method overrides go here
    afterRender: function(component) {
        var items = document.getElementsByClassName("demo");
        console.log(items[0].innerHTML);
        console.log(items[1].innerHTML);
    }
})

Output:

> Value 1
> Value 2

Demo using a lightning:input:


<aura:application extends="force:slds">
    <aura:attribute name="itemList" type="Object[]" default="[{label:'Value 1',value:'Input 1'},{label:'Value 2',value:'Input 2'}]" />
    <aura:iteration items="{!v.itemList}" var="item">
        <lightning:input name="{!item.label}" label="{!item.label}" aura:id="demo" value="{!item.value}" />
    </aura:iteration>
</aura:application>

({
    // Your renderer method overrides go here
    afterRender: function(component) {
        var items = component.find("demo");
        console.log(items[0].get("v.value"));
        console.log(items[1].get("v.value"));
    }
})

Output:

> Input 1
> Input 2
| improve this answer | |
  • Thank you for the response. I appreciate the help. Actually, afterRender was firing before aura:iterate created all of my elements. I'm working in a component that someone else created for the most part. He had a chain of functions firing in the helper class and I was able to resolve it by moving my code into a final "link" in his chain of functions instead of in the renderer. I will definitely be switching to aura:ids. I assumed they were meant to be used for unique elements because that's the purpose of an id in html. – Aubrey Walts Jan 16 '18 at 8:59
  • @AubreyWalts Yeah, component.find returns either null (no matches), an item (one match), or a List (multiple matches). This is not the same as normal HTML behavior, but it works really well for its intended purpose. Still, afterRender can be called multiple times if you've got server-side calls that you call on init. In the basic case, though, aura:iteration has "settled" (rendered to the current data state) by the time the event fires, which was the point of the answer. I'm glad I was able to help you out. – sfdcfox Jan 16 '18 at 9:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.