Subscribe to observable array for new or removed entry only

51,226

Solution 1

As of KnockoutJS 3.0, there's an arrayChange subscription option on ko.observableArray.

var myArray = ko.observableArray(["Alpha", "Beta", "Gamma"]);

myArray.subscribe(function(changes) {

    // For this example, we'll just print out the change info
    console.log(changes);

}, null, "arrayChange");

myArray.push("newitem!");

In the above callback, the changes argument will be an array of change objects like this:

[ 
   { 
      index: 3, 
      status: 'added', 
      value: 'newitem!' 
   }
]

For your specific problem, you want to be notified of new or removed items. To implement that using Knockout 3, it'd look like this:

myArray.subscribe(function(changes) {

    changes.forEach(function(change) {
        if (change.status === 'added' || change.status === 'deleted') {
            console.log("Added or removed! The added/removed element is:", change.value);
        }
    });

}, null, "arrayChange");

Solution 2

Since I couldn't find any info on this elsewhere, I'll add a reply for how to use this with TypeScript.

The key here was to use the KnockoutArrayChange interface as TEvent for subscribe. If you don't do that, it'll try to use the other (non-generic) subscribe and will complain about status, index, and value not existing.

class ZoneDefinition {
    Name: KnockoutObservable<String>;
}

class DefinitionContainer
{
    ZoneDefinitions: KnockoutObservableArray<ZoneDefinition>;
    constructor(zoneDefinitions?: ZoneDefinition[]){
        this.ZoneDefinitions = ko.observableArray(zoneDefinitions);
        // you'll get an error if you don't use the generic version of subscribe
        // and you need to use the KnockoutArrayChange<T> interface as T
        this.ZoneDefinitions.subscribe<KnockoutArrayChange<ZoneDefinition>[]>(function (changes) {
            changes.forEach(function (change) {
                if (change.status === 'added') {
                    // do something with the added value
                    // can use change.value to get the added item
                    // or change.index to get the index of where it was added
                } else if (change.status === 'deleted') {
                    // do something with the deleted value
                    // can use change.value to get the deleted item
                    // or change.index to get the index of where it was before deletion
                }
            });
        }, null, "arrayChange");
}

Solution 3

In order to only detect push() and remove() events, and not moving items, I put a wrapper around these observable array functions.

var trackPush = function(array) {
    var push = array.push;
    return function() {
        console.log(arguments[0]);
        push.apply(this,arguments);
    }
}
var list = ko.observableArray();
list.push = trackPush(list);

The original push function is stored in a closure, then is overlayed with a wrapper that allows me do do anything I want with the pushed item before, or after, it is pushed onto the array.

Similar pattern for remove().

Share:
51,226
Gelin Luo
Author by

Gelin Luo

A Java programmer since Year 2000 Author of ActFramework and Rythm Template Engine

Updated on July 05, 2022

Comments

  • Gelin Luo
    Gelin Luo almost 2 years

    So yes I can subscribe to an observable array:

    vm.myArray = ko.observableArray();
    vm.myArray.subscribe(function(newVal){...});
    

    The problem is the newVal passed to the function is the entire array. Is there anyway I can get only the delta part? Say the added or removed element?

  • Gelin Luo
    Gelin Luo over 11 years
    I am using a different approach: keep track whether an element has been instrumented in the element itself. See my answer above
  • beauXjames
    beauXjames over 10 years
    what is the status for a 'modified' individual?
  • Judah Gabriel Himango
    Judah Gabriel Himango over 10 years
    I don't understand what you're asking when you say, "modified individual".
  • beauXjames
    beauXjames over 10 years
    individual == instance of changes == change ['added','deleted','???','???',...???]
  • Judah Gabriel Himango
    Judah Gabriel Himango over 10 years
    If you're asking what the notification is if an individual item was changed, one of the items in the array, the answer is none. You'll only be notified of removals and additions.
  • Paul
    Paul almost 10 years
    the documentation indicates that moving items will also signal an added or deleted change myArray.reverse(); // [{ index: 0, moved: 1, status: 'deleted', value: 'Alpha' },{ index: 1, moved: 0, status: 'added', value: 'Alpha' }] maybe there should be a check for added and deleted status without the move property?
  • GrantDG
    GrantDG over 8 years
    To save yourself a property, you could use: if(!( 'displayName' in el)){ }