kSeudo
kSeudo

Reputation: 619

Detecting a change in a dependancy that is passed into a directive

I have a factory that I am passing into a directive. There is some data within the factory that gets updated via a function:

app.factory('CallsDataService', function() {

    var factory = {};

    //Contacts Handled data
    var data =  [
        {
            "id": "0",
            "handle": "A",
        },
        {
            "id": "1",
            "handle": "B",

        }
    ];


    factory.getData = function(){
        return data;
    };

    factory.updateData = function(value){
        data[0].handle= value);
    };

..............

In the directive it gets passed in as a dependancy:

app.directive('pieChart', ['CallsDataService', function(CallsDataService) {
    return {
        // use a new isolated scope
        restrict: 'AE',
        replace: 'true',
        scope:{
            templateUrl: "@"
        },
        link: function(scope,element,attrs){



            //Draw initial chart with default data
            drawChart();

            function drawChart() {
            }

....

My question is: if the data in the factory gets updated in factory via a call to the factory.updateData function in some other part of the code how can I detect this in the directive? I assume there has to be some use of $broadcast or $watch but I have had no success with this so far (needless to say I am new to Angular)

Any pointers would be appreciated.

Many thanks.

Upvotes: 0

Views: 53

Answers (3)

kSeudo
kSeudo

Reputation: 619

Thanks guys for your help. It turns out that I needed to add a timeout delay when sending the broadcast in order for the $watch function to pick up the change. This was why my previous efforts were failing. I am using cometd to retrieve data and I guess this might be causing the need to add the delay.

In the factory:

factory.updateData = function(value){
    data[0].teamFormations = Math.abs(value.rows[0].fields[0].v);
    $timeout(function() {
        $rootScope.$broadcast('updatedData1', data);
        console.log('update with timeout fired')
    }, 1000);

};

Many thanks,

Brian.

Upvotes: 0

Simon Staton
Simon Staton

Reputation: 4505

You can still use scope.$watch in your directive however you just need to return your factory value...

scope.$watch(function(){
    return MyFactory.getData()[0].handle;
}, function(newVal, oldVal){
    console.log(newVal, oldVal);
});

http://jsfiddle.net/q12ny8qq/

When a digest cycle starts angular will evaluate your scope.$$watchers which will return your factory data, it will then compare to the previous cached value and trigger your callback if it has changed.

Upvotes: 1

yangli-io
yangli-io

Reputation: 17354

The most broad way to do this is to use a $broadcast although this may not be the most efficient way, it usually gets the job done.

Note, $broadcast works by sending the data to child nodes and $emit works by sending the data to parent nodes. The node in reference here is usually the element the directive is attached to (or where the controller is). Since factories/services are not attached to any scope/element, we need to inject an element. The easiest way to achieve this is to inject the $rootScope service and $broadcast from there.

Step 1. Add $rootScope

app.factory('CallsDataService', function($rootScope) {
    ...
});

Step 2. Add $broadcast to method

factory.updateData = function(value){
    data[0].handle = value;
    $rootScope.$broadcast('updatedData', data);  //Note data here should be an object you want to pass to the listeners.
};

Step 3. Listen for event in directive

link: function(scope,element,attrs){
    scope.$on('updateData', function(data){
        //Do whatever you like here
    })
}

Keep in mind that there are better ways to do this through a better design process and if you're designing an performant app then you would try to avoid $broadcasting or $watching. However, if you're starting out and performance of the app is not exactly key (meaning that your app won't have a million things on it) then it should be perfectly fine.

Upvotes: 1

Related Questions