Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit c0498d4

Browse files
caitppetebacondarwin
authored andcommitted
feat(angular.merge): provide an alternative to angular.extend that merges 'deeply'
Closes #10507 Closes #10519
1 parent f591776 commit c0498d4

File tree

5 files changed

+128
-14
lines changed

5 files changed

+128
-14
lines changed

src/.jshintrc

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"extend": false,
3636
"toInt": false,
3737
"inherit": false,
38+
"merge": false,
3839
"noop": false,
3940
"identity": false,
4041
"valueFn": false,

src/Angular.js

+55-14
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
extend: true,
3030
toInt: true,
3131
inherit: true,
32+
merge: true,
3233
noop: true,
3334
identity: true,
3435
valueFn: true,
@@ -318,6 +319,31 @@ function setHashKey(obj, h) {
318319
}
319320
}
320321

322+
323+
function baseExtend(dst, objs, deep) {
324+
var h = dst.$$hashKey;
325+
326+
for (var i = 0, ii = objs.length; i < ii; ++i) {
327+
var obj = objs[i];
328+
if (!isObject(obj) && !isFunction(obj)) continue;
329+
var keys = Object.keys(obj);
330+
for (var j = 0, jj = keys.length; j < jj; j++) {
331+
var key = keys[j];
332+
var src = obj[key];
333+
334+
if (deep && isObject(src)) {
335+
if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
336+
baseExtend(dst[key], [src], true);
337+
} else {
338+
dst[key] = src;
339+
}
340+
}
341+
}
342+
343+
setHashKey(dst, h);
344+
return dst;
345+
}
346+
321347
/**
322348
* @ngdoc function
323349
* @name angular.extend
@@ -328,30 +354,45 @@ function setHashKey(obj, h) {
328354
* Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
329355
* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
330356
* by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
331-
* Note: Keep in mind that `angular.extend` does not support recursive merge (deep copy).
357+
*
358+
* **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use
359+
* {@link angular.merge} for this.
332360
*
333361
* @param {Object} dst Destination object.
334362
* @param {...Object} src Source object(s).
363+
* @param {boolean=} deep if the last parameter is set to `true`, objects are recursively merged
364+
* (deep copy). Defaults to `false`.
335365
* @returns {Object} Reference to `dst`.
336366
*/
337367
function extend(dst) {
338-
var h = dst.$$hashKey;
368+
return baseExtend(dst, slice.call(arguments, 1), false);
369+
}
339370

340-
for (var i = 1, ii = arguments.length; i < ii; i++) {
341-
var obj = arguments[i];
342-
if (obj) {
343-
var keys = Object.keys(obj);
344-
for (var j = 0, jj = keys.length; j < jj; j++) {
345-
var key = keys[j];
346-
dst[key] = obj[key];
347-
}
348-
}
349-
}
350371

351-
setHashKey(dst, h);
352-
return dst;
372+
/**
373+
* @ngdoc function
374+
* @name angular.merge
375+
* @module ng
376+
* @kind function
377+
*
378+
* @description
379+
* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
380+
* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
381+
* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`.
382+
*
383+
* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
384+
* objects, performing a deep copy.
385+
*
386+
* @param {Object} dst Destination object.
387+
* @param {...Object} src Source object(s).
388+
* @returns {Object} Reference to `dst`.
389+
*/
390+
function merge(dst) {
391+
return baseExtend(dst, slice.call(arguments, 1), true);
353392
}
354393

394+
395+
355396
function toInt(str) {
356397
return parseInt(str, 10);
357398
}

src/AngularPublic.js

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ function publishExternalAPI(angular) {
117117
'bootstrap': bootstrap,
118118
'copy': copy,
119119
'extend': extend,
120+
'merge': merge,
120121
'equals': equals,
121122
'element': jqLite,
122123
'forEach': forEach,

test/.jshintrc

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"nextUid": false,
3131
"setHashKey": false,
3232
"extend": false,
33+
"merge": false,
3334
"toInt": false,
3435
"inherit": false,
3536
"noop": false,

test/AngularSpec.js

+70
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,7 @@ describe('angular', function() {
382382
expect(hashKey(dst)).not.toEqual(hashKey(src));
383383
});
384384

385+
385386
it('should retain the previous $$hashKey', function() {
386387
var src,dst,h;
387388
src = {};
@@ -395,6 +396,7 @@ describe('angular', function() {
395396
expect(hashKey(dst)).toEqual(h);
396397
});
397398

399+
398400
it('should work when extending with itself', function() {
399401
var src,dst,h;
400402
dst = src = {};
@@ -405,6 +407,74 @@ describe('angular', function() {
405407
});
406408
});
407409

410+
411+
describe('merge', function() {
412+
it('should recursively copy objects into dst from left to right', function() {
413+
var dst = { foo: { bar: 'foobar' }};
414+
var src1 = { foo: { bazz: 'foobazz' }};
415+
var src2 = { foo: { bozz: 'foobozz' }};
416+
merge(dst, src1, src2);
417+
expect(dst).toEqual({
418+
foo: {
419+
bar: 'foobar',
420+
bazz: 'foobazz',
421+
bozz: 'foobozz'
422+
}
423+
});
424+
});
425+
426+
427+
it('should replace primitives with objects', function() {
428+
var dst = { foo: "bloop" };
429+
var src = { foo: { bar: { baz: "bloop" }}};
430+
merge(dst, src);
431+
expect(dst).toEqual({
432+
foo: {
433+
bar: {
434+
baz: "bloop"
435+
}
436+
}
437+
});
438+
});
439+
440+
441+
it('should replace null values in destination with objects', function() {
442+
var dst = { foo: null };
443+
var src = { foo: { bar: { baz: "bloop" }}};
444+
merge(dst, src);
445+
expect(dst).toEqual({
446+
foo: {
447+
bar: {
448+
baz: "bloop"
449+
}
450+
}
451+
});
452+
});
453+
454+
455+
it('should copy references to functions by value rather than merging', function() {
456+
function fn() {}
457+
var dst = { foo: 1 };
458+
var src = { foo: fn };
459+
merge(dst, src);
460+
expect(dst).toEqual({
461+
foo: fn
462+
});
463+
});
464+
465+
466+
it('should create a new array if destination property is a non-object and source property is an array', function() {
467+
var dst = { foo: NaN };
468+
var src = { foo: [1,2,3] };
469+
merge(dst, src);
470+
expect(dst).toEqual({
471+
foo: [1,2,3]
472+
});
473+
expect(dst.foo).not.toBe(src.foo);
474+
});
475+
});
476+
477+
408478
describe('shallow copy', function() {
409479
it('should make a copy', function() {
410480
var original = {key:{}};

0 commit comments

Comments
 (0)