Question

Application

I am working on a simple web application that is built on top of AngularJS. The application should be able to work offline as well as online. When the user is offline, the changes to the data is stored locally. Therefore, the id's that is used within this application in offline mode is only temporary id's, they get replaced when uploaded to the server

Problem

The data that are used in the application consists of complex objects (with relations/references to other objects). When i am saving to the server, i wanted the views to get updated with the new "real" id's. However, since JavaScript works with objects as references im not able to do what i want to: $scope.data = newdata This is not overwriting $scope.data but creates a new object. The old reference to the old data is still there.

Simplified example

var x = {id: 1, name: "myObject"}
var c = x    // c = {id: 1, name: "myObject"}
x = {id: 2, name: "myNewObject"} 
// c = {id: 1, name: "myObject"}

As you can see, c is still a reference to the old object. In practice, this causes that my view isn't updated with new data since it's still bound to the old data. What i need to is to overwrite the properties of, in this example, x. I need to do this recursively since my real objects are complex, however it shouldn't enter any circular references, since this will probably cause stack overflow. If i am overwriting a with b and a has properties that b hasn't got, those properties should be removed.

What i need

I need some sort of function that overwrites all properties in a (old object) with the properties in b (new object). All properties that exists in a but not in b should be removed.

Was it helpful?

Solution 3

I found a solution after some thinking. It's probably not the most efficient solution, but it does the job for me. The time complexity could probably be better, and all suggestions of improvement are welcome. First parameter is the object to be extended, the second the one to extend with. The third is supposed to be a boolean, indicating whether the properties in a that doesn't exist in b should be removed or not.

function extend(_a,_b,remove){
        remove = remove === undefined ? false : remove;
        var a_traversed = [],
            b_traversed = [];

        function _extend(a,b) {
            if (a_traversed.indexOf(a) == -1 && b_traversed.indexOf(b) == -1){
                a_traversed.push(a);
                b_traversed.push(b);
                if (a instanceof Array){
                    for (var i = 0; i < b.length; i++) {
                        if (a[i]){  // If element exists, keep going recursive so we don't lose the references
                            a[i] = _extend(a[i],b[i]);
                        } else { 
                            a[i] = b[i];    // Object doesn't exist, no reference to lose
                        }
                    }
                    if (remove && b.length < a.length) { // Do we have fewer elements in the new object?
                        a.splice(b.length, a.length - b.length);
                    }
                }
                else if (a instanceof Object){
                    for (var x in b) {
                        if (a.hasOwnProperty(x)) {
                            a[x] = _extend(a[x], b[x]);
                        } else {
                            a[x] = b[x];
                        }
                    }
                    if (remove) for (var x in a) {
                        if (!b.hasOwnProperty(x)) {
                            delete a[x];
                        }
                    }
                }
                else{
                    return b;
                }
                return a;
            }    
        }

        _extend(_a,_b);
    }

OTHER TIPS

If your environment supports ECMAScript 2015, you can use Object.assign():

'use strict'

let one = { a: 1, b: 2, c: 3 };
let two = { b: 20, c: 30, d: 40 };

let three = Object.assign({}, one, two);

console.log(three);

// will output: Object {a: 1, b: 20, c: 30, d: 40}

(let is the new locally scoped version of var in ECMAScript 2015) more...


So in the case of your simple example:

var x = { id: 1, name: "myObject" };
Object.assign(x, { id: 2, name: "myNewObject" });

console.log(x);

// will output: Object {id: 2, name: "myNewObject"}

Using the "extend" method which is available in underscore and jquery:

//Clear all the 'old' properties from the object
for (prop in old_object) {delete old_object[prop]}
//Insert the new ones
$.extend(old_object, new_object)

I'm adding an answer, even though everyone has explained both why and solutions.

The reason I'm adding answer, is because I've searched for this answer a few times over the years and always basically come to the same 2/3 SO questions. I put the solutions in the too-hard-basket, because the code I've been working with has many modules all following similar design patterns; it's just been too much work to try and resolve what boiled down to the same issue you were having.

What I've learned, and hopefully it holds some value for others out there now that I've actually re-factored our codebase to avoid this issue (sometimes maybe its unavoidable, but sometimes it definitely is), is to avoid using 'static private variables' to reference Objects.

This can probably be more genericised, but take for example:

var G = {
    'someKey' : {
        'foo' : 'bar'
    }
};

G.MySingletonClass = (function () {

    var _private_static_data = G.someKey; // referencing an Object

    return {

        /**
         * a method that returns the value of _private_static_data
         * 
         * @method log
         **/
        log: function () {

            return _private_static_data;

        } // eom - log()

    }; // end of return block

}()); // end of Class

console.log(G.MySingletonClass.log());

G.someKey = {
    'baz':'fubar'
};

console.log(G.MySingletonClass.log());

http://jsfiddle.net/goxdebfh/1/

As you can see, same problem experienced by the Questioner. In my case, and this use of private static variables referencing Objects was everywhere, all I needed to do was directly lookup G.someKey; instead of storing it as a convenience variable for my Class. The end result (though lengthier as a result of inconvenience) works very well:

var G = {
    'someKey' : {
        'foo' : 'bar'
    }
};

G.MySingletonClass = (function () {

    return {

        /**
         * a method that returns the value of _private_static_data
         * 
         * @method log
         **/
        log: function () {

            return G.someKey;

        } // eom - log()

    }; // end of return block

}()); // end of Class

console.log(G.MySingletonClass.log());

G.someKey = {
    'baz':'fubar'
};

console.log(G.MySingletonClass.log());

http://jsfiddle.net/vv2d7juy/1/

So yeah, maybe nothing new given the question has been solved, but I felt compelled to share that because I was even lead to believe that the first example was the correct way to do things. Maybe in some cases it is, it definitely didn't turn out to be.

Hopefully that helps someone, somewhere!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top