2

Using AngularJS 1.2.16, ui-router, and Restangular (for API services), I have an abstract state with a child state that uses multiple views. The child view accepts a URL parameter (via $stateParams) and a resolve to fetch the record from an API based on the given ID passed via the URL.

All works perfectly as you navigate throughout the site, however if you are on the given page and refresh it (Command/Ctrl+r or clicking the refresh button) or load the page directly by hitting the deep link with the ID (this is the more crucial problem) the API service is hit by the request but the promise never finishes resolving, thus the data isn't made available to the state, controller, templates, etc.

According to ui-router's documentation, controllers shouldn't be instantiated until all promises are resolved. I've searched high and low in Google and SO, read the AngularJS, Restangular, and ui-router documentation, and a multitude of blogs and tried every iteration I know to figure this out and can't find anything that points to a solution.

Here's the code in question:

company.js (controller code)

angular.module('my.company', ['ui.router', 'CompanySvc'])

.config(['$stateProvider', function config ($stateProvider) {
  $stateProvider
    .state('company', {
      abstract: true,
      url: '/company',
      data: {
        pageTitle: 'Company'
      },
      views: {
        'main': {
          templateUrl: 'company/companyMain.tpl.html'
        }
      }
    })
    .state('company.landing', {
      url: '/{id}',
      views: {
        'summary': {
          controller: 'CompanyCtrl',
          templateUrl: 'company/companyDetails.tpl.html'
        },
        'locations': {
          controller: 'CompanyCtrl',
          templateUrl: 'company/companyLocations.tpl.html'
        }
      },
      resolve: {
        companySvc: 'CompanySvc',
        // Retrieve a company's report, if the id is present
        company: ['$log', '$stateParams', 'companySvc', function ($log, $stateParams, companySvc) {
          $log.info('In company.landing:resolve and $stateParams is: ', $stateParams);

          var id = $stateParams.id;

          $log.info('In company.landing:resolve and id is: ', id);

          return companySvc.getCompany(id).$promise;

          // NOTE SO: this was another stab at getting the promise resolved,
          // left commented out for reference
          /*return companySvc.getCompany(id).then(function (response) {
            return response;
          });*/
        }]
      }
    });
  }
])

.controller('CompanyCtrl', ['$log', '$scope', '$state', 'company',
  function CompanyCtrl ($log, $scope, $state, company) {
    $log.info('In CompanyCtrl & $state is: ', $state);
    $log.info('In CompanyCtrl and company data is: ', company);
    $scope.reportData = company ? company : {};
    $log.info('In CompanyCtrl and $scope.reportData is: ', $scope.reportData);
  }
]);

companyService.js (API service code using Restangular)

angular.module('my.companyService', ['restangular']);

.factory('CompanySvc', ['$log', 'Restangular', function ($log, Restangular) {
  var restAngular = Restangular.withConfig(function (Configurer) {
    Configurer.setBaseUrl('/int');
  });

  // Object for working with individual Companies
  var _companySvc = restAngular.all('companies');

  // Expose our CRUD methods
  return {
    // GET a single record /int/companies/{id}/report
    getCompany: function (id) {
      $log.info('In CompanySvc.getCompany & id is: ', id);

      return restAngular.one('companies', id).customGET('report').then(function success (response) {
        $log.info('Retrieved company: ', response);

        return response;
      }, function error (reason) {
        $log.error('ERROR: retrieving company: ', reason);

        return false;
      });
    }
  };
}]);

Some other points that may be added: * html5mode is true with a hashPrefix * app is being served by Apache with rewrite rules set * base href is set * this is a problem endemic to the entire application where data is being resolved in a state; I just chose this as the example

Finally, here's some output from the console:

In company.landing:resolve and $stateParams is: Object { id="d2c936fb78724880656008a5545b01ea445d4dc4"}
In company.landing:resolve and id is: d2c936fb78724880656008a5545b01ea445d4dc4
In CompanySvc.getCompany & id is: d2c936fb78724880656008a5545b01ea445d4dc4\
In CompanyCtrl & $state is: Object { params={...}, current={...}, $current={...}, more...}
In CompanyCtrl and company data is: undefined
In CompanyCtrl and $scope.reportData is: Object {}
In CompanyCtrl & $state is: Object { params={...}, current={...}, $current={...}, more...}
In CompanyCtrl and company data is: undefined
In CompanyCtrl and $scope.reportData is: Object {}
In CompanyCtrl & $state is: Object { params={...}, current={...}, $current={...}, more...}
Retrieved company: Object { $promise={...}, $resolved=false, route="companies", more...}
CrankNPlank
  • 97
  • 2
  • 8
  • Firstly, any reason why you are putting `companySvc: 'CompanySvc'` in your resolve definition? Why not just inject `CompanySvc` into the `company:` resolve? – Matt Way Aug 07 '14 at 22:26
  • Hey @MattWay... Yeah, I was doing that originally, and then read this [article](http://www.jvandemo.com/how-to-resolve-angularjs-resources-with-ui-router/) and thought I'd give it a shot. Didn't help. :-/ – CrankNPlank Aug 07 '14 at 22:30
  • Are you able to make a fiddle or anything, replicating your problem? – Matt Way Aug 07 '14 at 22:32
  • I'm not sure how I would, since the problem only happens on a hard refresh or direct hit on a deep link. I'll think on that and see what I can do. – CrankNPlank Aug 07 '14 at 22:44
  • Hi @MattWay... I created a [fiddle](http://jsfiddle.net/CrankNPlank/1e3xh8s9/4/) to try to recreate the problem, but as you'll see I don't think I can demonstrate it using fiddle. You can't refresh the fiddle (or hit the record directly with a deep link). Edit: clarity. – CrankNPlank Aug 08 '14 at 18:52

1 Answers1

9

Firstly just a note. If you have routing issues (like those with ui-router), instead of using a fiddle, you can just use something like pastebin to provide a single complete demo file that can be run locally, so that the route issues can be tested.

I have created one of those from your fiddle: here

The interesting thing is, that your mock demo seems to work perfectly fine with regards to routing. This makes me think that the issue lies somewhere in your production service. One thing that used to annoy me with ui-router was the difficulty in solving resolve bugs, as there seemed to be little to no error reporting.

If you add this to your apps run function (as I have done in the link above), you should get better resolve error output:

// handle any route related errors (specifically used to check for hidden resolve errors)
$rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error){ 
    console.log(error);
});
Matt Way
  • 32,319
  • 10
  • 79
  • 85
  • Thanks! I'll dig in to this. So, will $stateChangeError still render the state even if a resolve error is thrown? In other words, I end-up with the rendered templates but without the data, and I would expect the error to prevent that from happening... – CrankNPlank Aug 09 '14 at 00:37
  • Yeah, in that case, I am not sure what is going on. If the resolve fails, it doesn't render the template, and without `$stateChangeError` is will silently fail. Templates with no data doesn't seem to be a routing issue, but an issue with your data retrieval service. Are you able to hook a demo into the real data so we can replicate the issue, let us know. – Matt Way Aug 09 '14 at 02:12
  • I'll have to come back to this down the road a bit, when I have time to set-up a demo that actually shows the problem happening. I'd hoped it was just some obvious mistake I was making, but no joy. Thanks, Matt, and I'll update this when I'm able. – CrankNPlank Aug 11 '14 at 19:50
  • 1
    did you find a solution CrankNPlank? I have the same pain – Sam Vloeberghs Dec 27 '15 at 13:10