Troubleshooting AngularJS: Why is my initial resolve not functioning

When visiting a route with a resolve for the first time, the request for the objects is not sent. The only way to access the page is to ensure the route is correct in the URL bar (by typing or clicking a link) and refreshing the page without caching (ctrl+shift+r in Firefox or ctrl+F5 in Chrome).

After the initial visit, the link will work as expected.

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

  $stateProvider.state('users', {
    templateUrl: '/app/Users/templates/users.html',
    controller: 'Users',
    resolve: {
      'users': function(Objects, $stateParams){
        return Objects.getUsers();
      }
    },
    url: '^/users'
  });

  $stateProvider.state('user', {
    templateUrl: '/app/Users/templates/user.html',
    controller: 'User',
    resolve: {
      'user': function(Objects, $stateParams){
        return Objects.getUser($stateParams.id);
      }
    },
    url: '^/users/:id/'
  });
}]);

app.factory('Objects', ['$http', '$q', function($http, $q){
  /* Retrieve objects once */
  var _cache = {};

  function cache(key, promiseGetterFn) {
    if (key in _cache) {
      return _cache[key];
    }
    else {
      var promise = promiseGetterFn();
      _cache[key] = promise;
      return promise;
    }
  }
  return {
    unsetKey: function(key){
      delete _cache[key];
    },
    getUsers: function() {
      return cache('users', function () {
        var deferred = $q.defer();
        $http.get(HOST + '/api/v1.0/users/all').then(
          function (result) {
            deferred.resolve(result);
          });
        return deferred.promise;
      });
    },

    getUser: function(id){
      return cache('user_' + id, function(){
        var deferred = $q.defer();
        return $http.get(HOST + '/api/v1.0/user/' + id).then(
          function(result){
            deferred.resolve(result.data.user);
          },
          function(status){
            deferred.reject(status);
          }
        );
        return deferred.promise;
      });
    },
  };
}]);

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

  $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
    if (!toState.publicAccess && !LocalService.get('loggedIn')){
      /* Store the route they were trying to access */
      LocalService.set('next', $location.path());

      $location.path('/login');
    }
  });
}]);

Redirect after login code

app.factory('AuthInterceptor', ['$q', '$injector', '$location', 'LocalService', function($q, $injector, $location, LocalService){
  /* Send Authorization in the header of each http request if there is a token */
  return {
    request: function(config){
      if (LocalService.get('token')){
        /* Using btoa to do Base64 */
        /* LocalService.password is only used on login to get token and will be empty ('') when using the token */
        config.headers.Authorization = 'Basic ' + btoa(LocalService.get('token') + ':' + LocalService.get('password'));
      }
      return config;
    },
    responseError: function(response){
      if(response.status === 401 || response.status === 403){
        /* Log the user out */
        LocalService.unset('loggedIn');
        LocalService.unset('token');
        LocalService.unset('user');
        $location.path('/login');
      }
      return $q.reject(response);
    }
  };
}]);

app.config(['$httpProvider', function($httpProvider){
  $httpProvider.interceptors.push('AuthInterceptor');
}]);

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

  $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
    if (!toState.publicAccess && !LocalService.get('loggedIn')){
      /* Store the route they were trying to access */
      LocalService.set('next', $location.path());

      $location.path('/login');
    }
  });
}]);

app.controller('Login', ['$scope', '$http', '$location', 'growl', 'LocalService',
  function($scope, $http, $location, growl, LocalService){
    $scope.email = '';
    $scope.password = '';

    $scope.submitLogin = function submitLogin(){
      LocalService.set('token', $scope.email);
      LocalService.set('password', $scope.password);
      $http.get(HOST + '/api/v1.0/token').
        success(function(data, status, headers, config) {
          LocalService.set('token', data.token);
          LocalService.set('loggedIn', true);
          LocalService.set('password', '');
          /* Set current user */
          $http.get(HOST + '/api/v1.0/authenticate').then(function(result) {
            LocalService.set('user', JSON.stringify(result.data));
            if (LocalService.get('next')){
              var next = LocalService.get('next');
              LocalService.unset('next');
              console.log(next);
              $location.path(next);
            }
            else{
              $location.path('/');
            }
          });
        }
      ).
      error(function(data, status, headers, config) {
          /* invalid credentials growl */
          growl.addErrorMessage('Invalid username or password.');
        }
      );
    };
  }
]);

Answer №1

