javascriptangularjsangularjs-components

$onChanges not triggered


Get updated by model but don't update the model. Should I use the $onChanges function

I have a model:

class Model {
  constructor(data) {
    this.data = data;
  }
  getData() {
    return this;
  }
}

2 nested components:

var parentComponent = {
  bindings: {
    vm: '<'
  },
  controller: function() {
    var ctrl = this;
  },
  template: `
    <div>
      <a ui-sref="hello.about" ui-sref-active="active">sub-view</a>
      Parent component<input ng-model="$ctrl.vm.data">
      <ui-view></ui-view>
    </div>
  `
};
var childComponent = {
  bindings: {
    vm: '<'
  },
  template: `
    <div>
      Child component <input ng-model="$ctrl.vm.data">
      <br/>
      Child component copy<input ng-model="$ctrl.vmCopy.data">
      <br/>
      Child component doCheck<input ng-model="$ctrl.vmCheck.data">
      </div>
  `,
  controller: function() {
    var ctrl = this;
    ctrl.$onChanges = function(changes) {
      ctrl.vmCopy = angular.copy(ctrl.vm);
      ctrl.vm = ctrl.vm;
    };
    ctrl.$doCheck = function () {
      var oldVm;
      if (!angular.equals(oldVm, ctrl.vm)) {
        oldVm = angular.copy(ctrl.vm);
        ctrl.vmCheck = oldVm;
        console.log(ctrl)
      }
    }
  }
}

Both get data from resolve:

.config(function($stateProvider) {
    var helloState = {
      name: 'hello',
      url: '/hello',
      resolve: {
        vm: [function() {
          return myModel.getData();
        }]
      },
      component: 'parent'
    }

    var aboutState = {
      name: 'hello.about',
      url: '/about',
      resolve: {
        vm: [function() {
          return myModel.getData();
        }]
      },
      component: 'child'
    }
    $stateProvider.state(helloState);
    $stateProvider.state(aboutState);

  })

I would like my components to be updated when model change or when parent change, but I don't wan't them to update the model.

I thought that was why one way binding '<' stands for.

Here's a fiddle to illustrate what I want to do.

In other word: I would like the child component to be updated on parent changes but don't want the child to update the parent.

You can see in the fiddle that if I bind directly to local scope, child get update from parent but also update the parent

If I copy the binding to local scope, child isn't updating the parent but also doesn't get updated by parent. If


Solution

  • With object content ā€” Use the $doCheck Life-cycle Hook1

    When binding an object or array reference, the $onChanges hook only executes when the value of the reference changes. To check for changes to the contents of the object or array, use the $doCheck life-cycle hook:

    app.component('nvPersonalTodo', {
      bindings: {
        todos: "<"
      },
      controller: function(){
        var vm = this;
        this.$doCheck = function () {
          var oldTodos;
          if (!angular.equals(oldTodos, vm.todos)) {
            oldTodos = angular.copy(vm.todos);
            console.log("new content");          
            //more code here
          };
        }
    })
    

    From the Docs:

    The controller can provide the following methods that act as life-cycle hooks:

    • $doCheck() - Called on each turn of the digest cycle. Provides an opportunity to detect and act on changes. Any actions that you wish to take in response to the changes that you detect must be invoked from this hook; implementing this has no effect on when $onChanges is called. For example, this hook could be useful if you wish to perform a deep equality check, or to check a Date object, changes to which would not be detected by Angular's change detector and thus not trigger $onChanges. This hook is invoked with no arguments; if detecting changes, you must store the previous value(s) for comparison to the current values.

    ā€” AngularJS Comprehensive Directive API Reference -- Life-cycle hooks

    For more information,