22

I would like to extend some properties recursive (aka. deep copy). much like jQuery does. I'm not including jquery only b/c of one thing.

jQuery.extend( true, target, object1 )

is there any elegant way you know of that does it with simple javascript or angularjs?

update please take a look and try to accomplish the same result http://plnkr.co/edit/GHabYbyhsqtfBPtplksO?p=preview

i did look into .copy() but the "properties (for objects) are deleted"

Endless
  • 34,080
  • 13
  • 108
  • 131
  • 1
    But you could simply copy `$.extend` from jQuery's source? It's [not that hard to find](https://github.com/jquery/jquery/blob/59232825aa87b941dd2418a6860b64017dfec0ae/src/core.js#L125), and pretty stand-alone. – Bergi Dec 17 '14 at 20:54

6 Answers6

28

Here is an extendDeep function based off of the angular.extend function. If you add this to your $scope, you would then be able to call

$scope.meta = $scope.extendDeep(ajaxResponse1.myMeta, ajaxResponse2.defaultMeta);

and get the answer you are looking for.

$scope.extendDeep = function extendDeep(dst) {
  angular.forEach(arguments, function(obj) {
    if (obj !== dst) {
      angular.forEach(obj, function(value, key) {
        if (dst[key] && dst[key].constructor && dst[key].constructor === Object) {
          extendDeep(dst[key], value);
        } else {
          dst[key] = value;
        }     
      });   
    }
  });
  return dst;
};

Note: This function has the side-effect of copying values from later arguments into the earlier arguments. For a simple fix to this side effect, you can change dst[key] = value to dst[key] = angular.copy(value).

Michael Herold
  • 1,292
  • 1
  • 13
  • 22
Ryan O'Neill
  • 3,727
  • 22
  • 27
  • 1
    That's also a good solution, wich angular team had done something like a first param boolen (like jquery) – Endless Mar 09 '13 at 14:48
  • Agreed. I would too, just wanted to demonstrate an easy way to do it using angular's methods. – Ryan O'Neill Mar 09 '13 at 14:50
  • 1
    I think the `if (dst[key])` should actually be `if (dst[key] && dst[key].constructor && dst[key].constructor === Object)` - it deosn't seem to work correctly otherwise – Bennett McElwee Nov 21 '13 at 00:35
  • 1
    This is the better answer since it supports multiple arguments, but would you please incorporate the fix in @BennettMcElwee's comment? – Richard Morgan Jan 19 '14 at 06:23
  • @Richard I have edited the answer to include the fix. Thank you for providing independent confirmation! – Bennett McElwee Jan 19 '14 at 10:42
  • 1
    What is the purpose of the check for dst[key].constructor === Object? Would a simple if (dst[key] && angular.isObject(dst[key])) work? – Godders Jan 28 '14 at 14:48
  • @Godders Yes, that's the point. The fastest approach (and arguably most readable) would be to do `typeof dst[key] === 'object'`. See http://jsperf.com/typeof-vs-underscore-isobject/2 for numbers – Michael Herold Aug 19 '14 at 17:59
17

All the answers here are valid for versions of Angular before 1.4

As of Angular 1.4, you can use angular.merge to do exactly that:

Unlike extend(), merge() recursively descends into object properties of source objects, performing a deep copy.

https://docs.angularjs.org/api/ng/function/angular.merge

Julien P
  • 559
  • 3
  • 9
7
function deepExtend(destination, source) {
  for (var property in source) {
    if (source[property] && source[property].constructor &&
     source[property].constructor === Object) {
      destination[property] = destination[property] || {};
      arguments.callee(destination[property], source[property]);
    } else {
      destination[property] = source[property];
    }
  }
  return destination;
}

Plunker

Src: https://gist.github.com/gregdangelo/2343158

Stewie
  • 60,366
  • 20
  • 146
  • 113
  • 1
    This answer only supports 2 arguments. The one by Ryan supports multiple arguments like jQuery.extend does, but be sure to use Bennett MeElwee's suggestion. – Richard Morgan Jan 19 '14 at 06:21
  • arguments.callee usage is discouraged now -https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments/callee – chrismarx Apr 18 '14 at 18:26
1

Building on Ryan's code, you can shorten the object check and you should also NOT extend functions so you don't override object pointers.

var extendDeep = function extendDeep(dst) {
    angular.forEach(arguments, function(obj) {
        if (obj !== dst) {
            angular.forEach(obj, function(value, key) {
                if (dst[key] && angular.isObject(dst[key])) {
                    extendDeep(dst[key], value);
                } else if(!angular.isFunction(dst[key])) {
                    dst[key] = value;
                }
            });
        }
    });
    return dst;
};
ses
  • 13,174
  • 31
  • 123
  • 226
amcdnl
  • 8,470
  • 12
  • 63
  • 99
0

The same solution as Ryan but with support for array merge

function extendDeep(dst) {
      angular.forEach(arguments, function (obj) {
          if (obj !== dst) {
            angular.forEach(obj, function (value, key) {
                if (dst[key] && dst[key].constructor && dst[key].constructor === Object) {
                  extendDeep(dst[key], value);
                } else if (dst[key] && dst[key].constructor && dst[key].constructor === Array) {
                  dst[key].concat(value);
                } else if(!angular.isFunction(dst[key])) {
                  dst[key] = value;
                }
              }
            );
          }
        }
      );
      return dst;
    }
-1

Angular has a copy method:

angular.copy

charlietfl
  • 170,828
  • 13
  • 121
  • 150
  • 1
    This solution deletes the contents of the destination. angular.copy is used for a different reason than angular.extend – Jamie Pate Oct 08 '13 at 18:14
  • 1
    Definitely not the correct answer for this question, but... this answer helped point me in the right direction for my particular case. :) – Kevin B Jul 01 '14 at 23:19
  • I'm thinking the OP meant angular.extend(myDeepCopy, angular.copy(originalObject)) – neaumusic Dec 01 '14 at 19:55