[SalesForce] component.set() on array attribute does not work after splice

I'm trying to build a column filter component for a table where there can be multiple column filter can be specified.

When I click on the Add button, new filter item appears below. But if I delete the filter item using remove button, the actual object in the array is removed but it is not reflected in the view.

I tried couple of workarounds, but it doesn't work. I'm running out of options here.

Here's the code:

filterBox.cmp

<aura:component>
    <aura:attribute name="columns" type="List" default="['Name','Email','Birthdate']" />
    <aura:attribute name="operators" type="List" default="['less than','greater than','equal to']" />
    <aura:attribute name="filters" type="List" default="[{'column':'','op':'','value':''}]" />
    <div class="filter-box">
        <aura:iteration items="{!v.filters}" var="filter" indexVar="index">
            <div class="filter-item">
                <ui:inputSelect value="{!filter.column}">
                    <aura:iteration items="{!v.columns}" var="column">
                        <ui:inputSelectOption label="{!column}" text="{!column}" />
                    </aura:iteration>
                </ui:inputSelect>
                <ui:inputSelect value="{!filter.op}">
                    <aura:iteration items="{!v.operators}" var="op">
                        <ui:inputSelectOption label="{!op}" text="{!op}" />
                    </aura:iteration>
                </ui:inputSelect>
                <ui:inputText value="{!filter.value}" />
                <button type="button" onclick="{!c.addFilter}">Add</button>
                <aura:if isTrue="{!index != 0}">
                    <button type="button" onclick="{!c.removeFilter}" data-index="{!index}">Remove</button>
                </aura:if>
            </div>
        </aura:iteration>
    </div>    
</aura:component>

filterBoxController.js

({
    addFilter : function(cmp, event, helper) {
        var filters = cmp.get("v.filters");
        filters.push({'column':'','op':'','value':''});
        cmp.set("v.filters",filters);
        console.log(cmp.get("v.filters"))
    },
    removeFilter : function(cmp, event, helper) {
        var index = event.target.getAttribute('data-index');
        var filters = cmp.get("v.filters");
        filters.splice(Number(index),1);
        console.log(filters,index);
        cmp.set("v.filters",filters); // didn't work
        cmp.set("v.filters",[].concat(filters)); // didn't work
        cmp.set("v.filters",JSON.parse(JSON.stringify(filters))); // didn't work
    }
})

FYI, I see the same behavior with locker service turned on and off as well.

Best Answer

This is a known issue with ui:inputSelectOption inside an aura:iteration. If you file a case you can reference the internal bug number W-3430029.

For now I'd see if you can structure your code to not use the ui:inputSelectOption inside your iteration.

I'll update this answer if I find a workaround for the issue.

Edit (12/2): A dirty workaround you can try is to destroy the last ui:inputSelect component in the list before removing the item from the array. For example, put an aura:id on each ui:inputSelect component and then do the following when you remove the row:

function destroySelect(select) {
    if ($A.util.isArray(select)) {
        var last = select.pop();
        last.destroy();
    }
}
destroySelect(cmp.find("mySelect1"));
destroySelect(cmp.find("mySelect2"));
Related Topic