Changes to nested array not being reflected upon splicing


#1

My route’s model has a nested array with data that gets rendered in the template. I noticed that when doing array.pushObject(foo), the appended data is immediately visible but when splicing an object from the array, nothing appears to change until I navigate away from and then back to the same route.

It got me wondering how Ember even tracks additions to the array in the first place. I didn’t think twice about it at first because everything just worked. I actually didn’t think that it would be an Ember Array at all when it was instantiated as I never declared it as such. The topmost array/highest level parent is a property in a Service however, so I’m guessing that might have something to do with it.

All of the additions and splices are being handled in a util. I’m not sure if that matters but I didn’t want to leave out any vital information. I was thinking that one way to handle this would be to create a component that renders this nested array and somehow re-render that component from within the util once the splice is made (hopefully this is possible). Otherwise I’m not really sure how I can tell Ember from within the util that something has been removed from the nested array and that I want this to be reflected in the template.

Thanks for any help.


#2

Hi @PDog

The first thing to note (I’m taking most of these from this page from the Ember docs) is that Ember will extend Javascript’s Array prototype by default unless you explicitly tell it not to.

This is why a normal array declared with [] will still have the pushObject method even though you never made it an array. If you’re writing an add-on that uses arrays or if you disable extending the prototype, you should never count on the extended prototype and wrap your arrays:

import { A } from '@ember/array';

const myArray = A([]);
myArray.pushObject({});

In your particular use case, there is no splice method that will trigger a property change notification and cause your component to rerender even on an Ember-extended array.

There are a few other methods that will give you similar behavior that are Ember friendly:

  • removeAt
  • removeObject
  • removeObjects
  • insertAt

You can also disregard the Ember Array altogether as Ember will also trigger a rerender if you set a new instance of your array onto the property that you want to change.

For example:

export default Component.extend({
  actions: {
    removeItemFromArray(index) {
      const [...newArray] = this.get('arrayProperty'); // copy the array
      newArray.splice(index, 1);
      // Will trigger rerender since newArray is a copy
      this.set('arrayProperty', newArray); 
    }
  }
});

I’ve also seen this turned into a nice pattern:

import { get, set } from '@ember/object';
import { copy } from '@ember/object/internals';

function updateProperty(ctx, key, updateFn) {
  const copiedValue = copy(get(ctx, key));
  set(ctx, key, updateFn(copiedValue);
}

export default Component.extend({
  actions: {
    removeItemFromArray(index) {
      updateProperty(this, 'arrayProperty', (newArray) => {
        newArray.splice(index, 1);
        return newArray;
      });
    }
  }
});

Anyway — that was probably way more than you were looking for, but hope some of this helps!


#3

Spencer, thanks tremendously for the detailed reply and explanation. That’s exactly what I was looking for. I had forgotten that pushObject was an Ember method as I had started this project a few years ago and only recently came back to it. Now it completely makes sense as to why changes would be immediately reflected upon pushObject but not a generic splice. Your examples are really helpful and now I have multiple ways to tackle this going forward. Thanks again for your help.