How to decorate current state resolve function in

2019-01-18 15:32发布

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) {
        //  }]
        //}
      }
    }
  })
  ...

2条回答
乱世女痞
2楼-- · 2019-01-18 16:21

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

查看更多
虎瘦雄心在
3楼-- · 2019-01-18 16:24

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;
        }
      }];
    });
  }
]);
查看更多
登录 后发表回答