Using 'resolve' to wait for query results in routeProvider - javascript

I'm having trouble figuring out how to get the routeProvider to wait until a remote call returns. The best solution I've seen for far was the example here: delaying angular route change
. Unfortunately, when I tired to apply that example to my own code, the binding would trigger before the data was actually loaded. Does anyone know of another example that uses the new Resource syntax from angular 1.1.5 ($promise can be accessed directly )?
Here is what my code looks like:
var productModule = angular.module('productModule', ['ngResource', 'ngLocale']).
config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/view1', {templateUrl: 'partials/partial1.html',
controller: 'ResourceCtrl as appController' ,
resolve:
{
productData: function($resource)
{
console.log(["calling service"]);
return $resource(Services.ProductServices.PATH).query(null,
function(data){
console.log(["call succeeded"]);
},
function(data){
console.log(["calling failed"]);
}).$promise;
}
}
});
$routeProvider.when('/view2', {templateUrl: 'partials/partial2.html'});
$routeProvider.otherwise({redirectTo: '/view1'});
}]) ;
productModule.controller('ResourceCtrl','$scope','productData',function($scope,productData) {
$scope.productData = productData;
console.log(["promise resolved"]);
}]);
If I run that code, the console would display:
calling service
promise resolved
call succeeded

It should be as simple as this:
resolve: {
productData: function(ProductServices) {
return ProductServices.query().$promise.then(function(data){
return data;
});
}
}
If your Service looks something like this:
myApp.factory('ProductServices', function($resource) {
return $resource('/path/to/resource/:id', { id: '#id' });
});

Related

i18n angular fails after minification

I am using ui bootstrap and use tmhDynamicLocaleProvider to get the correct translations for days,month etc in my datepicker.
It works all fine as long as I load the locale documents from online. If I load documents locally (I need to do it locally), it doesn't load the correct translations after minification.
My code looks as follows
app.config(['tmhDynamicLocaleProvider', function(tmhDynamicLocaleProvider) {
tmhDynamicLocaleProvider.localeLocationPattern('app/datepickerLocale/locale.{{locale}}.js');
}])
app.config(....{
$provide.decorator('uibDatepickerDirective', ['$delegate', function($delegate) {
angular.forEach($delegate, function (directive) {
var originalCompile = directive.compile;
var originalLink = directive.link;
if (originalCompile) {
directive.compile = function () {
return function (scope) {
scope.$on('$localeChangeSuccess', function () {
scope.move(0);
});
originalLink.apply(this, arguments);
};
}
}
});
return $delegate;
}]);
})
I resolve it in a state
.state('main', {
url: '/{language:[a-z]{2}}',
templateUrl: 'app/main/main.html',
controller: 'MainCtrl',
controllerAs: 'mainCtrl',
resolve: {
localeLanguage: ['resolveService', '$stateParams', function(resolveService, $stateParams){
resolveService.resolveLocale($stateParams)
}]
}
})
the service looks as follows
resolveLocale: function(stateParams){
var deferred = $q.defer();
if(stateParams.language){
tmhDynamicLocale.set(stateParams.language);
deferred.resolve(1);
} else {
deferred.resolve(2);
}
return deferred.promise;
}
it all works fine until minification. After that I obtain the error, that
the script was not able to be obtained. (GET error).
I assume that the while invoking the function the script locale.en.js has not been loaded yet. Or am I wrong here?
How can I solve this?

AngularJS ui-router: how to resolve typical data globally for all routes?

