Cheshire Katzzze
Cheshire Katzzze

Reputation: 127

How $watch changes of a variable in a service from component's controller?

I have been through all related topics on SO, namely these two:

$watch not detecting changes in service variable

$watch not detecting changes in service variable

are tackling the same issue, but i failed to make it working. Unlike in the above cases, I am using a controller from a component, hence maybe this is related to lacking binding in components, idk. Hope for some experinced assistance.

Have a service:

(function (angular) {
    'use strict';

    angular
        .module('Test')
        .service('ShareData', ShareData);

    ShareData.$inject = [];

    function ShareData() {
        let vm = this;

        vm.indexes = [];

        vm.setIndexes = function(firstIndexParam, lastIndexParam, message) {
            if (leaderIndexParam !== undefined || partnerIndexParam !== undefined) {
                vm.indexes.mainIndex = firstIndexParam;
                vm.indexes.secondaryIndex = lastIndexParam;
                vm.indexes.message = message;
            }
        };

        vm.getIndexes = function() {
            return vm.indexes;
        };
    }
})(angular);

It is used in 3 components. Two of them are sending data into the service, the third one uses this data. Sending of data is accomplished in the following way, works:

ShareData.setIndexes(firstIndex, secondIndex, 'update_indexes');

Now here is my problem. In main parent controller i can comfortably access the data by

ShareData.getIndexes();

But my issue is that I need changes in indexes to trigger certain actions in parent controller, so I tried so do as stipulated by relevant questions here on SO:

$scope.$watch('ShareData.getIndexes()', function(newVal) {
   console.log('New indexes arrived', newVal);
});

In main controller, I am injecting the service:

TabController.$inject = ['ShareData'];

and using it like:

let indexService = ShareData.getIndexes();

As i said, I can get the data when I am explicitly calling the function. The issue is that it needs to be triggered by the service itself.

It does not work regardless of shamanistic ceremonies a made :) Now, obviously, I am missing something. Should I somehow bind this service to the component, and if yes how is it done? Or maybe the solution is totally dysfunctional and impossible to achieve in my circumstances? An advise is appreciated!

UPDATE: I already have a functional solution with the same service working with $rootScope.$broadcast, however my aim is to get rid of it and not work with the $rootScope.

Upvotes: 0

Views: 53

Answers (1)

raina77ow
raina77ow

Reputation: 106463

The problem is that you never actually change the value of vm.indexes - it always points to the same array. setIndexes only modifies properties of this array. That's why $watch, which by default checks for reference equality only, fails to spot the changes.

There are (at least) two ways of solving this: either make $watch check the object equality instead, by adding a third param there:

$scope.$watch('ShareData.getIndexes()', function(newVal) {
   console.log('New indexes arrived', newVal);
}, true);

... or (better, in my opinion) rewrite the set function so that it'll create a new instance of indexes instead when there's a change:

vm.setIndexes = function(firstIndexParam, lastIndexParam, message) {
   if (leaderIndexParam === undefined && partnerIndexParam === undefined) {
     return;  
   }
   vm.indexes = vm.indexes.slice();
   Object.assign(vm.indexes, {
     mainIndex: firstIndexParam, 
     secondaryIndex: lastIndexParam, 
     message: message
   });
 };

As a sidenote, simply calling setIndexes() does not trigger the digest - and $watch listener only checks its expression when digest is triggered.

Upvotes: 2

Related Questions