简体   繁体   中英

How to decorate current state resolve function in UI-Router? Current function isn't invoked

I'm trying to DRY in $stateProvider and prevent adding the same auth function in each resolve. I've created decorator that in each state change would add this function to current state, but auth function isn't invoked, How to fix it or how to workaround discussed issue?

app.config(function ($stateProvider, $urlRouterProvider, $provide) {
  $provide.decorator('$state', function($delegate, $rootScope) {
    $rootScope.$on('$stateChangeStart', function(event, state, params) {
      if ($delegate.current === "login" || $delegate.current === "register") {
        return;
      }
      console.log("decorator", $delegate);
      $delegate.current.resolve = {
        auth: ['AuthService', '$stateParams', function(AuthService, $stateParams) {
          //how to invoke this function?
          if (AuthService.isAuthenticated()) {
            return AuthService.me(); //promise
          } else {
            return false;
          }
        }]
      };
    });
    return $delegate;
  });

states definition:

  $stateProvider.state('root', {
    abstract: true,
    url: '/',
    views: {
      "": {
        controller: 'RootCtrl',
        templateUrl: 'views/root.html'
      },
      "header@root": {
        templateUrl: 'views/header.html'
      }
    }
  })
  .state('root.home', {
    url: urlPrefix,
    views: {
      "content@artworks": {
        templateUrl: 'views/home.html',
        //resolve: {
        //  auth: ['AuthService', '$stateParams', function(AuthService, $stateParams) {
        //  }]
        //}
      }
    }
  })
  ...

If I understand your requirement correctly, we can use native UI-Router built-in decorator :

decorator(name, func)

Allows you to extend (carefully) or override (at your own peril) the stateBuilder object used internally by $stateProvider. This can be used to add custom functionality to ui-router, for example inferring templateUrl based on the state name... (read more in the doc )

There is a working plunker

So, we can have this var auth

var auth = ['AuthService', '$stateParams',
  function(AuthService, $stateParams) {
    //how to invoke this function on needed states?
    if (AuthService.isAuthenticated()) {
      return AuthService.me();
    } else {
      return false;
    }
  }
];

And here we just use decorator with some "IF" logic

.config(['$stateProvider', 
  function($stateProvider) {

    $stateProvider.decorator('views', function(state, parent) {
      var result = {},
        views = parent(state);

      // some naive example when to not inject resolve
      if (state.name === "home") { 
        return views;
      }
      // child already has that in parent
      if (state.name.indexOf(".") > 0) { 
        return views;
      }

      angular.forEach(views, function(config, name) {

        // here inject the resolve (if not existing)
        config.resolve = config.resolve || {};
        // and extend it with the auth stuff above
        config.resolve.auth = auth;

        result[name] = config;
      });

      return result;
    });

  }
])

And later few our states, which will be extended by the above stuff

$stateProvider
    .state('home', {
      url: "/home",
      templateUrl: 'tpl.html',
    })
    .state('parent', {
      url: "/parent",
      templateUrl: 'tpl.html',
      controller: 'SharedCtrl',
    })
    .state('parent.child', {
      url: "/child",
      templateUrl: 'tpl.html',
      controller: 'SharedCtrl',
    });

Check it in action here

I realized that $delegate.current object contains only raw stateProvider config data. To wrap resolve function I add my function to $delegate.$current on each state change.

$provide.decorator('$state', function($delegate, $rootScope) {
  $rootScope.$on('$stateChangeStart', function(event, state, params) {
    if ($delegate.current === "err404" || $delegate.current === "login" || $delegate.current === "register") {
      return;
    }
    console.log("decorator", $delegate);
    $delegate.$current.resolve["auth"] = ['AuthService', '$stateParams', function(AuthService, $stateParams) {
      if (AuthService.isAuthenticated()) {
        console.log('AuthService.me()');
        return AuthService.me();
      } else {
        return false;
      }
    }]
  });
  return $delegate;
});

Update

I found related discussion on github , you can add universal resolve function into toState param:

app.run(['$rootScope', '$state',
  function($rootScope, $state) {

    $rootScope.$on('$stateChangeStart', function(event, toState) {

      if (toState.name === "login" || toState.name === "register") {
        return;
      }

      toState["resolve"]["auth"] = ['AuthService', '$stateParams', function(AuthService, $stateParams) {
        if (AuthService.isAuthenticated()) {
          return AuthService.me();
        } else {
          return false;
        }
      }];
    });
  }
]);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM