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/
Related
I want to change the color of font in an input field depending on the value coming from firebase.
In jquery I use $(document).ready() but my code fires before the firebase data is loaded into the dom.
I have reverted to using using setTimeout() to give the dom enough time to load which is not really the way to do it.
There must be an event the fires after the data is attched to the DOM?
app.controller('myCtrl', function($scope, $stateParams, $firebaseObject) {
var ref = new Firebase('xxxxxxxxx');
$scope.Details = $firebaseObject(ref);
//what I really need is, "tell me when the firebase object is loaded to the DOM
//so I can do my stuff"
setTimeout(function(){
if($("#idInput").val() ==='foo'){
$("#idInput").css("color", "red");
}
}, 500);
});
Use $loaded(), if you need to use the data from the $firebaseObject.
$scope.Details = $firebaseObject(ref);
$scope.Details.then(function(data) {
// loaded data here
});
Otherwise though, the $firebaseObject informs the $digest loops when the data has loaded.
Another tactic is to use resolve in the router to load the data into the controller. This is much cleaner, because you don't need to unwrap the promise.
.config(function($stateProvider) {
$stateProvider.state('home', {
controller: 'myCtrl',
template: 'myTemplate.html',
resolve: {
details: function($firebaseObject, $stateParams) {
var ref = new Firebase('xxxxxxxxx');
var childRef = ref.child($stateParams.id);
return $firebaseObject(childRef).$loaded();
}
}
});
})
Then in your controller the data will be resolved:
.controller('myCtrl', function($scope, details) {
$scope.Details = details; // totally available to use
})
Read the docs for more information on resolving data with routing and AngularFire.
Don't seems that you need to use setTimeout here, you could use ng-class directive to apply CSS on change of input value.
HTML
<input id="idInput" ng-model="idInput" ng-class="{ 'red': idInput == 'foo' }"/>
CSS
.red {
color: red;
}
If you are really interested to do something after data gets loaded through $firebaseObject(ref) then you should use $loaded method over $firebaseObject which will get called once data loaded. Will prefer to use $timeout instead of setTimeout to make sync scope variable binding with html by running digest cycle. Additionally don't do DOM manipulation from controller its considered as antipattern.
Code
$scope.Details = $firebaseObject(ref);
$scope.Details.$loaded()
.then(function(data) {
//do some code
$timeout(function(){
//you shouldn't use DOM manipulation from angular controller.
//if($("#idInput").val() ==='foo'){
//$("#idInput").css("color", "red");
}
}, 500);
})
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.
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>