What is the better solution to hide template while loading data from server?
My solution is using $scope with boolean variable isLoading and using directive ng-hide, ex: <div ng-hide='isLoading'></div>
Does angular has another way to make it?
You can try an use the ngCloak directive.
Checkout this link http://docs.angularjs.org/api/ng.directive:ngCloak
The way you do it is perfectly fine (I prefer using state='loading' and keep things a little bit more flexible.)
Another way of approaching this problem are promises and $routeProvider resolve property.
Using it delays controller execution until a set of specified promises is resolved, eg. data loaded via resource services is ready and correct. Tabs in Gmail work in a similar way, ie. you're not redirected to a new view unless data has been fetched from the server successfully. In case of errors, you stay in the same view or are redirected to an error page, not the view, you were trying to load and failed.
You could configure routes like this:
angular.module('app', [])
.config([
'$routeProvider',
function($routeProvider){
$routeProvider.when('/test',{
templateUrl: 'partials/test.html'
controller: TestCtrl,
resolve: TestCtrl.resolve
})
}
])
And your controller like this:
TestCtrl = function ($scope, data) {
$scope.data = data; // returned from resolve
}
TestCtrl.resolve = {
data: function ($q, DataService){
var d = $q.defer();
var onOK = function(res){
d.resolve(res);
};
var onError = function(res){
d.reject()
};
DataService.query(onOK, onError);
return d.promise;
}
}
Links:
Resolve
Aaa! Just found an excellent (yet surprisingly similar) explanation of the problem on SO HERE
That's how I do:
$scope.dodgson= DodgsonSvc.get();
In the html:
<div x-ng-show="dodgson.$resolved">We've got Dodgson here: {{dodgson}}. Nice hat</div>
<div x-ng-hide="dodgson.$resolved">Latina music (loading)</div>
Related
In my angular project, when changing the path with $location.path('/foobar') the destination view is displayed but the data aren't reloaded (typically after saving an item and going back to the list, the list is not updated).
I tried to add $route.reload() or $scope.apply(), but nothing change.
I don't know what's wrong or missing to make this work.
UPDATE
$location.url() doesnt' work either
I'm using angular 1.2.26
UPDATE 2 - ANSWER
Ok, after a lot of comments and answers, I think it's time to end this.
I didn't think it would have been a so complicated question.
So, my conclusion, giving all you said is :
Giving simple example of #yvesmancera, the default behavior of the controller is to reload itself
In a complex controller with a resource factory and some REST calls, any save or update action should also manually update the list reference, or trigger a full reload of the list
All of you gave me some good advices, so thank you.
Use $window.location.href. to reload the page. I just check on $location document:
Page reload navigation
The $location service allows you to change only the URL; it does not allow you to reload the page. When you need to change the URL and reload the page or navigate to a different page, please use a lower level API, $window.location.href.
Example:
$window.location.href = "/your/path/here";
I had the same problem just yesterday, if you try to navigate to the same path you're already in, angular won't try to reload the view and controller. What fixed it for me is appending a "/" at the end of each route in $routeProvider, e.g:
$routeProvider
.when('/', {
templateUrl: 'views/home.html',
controller: 'HomeCtrl'
})
.when('/About/', {
templateUrl: 'views/about.html',
controller: 'AboutCtrl'
})
.when('/Contact/', {
templateUrl: 'views/contact.html',
controller: 'ContactCtrl'
})
Edit
Here is a working plunkr with angular 1.2.26
http://plnkr.co/edit/jkGKKCp0djN6Jvy2fIRd?p=preview
Pseudo Code:-
app.controller('myController', ['$scope', '$location','$http', 'ItemListService'
function($scope, $location, $http, ItemListService){
$scope.data = function(){
ItemListService.getAllItems(); //get all the items;
};
$scope.saveMethod = function(item){
$scope.data = ItemListService.save(item); //this is the refresh part, return data through save method. Pull the latest data and bind it to the scope.
$location.path('/fooView'); //dont think you even need this if you are entering data in a modal sorta thing, which on the same view.
}
}]);
You service should look like,
app.service('ItemListService', function(){
this.getAllItems = function(){
//get the items from itemList
//return all the items
}
this.save = function(item){
//save the item in itemList
//**return all items again, call getAllItems here too.
}
});
Hope this helps!!
You can switch https://github.com/angular-ui/ui-router it has method $state.reload() which can re-initialize whole controller.
If you dont want to switch ther is problem that controller is still living but you can implement after save
$rootScope.$broadcast('data:updated', $scope.data);
then wrap method of loading data in controller to function and then you can push new data to existing list / or make ajax reload
$rootScope.$on('data:updated',function(listener,data) {
$scope.data.push(data);
});
$rootScope.$on('data:updated',function()
{
callAjax.then(function(data) {
$scope.data = data;
}
});
https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$on
Try $scope.dataModel.$save(); $location.url('/foobar'); Another reason might solve the problem is: when you redirect to /foobar, the controller of foobar should have a AJAX call to your server to load the new data. And you should use angular factory to make your AJAX calls. If it is still not working, can you give more information about the version of the angular you are using, as well as your backend framework and database.
$location.path("/login");
$timeout(() => $scope.$apply(), 1000);
works for me
I'm using AngularJS and I need to have a global several global objects that can be accessed by any controller in the app.
For example one object that i need is a user object that has the users id and other properties. This user object comes from the database via ajax. So I need a way to set that user object, then initialize the controllers used on the page. Basically giving the app a page load for the fist load of the program.
Then if that object isn't set, I need to redirect.
Does anyone have an idea of how to do this cleanly? I've been trying to use broadcast but its truing into spaghetti code.
Currently I use ui-router, and have a hidden view with a controller GlobalsCtrl. GlobalsCtrl uses a service to get the objects and then $broadcasts them so controllers can initialize. But.... This broadcast only works on the initial site load. When changing $location.paths that event is not broadcast because the GlobalsCtrl variables are already set.
I could add some if statements but that seems messy to me.
Please help. Thanks!
Plunker - http://plnkr.co/edit/TIzdXOXPDV3d7pt5ah8i
var app = angular.module('editor.services', []);
app.factory('AnalysisDataService', ['$http', function($http) {
var self = this;
self.activeAnalysis = {};
self.openAnalysis = function(analysisId) {
return $http.get('api/v1/assignments/analysis/' + analysisId)
.success(function(data, status, headers, config) {
self.activeAnalysis = data;
return self.activeAnalysis;
}).error(function(data, status, headers, config) {
console.log("Error could not load analysis article.");
}).then(function(result) {
return self.activeAnalysis;
});
};
self.getAnalysis = function() {
return self.activeAnalysis;
};
self.navigateToStep = function(step) {
$location.path('/analysis/summary');
};
return {
open: self.openAnalysis,
get: self.getAnalysis,
navigate: self.navigateToStep,
}
}]);
The problem is I need the self.activeAnalysis variable to be set before a few other controllers load. Each page loads a different data-set based on the analysisId.
ui-router has a resolve method that you can use in your routes.
The resolve will tell the routing to wait and resolve something before it moves to the new route.
Here are some examples(Your problem is similar to authentication):
angular ui-router login authentication
https://gist.github.com/leon/6550951
One thing you can do is to use a resolve on the route with ui.router, ensuring that any promises are resolved before transitioning to the new state. Something like this:
.state('app.mymodule', {url: '/my-route/',
views:{
'someView': {
templateUrl: '/views/someView.html',
controller: 'someController'
}
},
resolve: {
someResolve: function (someService) {
return someService.getUserInfo();
}
}
})
Here, the $http call done in someService.getUserInfo() will be resolved before transitioning to that state. Inside that method, just set the data from the response in the service, and it will be available in the controller.
I want my Angular application to resolve a promise before changing the route to a certain path:
va.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/sendungen', {templateUrl: '../partials/sendungen.php', controller: 'OverviewCtrl',resolve: {
shipments: oc.fetchAllShipments
}}).
// ...
}]);
The funcion fetchAllShipments():
oc.fetchAllShipments = function(shipment){
shipment.fetchAllShipments().then(function(promise){
shipment.allShipments = promise.data;
});
};
The controller shall then copy the data from the shipment service to its $scope:
va.controller('OverviewCtrl',function($scope,$http,shipment){
$scope.allShipments = shipment.allShipments;
});
Everything is working fine as long as i change routes from within the application, e.g I load the mainpage, then switch to /sendungen
However, if i am already on that path and decide to refresh the page, the application is loaded before the data seems to be resolved. This happens only occasionally and seems to be depending on how fast he script was executed.
How do i prevent that behaviour?
The function in the resolve should return a promise, not like in your oc.fetchAllShipments method.
resolve - An optional map of
dependencies which should be injected into the controller. If any of
these dependencies are promises, the router will wait for them all to
be resolved or one to be rejected before the controller is
instantiated. If all the promises are resolved successfully, the
values of the resolved promises are injected and $routeChangeSuccess
event is fired.
For example:
resolve: {
shipments: ['$q', function($q){
var deffered = $q.defer();
shipment.fetchAllShipments().then(function(res){
deffered.resolve(res);
});
return deffered.promise;
}]
}
The quick and dirty fix will be to use $timeout:
va.controller('OverviewCtrl',function($scope,$http,shipment, $timeout){
$timeout(function(){$scope.allShipments = shipment.allShipments}, 1000);
});
I have an AngularJS app which grab data from PHP via AJAX and permit user to edit it through few steps.
Structure of the app is very simple :
I have a main controller which is loaded from ng-controller directive.
<div ng-controller="MainCtrl">
<!-- All my app take place here, -->
<!-- so all my others controllers are in MainCtrl scope -->
<div ng-view></div>
</div>
I have one controller by editing steps (ex. general info, planner, validation, etc.). Each controller is loaded by the $routeProvider (inside MainCtrl scope).
My problem is when I load (or refresh) the page, MainCtrl make an AJAX request to retrieve data to edit. The controller attached to $routeProvider is loaded before AJAX request is finished, so I can't use data grabbed by MainCtrl.
I want to defer $routeProvider loading route while AJAX request is not ended. I think I have to use the $q provider, but I can't prevent route loading.
I have tried this (in MainCtrl) and controller is still rendered premature :
$scope.$on('$routeChangeStart', function(event, current, previous) {
$scope.pathLoaded.promise.then(function() {
// Data loaded => render controller
return true;
});
// Stop controller rendering
return false;
});
And AJAX call is defined like this :
$scope.pathLoaded = $q.defer();
// Edit existing path
$http.get(Routing.generate('innova_path_get_path', {id: EditorApp.pathId}))
.success(function (data) {
$scope.path = data;
$scope.pathLoaded.resolve();
})
.error(function(data, status) {
// TODO
});
So the question is : is it the good way to achieve this ? And if yes, how can I defer controller rendering ?
Thanks for help.
You can use the resolve property of routes, execute the AJAX there and pass the result to your controller. In the route definition:
$routeProvider.when("path", {
controller: ["$scope", "mydata", MyPathCtrl], // NOTE THE NAME: mydata
templateUrl: "...",
resolve: {
mydata: ["$http", function($http) { // NOTE THE NAME: mydata
// $http.get() returns a promise, so it is OK for this usage
return $http.get(...your code...);
}]
// You can also use a service name instead of a function, see docs
},
...
});
See docs for more details. The controller for the given path will not be called before all members in the resolve object are resolved.
My problem is that i need a service loaded before the controller get called and the template get rendered.
http://jsfiddle.net/g75XQ/2/
Html:
<div ng-app="app" ng-controller="root">
<h3>Do not render this before user has loaded</h3>
{{user}}
</div>
JavaScript:
angular.module('app', []).
factory('user',function($timeout,$q){
var user = {};
$timeout(function(){//Simulate a request
user.name = "Jossi";
},1000);
return user;
}).
controller('root',function($scope,user){
alert("Do not alert before user has loaded");
$scope.user = user;
});
You can defer init of angular app using manual initialization, instead of auto init with ng-app attribute.
// define some service that has `$window` injected and read your data from it
angular.service('myService', ['$window', ($window) =>({
getData() {
return $window.myData;
}
}))
const callService = (cb) => {
$.ajax(...).success((data)=>{
window.myData = data;
cb(data)
})
}
// init angular app
angular.element(document).ready(function() {
callService(function (data) {
doSomething(data);
angular.bootstrap(document);
});
});
where callService is your function performing AJAX call and accepting success callback, which will init angular app.
Also check ngCloak directive, since it maybe everything you need.
Alternatively, when using ngRoute you can use resolve property, for that you can see #honkskillet answer
even better than manually bootstrapping (which is not always a bad idea either).
angular.module('myApp', ['app.services'])
.run(function(myservice) {
//stuff here.
});
As I said in the comments, it would be a lot easier to handle an unloaded state in your controller, you can benefit from $q to make this very straightforward:
http://jsfiddle.net/g/g75XQ/4/
if you want to make something in the controller when user is loaded: http://jsfiddle.net/g/g75XQ/6/
EDIT: To delay the route change until some data is loaded, look at this answer: Delaying AngularJS route change until model loaded to prevent flicker
The correct way to achieve that is using resolve property on routes definition:
see http://docs.angularjs.org/api/ngRoute.$routeProvider
then create and return a promise using the $q service; also use $http to make the request and on response, resolve the promise.
That way, when route is resolved and controller is loaded, the result of the promise will be already available and not flickering will happen.
You can use resolve in the .config $routeProvider. If a promise is returned (as it is here), the route won't load until it is resolved or rejected. Also, the return value will be available to injected into the controller (in this case Somert).
angular.module('somertApp')
.config(function($routeProvider) {
$routeProvider
.when('/home/:userName', {
/**/
resolve: {
Somert: function($q, $location, Somert) {
var deferred = $q.defer();
Somert.get(function(somertVal) {
if (somertVal) {
deferred.resolve(somertVal);
} else {
deferred.resolve();
$location.path('/error/'); //or somehow handle not getting
}
});
return deferred.promise;
},
},
});
});
There are a few ways, some more advanced than others, but in your case ng-hide does the trick. See http://jsfiddle.net/roytruelove/g75XQ/3/