AngularJS - Dependency injection involving asynchr

2019-07-21 07:57发布

问题:

I want to make the currently logged in user's ID and user name available to my Angular directives. I have created an API end-point to retrieve this information (along with some other information).

The problem is that the API call is asynchronous:

var url = baseUrl + 'api/sessions';
$http.get(url)

I am trying to create a factory that will return the JSON object returned by the API. However, since the call is asynchronous, I don't know how to implement the factory method.

Additionally, I don't want to instantiate the directives until after the value comes back from the API. Is there a mechanism built in to AngularJS that will make sure the asynchronous call has returned first, or should I just create a global object and update its values when the call returns (using the JSON object)?

This is what I currently have:

application.factory('session', ['$http', 'baseUrl', function ($http, baseUrl) {
    var session = {};
    var url = baseUrl + 'api/sessions';
    var promise = $http.get(url)
        .success(function (data) {
            for (var key in data) {
                session[key] = data[key];
            }
        });
    return session;
}]);

The problem is that any dependent directives are retrieving the session object before it is populated. I am concerned that if the API call takes a long time, the session object will be uninitialized when the user goes to use it.

回答1:

You could always use ngIf and wait for requirement to fulfill.

<div ng-if="session" session-toolbox></div>

This way you would have directive session-toolbox available in DOM, once session is set.

But if you gather this data outside of current scope, than you should $broadcast custom event and listen for that event inside desired controller.



回答2:

It turns out I was following a pretty nasty anti-pattern when it comes to AngularJS. I was binding attributes to functions in my HTML. Most often, this was in the form of ng-disabled="isThisFieldDisabled()".

The problem was I was calculating whether the field should be disabled every time the function was called. In cases where the data was loaded asynchronously, these functions would first need to check if the data had been loaded yet.

function ($scope, someFactory) {

    someFactory.then(function (data) {
        $scope.data = data;
    });

    $scope.isThisFieldDisable = function () {
        if (!angular.isDefined($scope.data)|| $scope.data === null) {
            return true;
        }
        return $scope.data.someCondition;
    };
}

A much easier approach is to simply update the condition when the data is returned from the call:

function ($scope, someFactory) {

    $scope.someCondition = true;

    someFactory.then(function (data) {
        $scope.someCondition = data.someCondition;
    });

    $scope.isThisFieldDisable = function () {
        return $scope.someCondition;
    };
}

Of course, once you are just returning the $scope value, the function becomes unnecessary. You can update the HTML like so ng-disabled="someCondition" and the controller ends up much more straight-forward:

function ($scope, someFactory) {

    $scope.someCondition = true;

    someFactory.then(function (data) {
        $scope.someCondition = data.someCondition;
    });
}

In most cases, I actually converted my $scope functions into local functions. They contained the logic for "calculating" the value of the backing field, so it made sense to call them from the $http callbacks. I usually had to add parameters since I wasn't dumping as much in the $scope anymore.