Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS: Databinding between Arrays

Given an Array sourceArray I would like to create a targetArray that depends on the entries of the first one. This means that the created Array should contain an entry for each of the source-entries and is updated whenever sourceArray changes. However modifying the targetArray should never update the source.

This Plunker kind of works as long as sourceArray is static. As soon as you start to modify the source-entries, it obviously won't update the target properly because i am lacking a proper databinding mechanism.

Do i need to update targetArray manually by observing sourceArray or is there any kind of 1-way-databinding mechanism implemented by Angular which i can use to keep two arrays synchronized?

like image 548
H W Avatar asked Dec 13 '16 08:12

H W


4 Answers

As Pritam said. You should Use $watch. But must bind the Collection to it, in order to make it work. And inside the watch merge the arrays.

Find this working sample:

$scope.$watchCollection(angular.bind(this, function () {
    return this.sourceArray;}), function (newVal, oldVal) {

      var arr = [];
      for(var i in vm.sourceArray){
         var shared = false;
         for (var j in vm.targetArray)
             if (vm.targetArray[j].id == vm.sourceArray[i].id) {
                 shared = true;
                 break; 
             }
         if(!shared) arr.push(vm.sourceArray[i])
      }
      console.log(arr);
      vm.targetArray = vm.targetArray.concat(arr);
    },true);

http://plnkr.co/edit/E2inRLtwfWnb1VBymNNl?p=preview

like image 109
Manu Obre Avatar answered Oct 17 '22 23:10

Manu Obre


You should use $watch. And then add the necessary function.

You can look here and the official documentation.

$watch(watchExpression, listener, [objectEquality]); Registers a listener callback to be executed whenever the watchExpression changes.

The watchExpression is called on every call to $digest() and should return the value that will be watched. (watchExpression should not change its value when executed multiple times with the same input because it may be executed multiple times by $digest(). That is, watchExpression should be idempotent.) The listener is called only when the value from the current watchExpression and the previous call to watchExpression are not equal (with the exception of the initial run, see below). Inequality is determined according to reference inequality, strict comparison via the !== Javascript operator, unless objectEquality == true (see next point) When objectEquality == true, inequality of the watchExpression is determined according to the angular.equals function. To save the value of the object for later comparison, the angular.copy function is used. This therefore means that watching complex objects will have adverse memory and performance implications. This should not be used to watch for changes in objects that are or contain File objects due to limitations with angular.copy. The watch listener may change the model, which may trigger other listeners to fire. This is achieved by rerunning the watchers until no changes are detected. The rerun iteration limit is 10 to prevent an infinite loop deadlock. If you want to be notified whenever $digest is called, you can register a watchExpression function with no listener. (Be prepared for multiple calls to your watchExpression because it will execute multiple times in a single $digest cycle if a change is detected.)

After a watcher is registered with the scope, the listener fn is called asynchronously (via $evalAsync) to initialize the watcher. In rare cases, this is undesirable because the listener is called when the result of watchExpression didn't change. To detect this scenario within the listener fn, you can compare the newVal and oldVal. If these two values are identical (===) then the listener was called due to initialization.

like image 44
Pritam Banerjee Avatar answered Oct 17 '22 23:10

Pritam Banerjee


Here's a snippet I made. Note when changing source array both arrays are affected, however when you change only the target the source stays intact.

angular.module('app', [])
  .controller('mainCtrl', function($scope) {
    var vm = this;
    vm.sourceArray = [];
    vm.source = '["change me!",{"a":3},[100]]';

    $scope.$watch('vm.source', function(newVal) {
      try {
        vm.sourceArray = JSON.parse(newVal);
        vm.target = newVal;
        vm.serr = null;
      } catch (e) {
        vm.serr = 'Invalid JSON';
      }
    });
  
  $scope.$watch('vm.target', function(newVal) {
      try {
        vm.targetArray = JSON.parse(newVal);
        vm.terr = null;
      } catch (e) {
        vm.terr = 'Invalid JSON';
      }
    });

    //Copy whole array on change
    $scope.$watch('vm.sourceArray', function(newVal) {
      vm.targetArray = angular.copy(newVal);
    }, true);

    return this;
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="mainCtrl as vm">
  <span>Change the inputs one at a time to see the changes take effect</span>
  <h5>Source:</h5>
  <input type="text" ng-model="vm.source" ng-model-options="{debounce: 300}" placeholder="Enter json formatted string for source array"><span>{{vm.serr}}</span>
  <div>Model: {{vm.sourceArray|json:null:2}}</div>
  <br>
  <h5>Target:</h5>
  <input type="text" ng-model="vm.target" ng-model-options="{debounce: 300}" placeholder="Enter json formatted string for source array"><span>{{vm.terr}}</span>
  <div>Model: {{vm.targetArray|json:null:2}}</div>
</div>
like image 27
Muli Yulzary Avatar answered Oct 17 '22 22:10

Muli Yulzary


You can use $watch expression.

Here is another way:-(download underscore.js or CDN)

http://plnkr.co/edit/hrOrEdaQ0M7wEgWlRHlO?p=preview

  1. angular.js copy (angular.copy()) method.
  2. underscore.js extend method.

     var app = angular.module('plunker', []);
        app.controller('MainCtrl', function($scope) {
            var vm = this;
            vm.sourceArray = [{id: '0', name: 'someObject'}, {id: '1', name: 'anotherObject'}];
            vm.targetArray = angular.copy(vm.sourceArray);
           // angular.copy(vm.sourceArray, vm.targetArray);
            vm.push = function(){
              let found = false;
              angular.forEach(vm.sourceArray, function(el){
                if (el.id === vm.id){
                  el.name = vm.name;
                  found = true;
                }
              });
              if (!found){
                vm.sourceArray.push({id: vm.id, name: vm.name});
               _.extend(vm.targetArray, vm.sourceArray);
              }
    
        };
    
    
    
    vm.pushTarget = function(){
      let found = false;
      angular.forEach(vm.targetArray, function(el){
        if (el.id === vm.id1){
          el.name = vm.name1;
          found = true;
        }
      });
      if (!found){
        console.log({id: vm.id, name: vm.name})
        vm.targetArray.push({id: vm.id1, name: vm.name1});  
      }
    
    };
    

    });

you can get underscore.js code:-

_.extend = createAssigner(_.allKeys);

// An internal function for creating assigner functions.
var createAssigner = function(keysFunc, undefinedOnly) {
    return function(obj) {
      var length = arguments.length;
      if (length < 2 || obj == null) return obj;
      for (var index = 1; index < length; index++) {
        var source = arguments[index],
            keys = keysFunc(source),
            l = keys.length;
        for (var i = 0; i < l; i++) {
          var key = keys[i];
          if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
        }
      }
      return obj;
    };
  };

  // Retrieve all the property names of an object.
  _.allKeys = function(obj) {
    if (!_.isObject(obj)) return [];
    var keys = [];
    for (var key in obj) keys.push(key);
    // Ahem, IE < 9.
    if (hasEnumBug) collectNonEnumProps(obj, keys);
    return keys;
  };

   // Extend a given object with all the properties in passed-in object(s).
like image 24
Manikandan Velayutham Avatar answered Oct 17 '22 21:10

Manikandan Velayutham