Caleb Faruki
Caleb Faruki

Reputation: 2725

Prevent $scope variable copy from listening to its original

In my AngularJS app, I generally pass data using the $rootScope. In the various states, I manipulate pull them into $scope when necessary. But since $scope has prototypical inheritance, I suppose this is just for readability and not much else. In my app, there are the following variables...

$rootScope.list // object
$rootScope.list.items // array
$rootScope.additions // array
$rootScope.cart // object
$rootScope.cart.items // array

Before I begin, this is generally laid out in pseudocode. So if it's not correct, that's okay. I'm looking for a conceptual understanding.

At first, the cart is empty. You look at list.items and copy individual list items into the cart.items array. This works great, until we start talking about duplicate items with unique additions.

Say, I wanted to have 2 of the same item, except one item has different additions (cart items have a quantity property, not list items).

$rootScope.cart.items = [
   {
      name: 'listItem1',
      quantity: 3,
      additions: ['addition1']
   }, {
      name: 'listItem2',
      quantity: 1,
      additions: ['addition2']
   }, {
      name: 'listItem1', // same as cart.items[1], but additions has diff values
      quantity: 1,
      additions: ['addition1','addition2']
   }];

Firstly, AngularJS complained because there were duplicates in my ng-repeater. I resolved that issue by appending track by $index in the ng-repeat directive. It was also the first red flag I saw that told me I was doing something wrong.

My first idea was to create a temporary cartItem, arbitrarily add its additions, then push it onto $rootScope.cart.items[]. But when it's time to push the 3rd cart item onto the cart, which is another copy of the 1st list item, it overwrites the additions[] of the first cart item. So, $rootScope.cart.items looks like this:

$rootScope.cart.items = [
   {
      name: 'listItem1',
      quantity: 3,
      additions: ['addition1','addition2']
   }, {
      name: 'listItem2',
      quantity: 1,
      additions: ['addition2']
   }, {
      name: 'listItem1', // same as cart.items[1], but additions has diff values
      quantity: 1,
      additions: ['addition1','addition2']
   }];

From this result, it's ostensibly pass-by-reference when it comes to copying $rootScope variables from one place to another.

So, my next thought was to try using a directive with an isolate scope. But I realized soon enough that my logic was faulty. While an isolate scope would give me one-way data-binding, it wasn't in the direction I was hoping for. The scope parent to the directive would still be able to set values.

What I would ideally like to do is arbitrarily add list.items to cart.items. But any cart items that come from the same list item, I would like to be unbound from each other. Thus, additions in one copy can be different from additions in another copy. What would be the best way to achieve this? I have a feeling it might have been a poor decision to use $rootScope to pass data between states in my app, though it is the fastest way I have found.

Upvotes: 0

Views: 198

Answers (2)

ribsies
ribsies

Reputation: 1218

Angular copy is what you are looking for.

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

There are a couple methods to use this that you will see in the docs, but my coworkers and I have found better use out using the optional 'destination' option.

So this is how it works

First setup your new target array.

var newArray = [];

Then you use that as the destination in angular copy

angular.copy(firstArray, newArray);

There you go, it is just that easy!

Now your new array will no longer be connected to the original array.

I would also look into using factories as services for your code instead of using rootScope. Learning that will increase your productivity a ton and make your code a lot more readable and user friendly. Here is a good article about it.

http://tylermcginnis.com/angularjs-factory-vs-service-vs-provider/

Edit

And as someone mentioned in the comments, it is indeed a deep copy. That means it will copy all children objects and arrays inside of an array.

Upvotes: 1

floribon
floribon

Reputation: 19193

When you edit the additions of your first cart item, you actually edit the additions of both the first and third cart items, because they are the same.

When you create the third cart item, instead, you could copy the properties of the first one:

var newCartItem = {};

newCartItem.name = firstCartitem.name;
newCartItem.quantity = firstCartitem.quantity;
// Do not point to the other array, instead create a new one and copy over
newCartItem.additions = [];
for (var i = 0; i < firstCartitem.additions.length; i++) {
  newCartItem.additions.push( firstCartitem.additions[i] );
}
// Now you can add anything to the new cart item without editing the first one    

Upvotes: 0

Related Questions