Controlling order of operations with services and controllers

≯℡__Kan透↙ 提交于 2019-12-11 11:19:27

问题


I have two services - one to store user details and the other to make a call to retrieve those details:

userService stores user details to be used across the entire app (i.e. injected in controllers, services, etc.)

function userService($log) {        
    var id = '';
    var username = '';
    var isAuthenticated = false;

    var service = {
        id: id,            
        username: username,
        isAuthenticated: isAuthenticated
    };

    return service;
}

authService is used (hopefully just once) to retrieve the user details from a Web API controller:

function authService($log, $http, userService) {
    $log.info(serviceId + ': Inside authService method');

    var service = {
        getUserDetails: getUserDetails
    };

    return service;

    function getUserDetails() {
        $log.info(serviceId + ': Inside getUserDetails method');

        return $http.get('api/authentication', { cache: true });
    }
}

Initially, I had the call to the authService fire in a .run block like so:

.run(['$log', 'authService', 'userService', function ($log, authService, userService) {

    authService.getUserDetails()
        .then(querySucceeded);

    function querySucceeded(result) {
        userService.id = result.data.Id;
        userService.username = result.data.username;
    }
}]);

But the problem was that the getUserDetails-returned promise did not resolve until after I my controllers fired and, thus, too late for me. The user data was not ready.

I then looked at the resolve option in the $stateProvider (for UI-Router):

.state('dashboard', {
                url: '/dashboard',
                views: {
                    header: {
                        templateUrl: 'app/partials/dashboard/header.template.html',
                        controller: 'DashboardHeaderController',
                        controllerAs: 'dashboardHeaderVM',
                        resolve: {
                            user: function (authService) {
                                return authService.getUserDetails();
                            }
                        }
                    }
                }
            })

The assumption is that the view won't be rendered until the promise in the resolve section is, well, resolved. That seems to work fine.

Here's the (relevant part of the) controller where I use the returned user property:

function DashboardHeaderController($log, user) {
    var vm = this;

    // Bindable members        
    vm.firstName = user.data.firstName;
}

However, I have two routes (more to come) and a user can navigate to either one. Do I need to have a resolve property in each state section for the authService? I want to fire the call to authService.getUserDetails just once no matter which route is served and have it available after that for any route, controller, etc.

Is there a better (best practice) way to do this?


回答1:


Not sure about better or best practice, but here is a plunker with my way.

The point is to move resolve into some parent root state. The one who is ancestor of all states in the application:

$stateProvider
  .state('root', {
    abstract : true,
    // see controller def below
    controller : 'RootCtrl',
    // this is template, discussed below - very important
    template: '<div ui-view></div>',
    // resolve used only once, but for available for all child states
    resolve: {
      user: function (authService) {
          return authService.getUserDetails();
      }
    }
  }) 

This is a root state with resolve. The only state with resolve. Here is an example of its first child (any other would be defined similar way:

$stateProvider
    .state('index', {
        url: '/',
        parent : 'root',
        ...

This approach will work out of the box. I just would like to mention that if the 'RootCtrl' is defined like this:

.controller('RootCtrl', function($scope,user){
  $scope.user = user;
})

we should understand the UI-Router inheritance. See:

  • Scope Inheritance by View Hierarchy Only

small cite:

Keep in mind that scope properties only inherit down the state chain if the views of your states are nested. Inheritance of scope properties has nothing to do with the nesting of your states and everything to do with the nesting of your views (templates).

It is entirely possible that you have nested states whose templates populate ui-views at various non-nested locations within your site. In this scenario you cannot expect to access the scope variables of parent state views within the views of children states...

More explanation could be found in this Q & A

So, what does it mean?

Our root view can pass the resolved stuff into child state only - if their views are nested.

For example, the $scope.user will be inherited in child states/views/$scopes only if they are nested like this

.state('index', {
    url: '/',
    parent : 'root',
    views: { 
      '' : { // the root view and its scope is now the ancestor
             // so $scope.user is available in every child view
        templateUrl: 'layout.html',
        controller: 'IndexCtrl'
      },
      'top@index' : { templateUrl: 'tpl.top.html',},
      'left@index' : { templateUrl: 'tpl.left.html',},
      'main@index' : { templateUrl: 'tpl.main.html',},
    },

Check it here




回答2:


If I correctly understand you want that on page load you would have user info before any controller or service request it.

I had similar task in my current project.

To solve the problem I manually requested current user info before app bootstapping & store it in localStorage.

Then after app bootstrapping all controllers/services have accesss to current user info.

TIP: to get user info before app bootstrap you can still use $http service by manually injecting it:

angular.injector(['ng']).get('$http');


来源:https://stackoverflow.com/questions/26471326/controlling-order-of-operations-with-services-and-controllers

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!