8

Is there any convenient way to replace the content of an array, AND keep a reference to it? I don't want to replace the array like this:

var arr1 = [1,2,3];
var referenceToArr1 = arr1;
var arr2 = [4,5,6];

arr1 = arr2;
// logs: [4,5,6] false
console.log(arr1, arr1===referenceToArr1);
// logs [1,2,3]
console.log(referenceToArr1);

This way arr1 has the content of arr2, but I loose the reference in referenceToArr1, because it still points to the original arr1.

With this way, I don't loose the reference:

var arr1 = [1,2,3];
var referenceToArr1 = arr1;
var arr2 = [4,5,6];

arr1.length = 0;
for (var i = 0; i < arr2.length; i++) {
  arr1.push(arr2[i]);
}
// logs: [4,5,6] true
console.log(arr1, arr1===referenceToArr1);
// logs: [4,5,6]
console.log(referenceToArr1)

The drawback here is, that I have to empty arr1.length = 0, iterate over every element of arr2 and push it to arr1 by hand.

My questions are:

  • Sure, I could write a helper for this, but what is the most efficient way?
  • Is there a short, vanilla javascript way, to do this (maybe a one liner?)
  • I'm also using underscore.js but haven't found a method for this kind of problem. Is there a way to do this with underscore.js?

Background:

I have an AngularJS app with a service. Inside this service, I have an array to keep all the elements loaded from the server. The data from the service get's bind to a controller and is used in a view. When the data from the service get's change (e.g. a refetch happens), I want to auto update my controllers vars and the view it is bind to.

Here is a Plunker: http://plnkr.co/edit/8yYahwDO6pAuwl6lcpAS?p=preview

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope, myService) {
  $scope.name = 'World';
  myService.fetchData().then(function(data) {
    console.log(data)
    $scope.data = data;
  })
});

app.service('myService', function($timeout, $q) {
  var arr;
  var deferred;

  var loadData = function() {
    // here comes some data from the server
    var serverData = [1,2,3];
    arr = serverData;
    deferred.resolve(arr);

    // simulate a refetch of the data
    $timeout(function() {
      var newServerData = [4,5,6];
      // this won't work, because MainCtrl looses it's reference
      // arr = newServerData;
    }, 1000);

    $timeout(function() {
      var newServerData = [7,8,9];
      arr.length = 0;
      [].push.apply(arr, newServerData);
    }, 2000);

    $timeout(function() {
      var newServerData = [10,11,12];
      [].splice.apply(arr, [0, arr.length].concat(newServerData));
    }, 3000);
  }

  return {
    fetchData: function() {
      deferred = $q.defer();

      loadData();

      return deferred.promise;
    }
  }
})

And the view:

<!DOCTYPE html>
<html ng-app="plunker">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.2.x" src="https://code.angularjs.org/1.2.16/angular.js" data-semver="1.2.16"></script>
    <script src="app.js"></script>
  </head>

  <body ng-controller="MainCtrl">
    <ul>
      <li ng-repeat="d in data">{{d}}</li>
    </ul>
    3 refetches every second
  </body>

</html>
23tux
  • 14,104
  • 15
  • 88
  • 187
  • See http://stackoverflow.com/questions/7486085/copying-array-by-value-in-javascript – elclanrs May 06 '14 at 05:38
  • 2
    @elclanrs this is exactly the opposite I want to achieve. They are copying arrays into new arrays. I want to replace the content of one array with another array without creating a new array. – 23tux May 06 '14 at 05:43
  • 1
    My intuition tells me this is an XY problem. Why do you need to mutate an array, or a variable like that? What is the real-world use case? – elclanrs May 06 '14 at 05:45
  • 1
    I added some background information and a plunker. I think this is a real world use case, isn't it? – 23tux May 06 '14 at 06:09
  • 1
    @23tux I just have to confirm: it is real world case! ;) had to solve almost the same... and the `arr1.length = 0` *(in fact something like `arr1.clear()`)* is the trick which solved lot of similar stuff for me – Radim Köhler May 06 '14 at 06:16
  • So it seems there's no workaround other that mutation, that's unfortunate, but good to know for reference. – elclanrs May 06 '14 at 06:41
  • Yes, it would be cool, if angularjs would provide some functionality built in to handle this. – 23tux May 06 '14 at 06:55

3 Answers3

13

What about this:

// 1. reset the array while keeping its reference
arr1.length = 0;
// 2. fill the first array with items from the second
[].push.apply(arr1, arr2);

See:

  1. How to empty an array in JavaScript?
  2. .push() multiple objects into JavaScript array returns 'undefined'
Community
  • 1
  • 1
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
6

You can use splice to replace the content of arr1 with the one of arr2 :

[].splice.apply(arr1, [0, arr1.length].concat(arr2));

This way, all references to arr1 would be correctly updated as this would be the same array with a new content.

As you see, that's possible and easy. But there's normally no reason to do this in a well designed program. If the reason is that you're passing an array to different places, then perhaps you should consider embedding this array in an object.

Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
  • Another question: What do you think is more efficient? Your solution or the other one from Radim Köhler? – 23tux May 06 '14 at 06:17
  • Probably the one of Radim but it doesn't matter (it you think it matters, profile), that's why I choose to answer with the one that could be written as a one-liner. I like one-liners... – Denys Séguret May 06 '14 at 06:22
  • 1
    In ES6 this can be written as `arr1.splice(0, arr1.length, ...arr2)` – ronen Nov 18 '19 at 08:04
0

This will merge both arrays persisting items from second array

[...arr2, ...arr1.slice(arr2.length)]
Luis Sardon
  • 516
  • 3
  • 8