2

I've encounter a strange behavior happening in an Angular's directive I don't understand at all; let me just explain it using code, I think is the easiest way.

What does work:

.directive('myDirective', function ($http) {
    return {
        template: '...',
        link: function (scope, elem, attrs) {
            function getData() {
                console.log('Starting...');
                $http.get(...).success(/* update the template */);
            }
            getData();
        }
    };
});

What does not:

.directive('myDirective', function ($http) {
    return {
        template: '...',
        link: function (scope, elem, attrs) {
            function getData() {
                console.log('Starting...');
                $http.get(...).success(/* update the template */);
            }
            scope.$on('callMyDirective', getData);
        }
    };
});

The $http call is complete ignored in the second example, not even printing a thing in the finally() part of the promise (there is indeed no network activity), while the log is properly printed in both cases (in the second case, of course, after triggering the event, by clicking a button).

Any clue?

Thanks a lot.

Update:

I found the source of the error, but it is in turn another problem. The thing is that I have an Interceptor where I wrap all the requests but the one which is supposed to authenticate the user with a promise, which I resolve only once the authentication ends. So my interceptor looks like this:

request: function (config) {
    if (userHasArrived || isAuthUrl) {
        return config || $q.when(config);
    }

    var defer = $q.defer();

    $rootScope.$on('user_logged_in', function (user) {
        console.log('User logged in');
        userHasArrived = true;
        defer.resolve(config);
    });
    return defer.promise;
}

At the same time, I have a service that runs the auth request, and broadcast the event shown above on success. This is working for all the other requests but for the one I have the directive I mentioned above, which is in a different "page", and never receives the event in the Interceptor (the log never gets printed).

Only when I force a second request to my "auth" URL, the event is received, and the directive stuff works without problems. Could it be that the services is loaded before the interceptor somehow? And in that case, is there a way to control this load order?

Update:

Ok, sorry for bothering you, but if someone is still interested, I found the solution to the second problem. This was the order in which things were happening:

  1. Authentication request starts
  2. Authentication request ends, success is broadcasted
  3. Directive request starts (on demand, clicking a button)
  4. Authentication success starts to being listened

So the event was being fired before anyone listened to it. This would be the final code (an incomplete version of it):

$rootScope.$on('user_logged_in', function (user) {
    userHasArrived = true;
});

return {
    request: function (config) {
        if (userHasArrived || isAuthUrl) {
            return config || $q.when(config);
        }

        var defer = $q.defer();

        var $off = $rootScope.$on('user_logged_in', function (user) {
            userHasArrived = true;
            $off(); // You better unsubscribe for the events too, if you do something along this lines
            defer.resolve(config);
        });
        return defer.promise;
    }
};
DanielM
  • 1,106
  • 3
  • 17
  • 27
  • you should probably use the `controller` property rather than `link` – Callum Linington Aug 04 '15 at 09:50
  • Are you getting any error? – Boaz Aug 04 '15 at 09:50
  • I think this is more to do with how the event `callMyDirective` is broadcasted. Is it done from the `$rootScope`? – Callum Linington Aug 04 '15 at 09:52
  • In order... Why should I use the controller function in this case?; I'm not getting any error; and yes, I'm broadcasting the event from the $rootScope. – DanielM Aug 04 '15 at 09:57
  • By the way... I'm confused now with this concept of "controller property" in a directive. I know the two options that can be used are link and compile, did you mean compile there? – DanielM Aug 04 '15 at 09:59
  • Where do you `$broadcast` the `callMyDirective` from? Somewhere in a controller or other JavaScript code you must call `$rootScope.$broadcast("callMyDirective")` or `$scope.$emit("callMyDirective")`depending on current scope's level in the DOM. Have a look at [broadcast and emit](http://stackoverflow.com/questions/14502006/working-with-scope-emit-and-on) – AndersRehn Aug 04 '15 at 10:00
  • No, there are different phases of a directive, first it gets compiled (this phase is for DOM manipulation), then the linking phase, then during normal operation of a directive use the controller function. That is what UI router does etc.. – Callum Linington Aug 04 '15 at 10:02
  • Yes, I'm taking the first option you mention: I have a controller, which has a function, which broadcast the event the directive is listening at, and which is bound to an event click of a button, so I can control when it happens. And as I explained, that part is working: the log message is displayed in both cases. – DanielM Aug 04 '15 at 10:03
  • Callum Linington, could you refer to an example / documentation of this? My Angular theory lacked of this particular point, actually. – DanielM Aug 04 '15 at 10:04
  • [AngularJs Doc](https://docs.angularjs.org/api/ng/service/$compile) – Callum Linington Aug 04 '15 at 10:06
  • 1
    Could you repro this in plnkr or fiddle or something – Callum Linington Aug 04 '15 at 10:06
  • I'm not sure I can reproduce this very easily, but I'll try. I tried already moving from link to controller, but I guess it's not as simple as changing a couple of characters (I got this DI error: https://docs.angularjs.org/error/$injector/unpr?p0=scopeProvider). – DanielM Aug 04 '15 at 10:19
  • Can you check if the event raised is handled in the code that does not work? I think the event handler itself is not called and thus the HTTP request does not execute – callmekatootie Aug 04 '15 at 10:25
  • make sure you use `$scope` instead of just `scope` in the controller because the controller is created using DI unlike link and compile – Callum Linington Aug 04 '15 at 10:37
  • I mean, I'm not the smartest person in the world, but what's the point of answering a question without even reading it, callmekatootie? – DanielM Aug 04 '15 at 12:34
  • Anyway, I did the Plunker (http://plnkr.co/edit/hygZacxdtBwO4OsCvlGA), but it is -weird to say- unfortunately working. Which at least makes me focus in other possible sources of the problem, so thank you, Callum. There are other factors involved here, such as the new and incomplete angular router, which gave me other problems in the past (I was hoping they release a stable version soon, but it's taking a long time), or the HighCharts library, which is essentially the DOM modification I'm trying to make after the HTTP arrives with the data. – DanielM Aug 04 '15 at 12:35
  • And sorry, I didn't clarify I still got errors when changing from scope to $scope, from elem to $element, and so on. Thanks for the tip anyway. I'll probably try to solve this in a different way. – DanielM Aug 04 '15 at 12:39

1 Answers1

0

I hope this works

.directive('myDirective', function ($http) {
    return {
        template: '...',
        link: function (scope, elem, attrs) {
             scope.getData = function () {
                console.log('Starting...');
                $http.get(...).success(/* update the template */);
            }
            scope.$on('callMyDirective', function (event, args) {
              scope.getData();
            });
        }
    };
});

or other alternative

.directive('myDirective', function ($http) {
    return {
        template: '...',
        link: function (scope, elem, attrs) {
            var _this = this;
            function getData() {
                console.log('Starting...');
                $http.get(...).success(/* update the template */);
            }
            scope.$on('callMyDirective', function (event, args){
               $scope.$apply(function (){
                 _this.getData();
               })
             });
        }
    };
});
Amerrnath
  • 2,227
  • 4
  • 22
  • 33