18

I'm attempting to follow this SO answer explaining how to render a recursive JSON structure using a directive. However, unlike the answer provided, my data is not known when the DOM is loaded and Angular runs for the first time.

Instead, my data is retrieved from an HTML input field and stored in an Angular Service (when the user submits the form).

How can I keep an Angular Directive up-to-date when the Service's data is modified?


Update in response to answer

@musically_ut provided an excellent answer that has helped, but revealed a related problem, preventing an implementation (updated here).

The directive renders HTML that contains Angular {{expressions}} which access data stored in $scope. Since the original solution was to $watch when the service had it's data ready. How can I ensure the 'new' data is added to $scope before the directive renders?

An overview of the architecture and flow is:

  1. ControllerA -> Get input from user
  2. ControllerA -> Use service to transform data
  3. ControllerB -> $watch for changes in service
  4. Directive -> $watch for changes in service
  5. ControllerB -> Add data to $scope
  6. Directive -> Display transformed data (from service) using directives

The problem is between steps 5 and 6. The directive renders {{expressions}} before ControllerB has added the data to $scope. Even if this did work, it feels way too complex and 'hacky'.

In fact, to regress, I'm using $watch in ControllerB to listen for when the transformed data is ready in a service. Even this feels a little overkill (the service makes no asynchronous calls).

Community
  • 1
  • 1
Jack
  • 10,313
  • 15
  • 75
  • 118
  • I am new in angular, and get to your post by searching info about watching services changes in directives. As far as I understand you pb (and according to what I read recently), you should not 'add data to the scope' in your **ControllerB**, you better should report it to the **service**, and it will be automatically reported to the **controllerB** scope. By the way, you will be able to track **service** changes in your **directive** (I mean easily than tracking the 'external' **controllerB** scope.) But maybe I am wrong. – M'sieur Toph' Sep 29 '14 at 00:52

2 Answers2

17

You can inject the service while defining the directive and then set up a $watch on the data. So in your case:

.directive('tree', function ($compile, myService) {
    return {
        // ...
        link: function (scope, element, attrs) {
            scope.$watch(function () {
                return myService.getData();
            },
            function (newVal) {
                if (typeof newVal !== 'undefined') {
                    // ...
                }
            }
        });
    }
});

This will watch the data for change and run the code each time the data changes.


However, if the data does not change after being set once, a better way (more efficient) would be to return a promise from the service ($http methods returns a promise directly or you can create one using the $q service) and then add your computation as a continuation to the data.

.directive('tree', function ($compile, myService) {
    return {
        // ...
        link: function (scope, element, attrs) {
            myService.getData().then(function (data) {
                // ...
            }, function (err) {
                // Handle error
            });
        }
    }
});
GillesC
  • 10,647
  • 3
  • 40
  • 55
musically_ut
  • 34,028
  • 8
  • 94
  • 106
  • 1
    Thank you. However, the problem is that the **directive** renders **expressions** that make use of the JSON data in the $scope. The code for your **directive** runs when **service** updates, but what if it runs before the **service** data can be placed in $scope? This occurs because my data is retrieved from ControllerA, but the directives are rendered in ControllerB. I $watch `myService.getData()` in ControllerB and set the data in $scope. However, my directives are rendering before ControllerB adds the updated data to $scope. – Jack Nov 28 '13 at 12:33
0

I have written a short function that let you put data in Object without replace it. You can use it in you service. This way youu don't need to use watch. You can use the normal digest loop of Angular. For example, see this simple controller & service:

Demo: JSFidde - http://jsfiddle.net/y09kx7f7/1/ with assert tests

Controller:

app.controller('dem',function($scope,me){
    $scope.user=me.user;   // {name:'user name'}
})

The controller will be chnage automatically without watch every time the user name changed!

Service

.factory('me',function($timeout){
    var obj={}
    obj.user={name:'hello'}
    $timeout(function(){  //Example of change the data
        newUser={name:'israel'}
        cloneInto(obj.user,newUser)
    },1000)
    return obj
})

The cloneInto function that I written:

// The function it is like a=b, 
// but keeping the object in his original memory position.
function cloneInto(a,b){
    var i
    for (i in a){
        if(typeof a[i]!='object') //For supporting deep-copy
           delete a[i]
    }
    for (i in b){
        if (typeof b[i]=='object'){
            if(!a[i]) a[i]={}
            cloneInto(a[i],b[i])
        }
        else{
            a[i]=b[i]
        }
    }
}
Aminadav Glickshtein
  • 23,232
  • 12
  • 77
  • 117