$watch not firing on data change
Asked Answered
C

5

59

I have a watch setup against the model of a ui-select2 dropdown (from ui-bootstrap). The watch fires on load but not on data changes and I can't figure out why.

It isn't the usual problem of not $apply'ing the model change or not using the third parameter for equality comparison (at least from my code).

What do I need to do to get it to fire?

Here is a plunk demonstrating the issue.

Coition answered 22/7, 2013 at 18:46 Comment(1)
I had a $watch not firing but turns out I had an ng-if that was creating a new scope, so I needed to put ng-model="$parent.myModal", since I was actually watching the parents myModal..Jaan
S
36

I fixed some stuff.

http://plnkr.co/edit/5Zaln7QT2gETVcGiMdoW?p=preview

The JS

var myMod = angular.module("myApp",[]).controller("MainController",  function($scope){
  $scope.myModel = {selectedId:null};
}).controller("DetailController",function($scope){
  $scope.items = [1,2,3,4];

  $scope.watchHitCount = 0;
  $scope.$watch('myModel.selectedId', function(newVal, oldVal){
    console.log(newVal + " " + oldVal);
    $scope.watchHitCount++;
  },true);
});

The index.html

  <body ng-app="myApp">
    <div ng-controller="MainController">
      <ng-include src="'detail.html'" ng-controller="DetailController"></ng-include>
    </div>
  </body>

The detail.html

<pre>watch hit: {{watchHitCount}}</pre>
<pre>selected value: {{myModel.selectedId}}</pre>
<select ng-model="myModel.selectedId" ui-select2="">
  <option></option>
  <option ng-repeat="item in items" value="{{item}}">{{item}}</option>
</select>

It was complaining about not finding the controller so I set it up the way I normally would with a named ng-app and a module declared that has controllers defined on it.

I also added an object to hold the value in your model. It is bad practice to use the $scope object as your model, instead your scope should refer to an object that is your model.

Spoonbill answered 22/7, 2013 at 18:57 Comment(3)
So this worked previously for me without using an object... At some point this broke without me making direct changes to the filters. I may have added some rootscope variables elsewhere and some watchers elsewhere in the app template components.. Is there a common reason why I should be using objects rather than just variables, like in this solution?Iggie
@Spoonbill can you please explain why variable wont worked any object workedErastianism
@parshuram if you look up mtv best practices angular you'll get a video from misko around 30 min in he explains this a bit.Spoonbill
F
124

Try passing true as a 3rd argument to .$watch()

$rootScope.Scope documentation

$watch(watchExpression, listener, objectEquality)

objectEquality(optional) – {boolean=} – Compare object for equality rather than for reference.

Faustino answered 22/7, 2013 at 18:53 Comment(0)
S
36

I fixed some stuff.

http://plnkr.co/edit/5Zaln7QT2gETVcGiMdoW?p=preview

The JS

var myMod = angular.module("myApp",[]).controller("MainController",  function($scope){
  $scope.myModel = {selectedId:null};
}).controller("DetailController",function($scope){
  $scope.items = [1,2,3,4];

  $scope.watchHitCount = 0;
  $scope.$watch('myModel.selectedId', function(newVal, oldVal){
    console.log(newVal + " " + oldVal);
    $scope.watchHitCount++;
  },true);
});

The index.html

  <body ng-app="myApp">
    <div ng-controller="MainController">
      <ng-include src="'detail.html'" ng-controller="DetailController"></ng-include>
    </div>
  </body>

The detail.html

<pre>watch hit: {{watchHitCount}}</pre>
<pre>selected value: {{myModel.selectedId}}</pre>
<select ng-model="myModel.selectedId" ui-select2="">
  <option></option>
  <option ng-repeat="item in items" value="{{item}}">{{item}}</option>
</select>

It was complaining about not finding the controller so I set it up the way I normally would with a named ng-app and a module declared that has controllers defined on it.

I also added an object to hold the value in your model. It is bad practice to use the $scope object as your model, instead your scope should refer to an object that is your model.

Spoonbill answered 22/7, 2013 at 18:57 Comment(3)
So this worked previously for me without using an object... At some point this broke without me making direct changes to the filters. I may have added some rootscope variables elsewhere and some watchers elsewhere in the app template components.. Is there a common reason why I should be using objects rather than just variables, like in this solution?Iggie
@Spoonbill can you please explain why variable wont worked any object workedErastianism
@parshuram if you look up mtv best practices angular you'll get a video from misko around 30 min in he explains this a bit.Spoonbill
K
12

There are a simple fix for that, to use watch with complex object instead of simple variable

For example (DON"T USE)

$scope.selectedType=1;//default
$scope.$watch(
            function () {
                return $scope.selectedType;
            },
            function (newValue, oldValue) {
                if (!angular.equals(oldValue, newValue)) {
                    $scope.DoWork();
                }
            },
            true);

But Use below

$scope.selecteditem={selectedType:1};
$scope.$watch(
            function () {
                return $scope.selecteditem.selectedType;
            },
            function (newValue, oldValue) {
                if (!angular.equals(oldValue, newValue)) {
                    $scope.DoWork();
                }
            },
            true);

note that "slectedTypes" in the second example located inside object, not just scope variable. this will work even in old Angular versions.

Kroon answered 30/12, 2015 at 16:7 Comment(1)
I think my answer could work, but "@Casey Flynn" 's answer is more accurate and precise . https://mcmap.net/q/327105/-watch-not-firing-on-data-changeKroon
B
7

If you're using the controller-as approach some reading might suggest a syntax like this:

var myController = {
    myValue: 1
};
$scope.$watch('$ctrl.myValue', function () {
    ...
}, true);

Instead just wrap the field in a function like this:

var myController = {
    myValue: 1
};
$scope.$watch(function () {
    return myController.myValue;
}, function () {
    ...
}, true);
Brigandine answered 7/4, 2017 at 10:9 Comment(0)
B
0

The only thing that worked for me:

  var ctrl = this;

  this.$onInit = function () {
    console.log("id " + ctrl.event.id);
  };

from https://docs.angularjs.org/guide/component#!

Beaufert answered 1/2, 2021 at 3:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.