Upon initial reflection, it seems that the resolved objects are only resolved during a hard load. After the app is instantiated from an index.html, the partial views may not recognize the objects as promises. One suggestion would be to ensure that the objects returned from the factory (the api) are actual promises. Your current promise structure is unlike any I have seen before, lacking 'deferred.resolve' or 'deferred.reject'. For example:

 return {
    getBundles: function(){
      return cache('bundles', function(){
        var deferred = $q.defer
        return $http.get(HOST + '/api/v1.0/bundles/all').success(
          function(data, status, headers, config){
            deferred.resolve(data.bundles);
          }.error(function(status){
            deferred.reject(status);
        )};
        return deferred.promise;
      });
    },
  }

Additionally, I would recommend converting the objects to a JavaScript object before binding them to the view. This can be done in the controller, with 'bundles' injected into it.

var thisBundle = bundles.data.bundles;
thisBundle.then(function(data){
    $scope.bundles = data;
});

Another approach is to enclose all items with a resolve in the routing configuration. Consider the following example:

resolve: {
        'allProducts' : function(){
            var theResolvePromise = $q.defer();
            theResolvePromise.resolve({
                bundles: ['Objects', function(Objects){
                  return Objects.getBundles();
                }],
                products: ['Objects', function(Objects){
                  return Objects.getProducts();
                }],
                technologies: ['Objects', function(Objects){
                   return Objects.getTechnologies();
                }],
                deliveryCategories: ['Objects', function(Objects){
                  return Objects.getDeliveryCategories();
                }],
             });
             return theResolvePromise.promise;
         };
      }
    }).

In the controller, you can access these resolved objects using the parameters passed through. Retrieved from:

Hope this information proves helpful,

Patrick

Answer №2

Give this a try. Although I haven't tested it, it may offer some guidance in the right direction.

app.factory('Objects', ['$http', function($http){
  var _cache = {};

  function cache(key, getterFn) {
    if (_cache[key] == null) {
      _cache[key] = getterFn();
      _cache[key].then(function (result) {
          _cache[key] = result;
      });
    }

    return _cache[key];
  }

  return {
    unsetKey: function(key){
      delete _cache[key];
    },
    getUsers: function() {
      return cache('users', function () {
        return $http.get(HOST + '/api/v1.0/users/all');
      });
    },
    getUser: function(id){
      return cache('user_' + id, function() {
        return $http.get(HOST + '/api/v1.0/user/' + id).then(function (result) {
          return result.data.user;
        });
      });
    },
  };
}]);

$http.get returns a promise that resolves when the request completes. Returning a value from a .then() function will pop that value into the next .then() in the chain. UI-Router's resolves automatically unwrap the promise if given one. If given anything else, it just returns it. Promises don't automatically unwrap in Angular 1.2+ so you need to unwrap it yourself when you're working in your cache.

Answer №3

Your query consists of two parts.

1) Why is my initial resolution not resolving the state's resolution?

When you first return a "promise to fulfill a promise of data fulfillment", it creates a chained promise. This will require an additional $digest cycle to resolve compared to returning a promise to retrieve the data directly. Remember, $http.get() returns a promise.

In AngularJS, the results of promise resolution are asynchronously propagated within a $digest cycle. This means that the callbacks registered with then() will only be executed during a $digest cycle.

Make sure to also refer to:

Angular JS: Chaining promises and the digest cycle

2) What changes can be made to rectify the issue?

You have two options: Simply return $http.get() as it is already a promise. Instead of caching a promise object, cache the actual data. You can also utilize {cache: true} with $resource and $http for caching results. Alternatively, if you prefer more control, consider using $cacheFactory. Here, you can store the actual results in the cache instead of the promise object.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What are the steps to create a class diagram for a NodeJS application?

For my final year project, I am looking to develop an application using Node API. As we delve into drawing the class diagram, it occurs to me that unlike Java or C#, Node JS does not have a built-in class concept. What would be the most effective approac ...

Utilizing NextJS to Call the Layout Component Function from the Page Component

I can't seem to find an answer to this question for Next.js after searching online. While there are solutions available for React, I don't think they will work in the Next.js framework. My application is essentially a shop with a navigation menu ...

The custom directive is designed to trigger its watcher only once and does not continue to monitor changes

I'm trying to trigger an animation with a custom directive called "activate" which I am using as an attribute in my file, partials/test.html <div activate="{{cardTapped}}" > The directive is defined after my app definition in js/app.js myApp. ...

