Roland Pish
Roland Pish

Reputation: 815

AngularJS prevent template from rendering based on response from an API

In a single page application we're building with angularjs and a Rest API in python, we have an ACL logic in the Rest API that determines if the current logged in user has access to a certain resource or not. What we are currently doing is to wrap the markup of each "protected" template in a div that contains an ng-if="authorized" attribute where the $scope.authorized variable gets true if the API responds that the current user is authorized to view the content of the template.

With this solution, what the end user experiences, is a quick blank template between the time the API takes to determine if they are authorized to access the resource and the time it takes to redirect them to a dashboard page (a page that all kinds of users have access to) along with a message that tells them that they are not authorized to access that particular resource. Of course, our concern is the time that the blank template is displayed to the end user before the application takes them to the dashboard page.

Is there something we can do in AngularJS to avoid that span time when the blank template is displayed to the end user and instead make them keep in the current page and only go to the page they requested only if the API resolved that they have access to it?

Upvotes: 0

Views: 794

Answers (1)

Chad Robinson
Chad Robinson

Reputation: 4623

Do you have to use ngRoute? ui-router has two excellent tools that you could use either separately or together to solve this:

  1. It has a $stateChangeStart event that gets fired before a state transition occurs, with all information about the previous/next states. You can stop state transitions here. For example, I use something like this to send auth-required accesses to a login experience:

    $stateProvider.state('auth-required-state', {
        url: '^/auth-required-state',
        templateUrl: '/views/view/auth-required-state.html',
        controller: 'AuthRequiredStateController',
    
        // NOTE: You have access to this entire block in $stateChangeStart, so you can
        // define your own attributes that your own code looks at
        allowAnonymous: false
    });
    

    and in $stateChangeStart:

    // Handle some checking that needs to be performed
    $rootScope.$on('$stateChangeStart', function(e, toState, toParams, fromState, fromParams) {
        // If we aren't logged in and we need to be, do that
        if (!$rootScope.currentUser.loggedIn && !toState.allowAnonymous) {
            $state.go('login');
            e.preventDefault();
        }
    });
    
  2. It has a resolve operator that you can use to lazy-load things like if you need a data block from a REST service, not just a template. For another use-case I had, I only wanted to go to the target screen if the user had access to that data - the template was always available because you could access some records but not others. You can do something like this:

    var ShowDataControllerConfig = function($stateProvider) {
        var getDataBlock = function($stateParams, myDataManager) {
            return myDataManager.getDataBlockAndReturnAPromise($stateParams.data_id);
        };
    
        // Tolerate minification - you can't use the array trick in state definitions
        getGroupEntry.$inject = ['$stateParams', 'myDataManager'];
    
        $stateProvider.state('show-data', {
            url: '^/show-data/{data_id}',
            templateUrl: '/views/view/show-data.html',
            controller: 'ShowDataRoomController',
            resolve: {
                dataBlock: getDataBlock
            }
        });
    };
    
    ShowDataControllerConfig.$inject = ['$stateProvider'];
    
    var ShowDataController = function(dataBlock) {
        // dataBlock contains our data - but unlike ngRoute, we never even get here if
        // it couldn't be loaded. You can do error handling in $stateChangeError.
    };
    
    ShowDataController.$inject = ['dataBlock'];
    
    angular
        .module('myApp.controllers.showData', [])
        .config(ShowDataControllerConfig)
        .controller('ShowDataController', ShowDataController);
    

Upvotes: 1

Related Questions