最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - AngularJS: Databinding between Arrays - Stack Overflow

programmeradmin6浏览0评论

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?

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?

Share Improve this question edited Dec 13, 2016 at 8:53 H W asked Dec 13, 2016 at 8:42 H WH W 2,5963 gold badges24 silver badges45 bronze badges 1
  • It's best if you avoid extra $watches as some of the answers here have shown. It's a workaround but on the cost of performance when scaling. Angular does provides one way data binding. Here's a link if you'd like to know how it works toddmotto./one-way-data-binding-in-angular-1-5 – Amin Mohamed Ajani Commented Dec 19, 2016 at 18:19
Add a ment  | 

4 Answers 4

Reset to default 3 +25

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

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 parison 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 parison, the angular.copy function is used. This therefore means that watching plex 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 pare the newVal and oldVal. If these two values are identical (===) then the listener was called due to initialization.

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./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>

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).
发布评论

评论列表(0)

  1. 暂无评论