Tips on getting the dropdown value to show up on the header when it changes using Angular 2 and TypeScript

I need assistance with creating a dropdown field in Angular2. When the user selects "car", I want it to display beside the heading. Can anyone provide guidance on how to achieve this? HTML: <h1>Heading <span *ngFor= "let apps of apps">({{apps ...

The upload method in flowjs is not defined

I am a novice when it comes to flow.js and am currently using the ng-flow implementation. I have a specific task in mind, but I'm unsure if it's feasible or not, and if it is possible, how to achieve it. I've created a factory that captures ...

Using Axios in Vuejs to prompt a file download dialog

I am currently working on figuring out how to trigger a file download dialog box after receiving an Ajax request from the Flask server using Axios. Here is my current client-side code snippet: <script> export default { ... exportCSV: function() { ...

Webstorm seems to be having trouble identifying Next.js

When I create a Next.js app using the command npx create-next-app my-app --use-npm Everything is successfully installed, but when using WebStorm, I noticed that it does not auto import the <Link> component from Next.js. I have to manually import it ...

Vue.js versatile form for both adding and editing

As a newcomer to the world of vue.js, I am currently working on expanding some tutorials that I have completed. After struggling with this for three hours now, I must admit that I am feeling quite frustrated. Just to give you a heads up, I am using firebas ...

Including content without triggering the digest cycle (utilizing raw HTML) within a Directive

My goal is to include raw HTML inside a directive for later transclusion (to populate a modal when opened). The issue arises when the contents of dialog-body are executed, triggering the ng-repeat loop and causing the template to be rerun, leading to a po ...

What is the best way to execute a Java script using AJAX from a different file?

I have included multiple ajax scripts in the main body of the Django template. However, when I try to run them from a separate JS file, they do not seem to work. This is an example of one of the working scripts within the body template: <!--Add product ...

Executing an Ajax request to a document containing <script> elements

Recently, I developed a sample page that effectively mimics table layout pages but without relying on the traditional mobile-unfriendly table features. The structure of the page is as follows: <html> ... <body> <div type="page" ...

Is a function repeatedly invoked?

I have implemented a Pagination component in NextJS as follows: import Pagination from 'react-bootstrap/Pagination'; import Router from "next/router"; import {useEffect} from "react"; export { Paging } function Paging(props) ...

Methods for submitting POST requests with key data enclosed in quotation marks?

Upon investigation, I discovered that the Request Payload object's key does not have quotation marks as shown below. https://i.sstatic.net/U54V9.png However, I am interested in sending a request with keys that are marked with quotations. Interestingl ...

Tips for concealing dynamically generated div elements within a VueJS v-for loop

Is there a way to hide dynamically generated div elements in VueJS? In my chatbot application, messages are passed through a prop called messages. These message arrays create multiple Divs on the screen displaying information. One particular Div is used to ...

Arrange the JSONB data type with sequelize literal in your order

My current approach involves querying for all items using the following structure: const filteredItems = await allItems.findAll({ where: conditions, include: associations, order: sortingCriteria, limit: limit, o ...

Angular directive that inserts a DOM element with a click event attached

Content updated In an attempt to enhance the functionality of our apps, I am working on creating a directive that can be linked to a textbox. This directive is designed to display an image/button next to the textbox when it gains focus. Clicking on this i ...

Is it possible to show or hide a DIV based on the text content of another DIV using JavaScript in

I am looking for a way to dynamically hide a DIV based on user roles using only the text inside a title tag as a reference. Here is an example of the HTML structure: <title>admin</title> If the user role is admin, then hide the following DI ...

How does the PhoneGap API handle Timestamp format?

Within the realm of app development, phoneGap offers two vital APIs: Geo-location and Accelerometer. Both these APIs provide a timestamp in their onSuccess method. In Accelerometer, the timestamp appears as '1386115200', whereas in Geo-location i ...

Error: Missing responseText in Jquery

Struggling with accessing ajax response data? I have encountered an issue with my ajax request that sends Json to a server and the response returns Json as well. Even though I receive the response, I am unable to access the responseText. Below is the code ...

"I am interested in using the MongoDB database with Mongoose in a Node.js application to incorporate the

I am facing a situation where I need to validate the name and code of a company, and if either one matches an existing record in the database, it should notify that it already exists. Additionally, when receiving data with isDeleted set to true, I want to ...