I have an AngularJS service which communicates with the server and returns
translations of different sections of the application:
angular
.module('utils')
.service('Translations', ['$q','$http',function($q, $http) {
translationsService = {
get: function(section) {
if (!promise) {
var q = $q.defer();
promise = $http
.get(
'/api/translations',
{
section: section
})
.success(function(data,status,headers,config) {
q.resolve(result.data);
})
.error(function(data,status,headers,config){
q.reject(status);
});
return q.promise;
}
}
};
return translationsService;
}]);
The name of the section is passed as the section parameter of the get function.
I'm using AngularJS ui-router module and following design pattern described here
So I have the following states config:
angular.module('app')
.config(['$stateProvider', function($stateProvider) {
$stateProvider
.state('users', {
url: '/users',
resolve: {
translations: ['Translations',
function(Translations) {
return Translations.get('users');
}
]
},
templateUrl: '/app/users/list.html',
controller: 'usersController',
controllerAs: 'vm'
})
.state('shifts', {
url: '/shifts',
resolve: {
translations: ['Translations',
function(Translations) {
return Translations.get('shifts');
}
]
},
templateUrl: '/app/shifts/list.html',
controller: 'shiftsController',
controllerAs: 'vm'
})
This works fine but as you may notice I have to explicitly specify translations in the resolve parameter. I think that's not good enough as this duplicates the logic.
Is there any way to resolve translations globally and avoid the code duplicates. I mean some kind of middleware.
I was thinking about listening for the $stateChangeStart, then get translations specific to the new state and bind them to controllers, but I have not found the way to do it.
Any advice will be appreciated greatly.
Important note:
In my case the resolved translations object must contain the translations data, not service/factory/whatever.
Kind regards.
Let me show you my approach. There is a working plunker
Let's have a translation.json like this:
{
"home" : "trans for home",
"parent" : "trans for parent",
"parent.child" : "trans for child"
}
Now, let's introduce the super parent state root
$stateProvider
.state('root', {
abstract: true,
template: '<div ui-view=""></div>',
resolve: ['Translations'
, function(Translations){return Translations.loadAll();}]
});
This super root state is not having any url (not effecting any child url). Now, we will silently inject that into every state:
$stateProvider
.state('home', {
parent: 'root',
url: "/home",
templateUrl: 'tpl.html',
})
.state('parent', {
parent: 'root',
url: "/parent",
templateUrl: 'tpl.html',
})
As we can see, we use setting parent - and do not effect/extend the original state name.
The root state is loading the translations at one shot via new method loadAll():
.service('Translations', ['$http'
,function($http) {
translationsService = {
data : {},
loadAll : function(){
return $http
.get("translations.json")
.then(function(response){
this.data = response.data;
return this.data;
})
},
get: function(section) {
return data[section];
}
};
return translationsService;
}])
We do not need $q at all. Our super root state just resolves that once... via $http and loadAll() method. All these are now loaded, and we can even place that service into $rootScope:
.run(['$rootScope', '$state', '$stateParams', 'Translations',
function ($rootScope, $state, $stateParams, Translations) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
$rootScope.Translations = Translations;
}])
And we can access it anyhwere like this:
<h5>Translation</h5>
<pre>{{Translations.get($state.current.name) | json}}</pre>
Wow... that is solution profiting almost from each feature coming with UI-Router... I'd say. All loaded once. All inherited because of $rootScope and view inheritance... all available in any child state...
Check that all here.
Though this is a very old question, I'd like to post solution which I'm using now. Hope it will help somebody in the future.
After using some different approaches I came up with a beautiful angularjs pattern by John Papa
He suggest using a special service routerHelperProvider and configure states as a regular JS object. I'm not going to copy-paste the entire provider here. See the link above for details. But I'm going to show how I solved my problem by the means of that service.
Here is the part of code of that provider which takes the JS object and transforms it to the states configuration:
function configureStates(states, otherwisePath) {
states.forEach(function(state) {
$stateProvider.state(state.state, state.config);
});
I transformed it as follows:
function configureStates(states, otherwisePath) {
states.forEach(function(state) {
var resolveAlways = {
translations: ['Translations', function(Translations) {
if (state.translationCategory) {
return Translations.get(state.translationCategory);
} else {
return {};
}
}],
};
state.config.resolve =
angular.extend(state.config.resolve || {}, resolveAlways || {});
$stateProvider.state(state.state, state.config);
});
});
And my route configuration object now looks as follows:
{
state: ‘users’,
translationsCategory: ‘users’,
config: {
controller: ‘usersController’
controllerAs: ‘vm’,
url: ‘/users’.
templateUrl: ‘users.html'
}
So what I did:
I implemented the resolveAlways object which takes the custom translationsCategory property, injects the Translations service and resolves the necessary data. Now no need to do it everytime.

Using service/provider with $http and $q in angular config

I want to load some configuration to each controller in app.config section. Each controller needs a different, but non-mutually-exclusive set of data to be loaded. I can't figure out how to achieve this.
.config(['$routeProvider', '$locationProvider',
function($routeProvider, $locationProvider){
$routeProvider
.when('/', {
templateUrl: "partials/pages/dashboard.html",
controller: "dashboard_controller",
resolve: { dash_config: 'SomeConfigD'},
})
.when('/a', {
templateUrl: "partials/pages/a.html",
controller: "a_controller",
resolve: { dash_config: 'SomeConfigA'},
})
}])
However, I don't want to write seperate factories for someConfigA and someConfigD, since they share code. I want something like,
app.factory('configFactory', function(...){
var factory = ;
function get1(){
// some $http calls here and return a promise
}
function get2(){
// some $http calls here and return a promise
}
function get3(){
// some $http calls here and return a promise
}
factory.configA = function(){
// return a promise to resolve both get1 and get2
};
factory.configD = function(){
// return a promise to resolve both get2 and get3
};
})
How can I do this?
It sounds like what you are looking for is $q.all, which you can read about here.
I also made a fiddle that uses your factory, although I do it in the run method of the module since I didn't want to deal with creating the factory. It looks something like this:
f.configA = function(){
var get12 = $q.all([
get1().then(thenFn),
get2().then(thenFn)
]).then(function() {
console.log('both resolved');
});
return get12;
};
the function in the then is only called when both promises have been resolved (which happen at different times, simulated with $timeout
Now the get functions can be reused, Hope this helped!

How can you manually inject route resolve data inside a controller?

I have 2 routes that share a controller, one needs data resolved prior to the view loading, and the other does not need the resolved data.
Routing segment example:
...
when('/users', {
controller: 'UsersCtrl',
templateUrl: '/partials/users/view.html',
resolve: {
resolvedData : ['Accounts', function(Accounts) {
return Accounts.get();
}]
}
}).
when('/users/add', {
controller: 'UsersCtrl',
templateUrl: '/partials/users/add.html'
})
...
Controller example:
app.controller('UsersCtrl', ['$scope', 'Helper', 'resolvedData',
function($scope, Helper, resolvedData) {
// this works for the first route, but fails for the second route with
// unknown "resolvedDataProvider"
console.log(resolvedData);
}]);
Is there any way I can get the resolvedData in the controller without explicitly using the resolve name as a dependency? So a check can be performed?
Using the $injector does not work. I would like to do something similar to:
if ($injector.has('resolveData')) {
var resolveData = $injector.get('resolveData');
}
However this does not work even for the route that has the resolveData set ('/users'):
app.controller('UsersCtrl', ['$scope', 'Helper', '$injector',
function($scope, Helper, $injector) {
// this does not work -> fails with the unknown "resolvedDataProvider" as well
$injector.get('resolvedData');
}]);
Can this be done in angularjs? Or should I just create a new controller?
Thank you.
Looks like I figured out another way to go. The resolved data is part of the $route. So you can access it using:
app.controller('UsersCtrl', ['$scope', '$route', 'Helper',
function($scope, $route, Helper) {
if ($route.current.locals.resolvedData) {
var resolvedData = $route.current.locals.resolvedData;
}
}]);
If the other route doesn't need it, just inject undefined on that route:
router:
when('/users', {
controller: 'UsersCtrl',
templateUrl: '/partials/users/view.html',
resolve: {
resolvedData : ['Accounts', function(Accounts) {
return Accounts.get();
}]
}
}).
when('/users/add', {
controller: 'UsersCtrl',
templateUrl: '/partials/users/add.html',
resolve: {
resolvedData: function() {
return undefined;
}
}
})
controller:
app.controller('UsersCtrl', ['$scope', 'Helper', 'resolvedData',
function($scope, Helper, resolvedData) {
if(resolvedData){
//set some scope stuff for it
} else {
//do what you do when there is no resolvedData
}
}]);

AngularJS, resolve data before showing view

This subject has been already asked but I couldn't figure out what to do in my case.
Using AngularJS 1.0.5:
Before showing the view "login", I want to get some data and delay the view rendering while the data isn't loaded from an AJAX request.
Here is the main code. Is it the good way?
angular.module('tfc', ['tfc.config', 'tfc.services', 'tfc.controllers']).config([
'$routeProvider', '$locationProvider', '$httpProvider',
function($routeProvider, $locationProvider, $httpProvider) {
$routeProvider.when('/login', {
templateUrl: 'views/login.html',
controller: "RouteController",
resolve: {
data: function(DataResolver) {
return DataResolver();
}
}
});
}
]);
module_services = angular.module("tfc.services", []);
module_services.factory("DataResolver", [
"$route", function($route) {
console.log("init");
return function() {
// Tabletop is a lib to get data from google spreadsheets
// basically this is an ajax request
return Tabletop.init({
key: "xxxxx",
callback: function(data, tabletop) {
console.log("[Debug][DataResolver] Data received!");
return data;
}
});
};
}
]);
The point of AngularJS is that you can load up the templates and everything and then wait for the data to load, it's meant to be asynchronous.
Your view should be using ng-hide, ng-show to check the scope of the controller so that when the data in the scope is updated, the view will display. You can also display a spinner so that the user doesn't feel like the website has crashed.
Answering the question, the way you are loading data explicitly before the view is rendered seems right. Remember that it may not give the best experience as there will be some time to resolve that, maybe giving an impression that your app stopped for some moments.
See an example from John Pappa's blog to load some data before the route is resolved using angular's default router:
// route-config.js
angular
.module('app')
.config(config);
function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'Avengers',
controllerAs: 'vm',
resolve: {
moviesPrepService: function(movieService) {
return movieService.getMovies();
}
}
});
}
// avengers.js
angular
.module('app')
.controller('Avengers', Avengers);
Avengers.$inject = ['moviesPrepService'];
function Avengers(moviesPrepService) {
var vm = this;
vm.movies = moviesPrepService.movies;
}
You basically use the resolve parameters on the route, so that routeProvider waits for all promises to be resolved before instantiating the controller. See the docs for extra info.

Categories