I am trying to run the following code before any of my AngularJS app controllers, directives run, but unfortunately the app main page controller loads before this code finish executing, so I was wondering if there is a way to ensure that all my app controllers, directives won't run / load before this code finish completely? Thanks
myApp.run(['TokenSvc',function (TokenSvc) {
TokenSvc.getToken().then(function(serverToken){
console.log('Got it...');
}, function(status){
console.log(status);
});
}]);
Most commonly you'll see resolve in the ng-route or ui-router $state definition used for this concern, but that can be problematic. If the resolution takes a while, the user will just be staring at a blank screen. Of course, you can mitigate this problem by using an interceptor to display a loader, but I'd argue that that's outside the intended utility of interceptors.
I like to use something to manage the initialization promise(s), and inject that thing into top-level Controllers (i.e. either a Mediator or Observer pattern):
(function () {
function UserInfoLoader($q, facebookService, githubService) {
var _initPromise = null;
function initialization() {
var deferred = $q.defer(),
_initPromise = deferred.promise,
facebookLoading = facebookService.somePromiseFunc(),
githubLoading = githubService.somePromiseFunc();
$q.all([facebookLoading, githubLoading])
.then(function (results) {
// do something interesting with the results
deferred.resolve();
// set the promise back to null in case we need to call it again next time
_initPromise = null;
});
return promise;
}
this.initialize() {
// if there's already an initialization promise, return that
return _initPromise ? _initPromise : initialization();
}
}
angular.module('myApp').service('userInfoLoader', UserInfoLoader);
}());
This is great, because you can have multiple Controllers depend on the same workflow logic and they'll only produce one promise.
(function () {
function UserProfileController($scope, userInfoLoader) {
$scope.loading = true;
function load() {
userInfoLoader.initialize().then(function () {
$scope.loading = false;
});
}
load();
}
function UserMessagesController($scope, userInfoLoader) {
// same sort of loading thing
}
angular.module('myApp')
.controller('userProfileController', UserProfileController)
.controller('userMessagesController', UserMessagesController)
;
}());
To borrow from Mr. Osmani's book linked above, the loader service is like an air traffic controller. It coordinates the schedules of and passing information between multiple "airplanes", but they never have to talk to each other.
Another approach that I've seen is to use a FrontController, usually added on the body element, that manages a global loader, showing it during long-running async operations. That one's pretty simple, so I won't write it all out.
Do the fowllowing in each route:
$routeProvider.when("/your/path", {
templateUrl: "template/path",
controller: "controllerName",
resolve: {
getToken: ['TokenSvc',function (TokenSvc) {
return TokenSvc.getToken();
}]
}
});
You need that the getToken method return always the same object. Something like this:
obj.token = null;
obj.getToken = function(){
if(!obj.token){
var deferred = $q.defer();
obj.token = deferred;
deferred.promise.then(function(serverToken){
console.log("Got it. The token is ",serverToken);
}, function(status){
console.log("something is wrong ", status);
});
$http.get("url/to/token")
.success(function(data){
deferred.resolve(data);
})
.error(function(data, status){
deferred.reject(status);
});
}
return obj.token.promise;
}
Related
I have still problems with my database, but I found out, that the problems come from the fact, that opening the database takes some time, but the app is not waiting till this task has finished.
Is there a way to make angular wait till the database is opened correctly before it starts the next task?
Thanks,
Christian.
Update: 2016-08-08 - 03:13 pm
Thanks for all the answers. As I can see, my first idea with promises ($q) was right, but:
My app has an app.js, which is my main file. It only calls the InitDB. This should open the database. Then it should call the CreateTables, this creates the table, if it doensn't exist.
The rest of my app is splitted in 4 pages (templates). Every page has it's own controller.
So the idea was to init the db, to create the table, and then work with the database, but used over different controllers.
This won't work, because I would always need to put all of my stuff in the .then() of my initDB in the app.js???
This is my first AngularJS app, maybe this is the reason why I do a lot of mistakes...
Thanks,
Christian.
One of the core concepts of Angular is working with services/factories. There is ample documentation and blogs about how these work and how to use them, but the basic idea is that these are singleton "controllers" that handle shared data and methods across your entire application. Used in combination with promises, you can easily create a service that will manage communications with your database.
angular
.module('myApp')
.service('DBService', DBService)
.controller('Ctrl1', Ctrl1)
.controller('Ctrl2', Ctrl2)
.controller('Ctrl3', Ctrl3)
.controller('Ctrl4', Ctrl4);
DBService.$inject = ['$q'];
function DBService($q) {
var DBService = this;
var DBServiceDeferred = $q.defer();
DBService.ready = DBServiceDeferred.promise;
// a service is only initialized once, so this will only ever be run once
(function() {
init();
})();
function init() {
// you can use promise chaining to control order of events
// the chain will halt if any function is rejected
initDB()
.then(createTablesUnlessExist)
.then(setDbReady);
}
function initDB() {
var deferred = $q.defer();
// simulate async db initialization
$timeout(function() {
deferred.resolve();
// or reject if there is an error
// deferred.reject();
}, 5000);
return deferred.promise;
};
function createTablesUnlessExist() {
//create tables if needed (only happens once)
var deferred = $q.defer();
// simulate async table creation
$timeout(function() {
deferred.resolve();
// or reject if there is an error
// deferred.reject();
}, 5000);
return deferred.promise;
}
function setDbReady() {
DBServiceDeferred.resolve();
}
}
Now you have your DB setup and you don't have to worry about it anymore. You can access the DB from any controller using the service. None of the queries will run until the DB has been initialized and the tables have been created.
Ctrl1.$inject = ['DBService', '$q'];
function Ctrl1(DBService, $q) {
$q.when(DBService.ready).then(function() {
DBService.conn.query("Select something");
});
}
Ctrl2.$inject = ['DBService', '$q'];
function Ctrl2(DBService, $q) {
$q.when(DBService.ready).then(function() {
DBService.conn.query("Select something");
});
}
Ctrl3.$inject = ['DBService', '$q'];
function Ctrl3(DBService, $q) {
$q.when(DBService.ready).then(function() {
DBService.conn.query("Select something");
});
}
Ctrl4.$inject = ['DBService', '$q'];
function Ctrl4(DBService, $q) {
$q.when(DBService.ready).then(function() {
DBService.conn.query("Select something");
});
}
Angular provides a service $q. A service that helps you run functions asynchronously, and use their return values (or exceptions) when they are done processing. Please refer the documentation https://docs.angularjs.org/api/ng/service/$q for the same.
$q basically revolves around the concepts of promises. Promises in AngularJS are provided by the built-in $q service.
Write a method that check if connection is established or not...which returns true or false.
app.controller('MainCtrl', function($scope, httpq) {
http.get('server call method')
.then(function(data) {
if(data.conn==true)
// do what u want
//write other calls
})
.catch(function(data, status) {
console.error('error', response.status, response.data);
})
});
you can use $q library
example:
app.service("githubService", function ($http, $q) {
var deferred = $q.defer();
this.getAccount = function () {
return $http.get('https://api.github.com/users/haroldrv')
.then(function (response) {
// promise is fulfilled
deferred.resolve(response.data);
// promise is returned
return deferred.promise;
}, function (response) {
// the following line rejects the promise
deferred.reject(response);
// promise is returned
return deferred.promise;
})
;
};
});
using above service:
app.controller("promiseController", function ($scope, $q, githubService) {
githubService.getAccount()
.then(
function (result) {
// promise was fullfilled (regardless of outcome)
// checks for information will be peformed here
$scope.account = result;
},
function (error) {
// handle errors here
console.log(error.statusText);
}
);
});
Recently it has become possible to use angularjs within google apps script via the iframe sandbox mode.
My problem comes when trying to communicate with the server (gapps spreadsheet) and receiving asynchronous data in return.
The implementation for receiving data from the server is to use a function with a callback function like so:
google.script.run.withSuccessHandler(dataGatheringFunction).getServerData();
getServerData() would be a function that resides server-side that would return some data, usually from the accompanying spreadsheet. My question is how to use the callback function within the parameters of AngularJS. A typical $http function could be placed in a provider, and the scope value could be populated after then.() returns. I could also invoke $q. But how would I deal with the necessity of google's callback?
Here's a simplified version of what I'm messing with so far:
app.factory("myFactory", function($q){
function ssData(){
var TssData = function(z){
return z;
}
google.script.run.withSuccessHandler(TssData).getServerData();
var deferred = $q.defer();
var d = deferred.resolve(TssData)
console.log("DP: " + deferred.promise);
return deferred.promise;
}
return ssData();
})
Then in the controller resolve the server call similar to this:
myFactory.then(set some variables here with the return data)
My question is simply - How do I deal with that callback function in the provider?
The script throws no errors, but does not return the data from the server. I could use the old $timeout trick to retrieve the data, but there should be a better way.
You only need to $apply the output from the server function:
google.script.run.withSuccessHandler(function(data) {
$scope.$apply(function () {
$scope.data = data;
});
}).withFailureHandler(errorHandler).serverFunction();
Maybe the most elegant solution that makes sure the google.script.run callbacks are registered automatically in the AngularJS digest cycle would be to use the $q constructor to promisify the google callbacks. So, using your example above:
app.factory('myFactory', ['$q', function ($q){
return {ssData: ssData};
function ssData(){
var TssData = function(z){
return z;
};
var NoData = function(error) {
// Error Handling Here
};
return $q(function(resolve, reject) {
google.script.run
.withSuccessHandler(resolve)
.withFailureHandler(reject)
.getServerData();
}).then(TssData).catch(NoData);
}
}]);
Then in your controller you can call myFactory.ssData()
Since I don't know exactly what TssData is doing I included it here but note that this simply returns another promise in this context which you will still have to handle in your controller:
myFactory.ssData().then(function(response) {
// Set data to the scope or whatever you want
});
Alternately, you could expose TssData by adding it to the factory's functions if it is doing some kind of data transformation. If it is truly just returning the response, you could refactor the code and omit TssData and NoData and handle the promise entirely in the controller:
app.factory('myFactory', ['$q', function ($q){
return {ssData: ssData};
function ssData(){
return $q(function(resolve, reject) {
google.script.run
.withSuccessHandler(resolve)
.withFailureHandler(reject)
.getServerData();
});
}
}]);
app.controller('myController', ['myFactory', function(myFactory) {
var vm = this;
myFactory.ssData()
.then(function(response) {
vm.myData = response;
}).catch(function(error) {
// Handle Any Errors
});
}]);
An excellent article about promises (in Angular and otherwise) is here: http://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
This guy seems to be pulling data from a GSheet into angular quite happily without having to do anything fancy.
function gotData(res) {
$scope.validUser = res.validUser;
var data = angular.copy(res.data), obj, i=0;
Object.keys(data).forEach(function(sh) {
obj = {title: sh, checked: {}, showFilters: false, search: {}, sort: {index: 0, reverse: false}, currentPage: 0, checkedAll: true, showBtns: true, searchAll: ''};
obj.heading = data[sh].shift();
obj.list = data[sh];
obj.heading.forEach(function(s,i) {
obj.checked[i] = true;
});
$scope.sheets.push(obj);
});
$scope.sheets.sort(function(a,b) {
return a.title > b.title ? 1 : -1;
});
$scope.gotData = true;
$scope.$apply();
}
google.script.run.withSuccessHandler(gotData).withFailureHandler($scope.gotError).getData();
My solution was to get rid of the $q, promise scenario all together. I used $rootScope.$broadcast to update scope variables from the server.
Link to spreadsheet with script.
I have a pretty standard app which will display news items from a remote JSON feed. So basically I have decided to poll the remote server and store the JSON in localStorage (to enable offline usage). For the moment, I have a manual page/view I must click on to update the localStorage , this works fine.
The problem is that after I use my temporary manual update page, I then go to the news page/view and it is not updated. To view the current JSON contents I must hit refresh (while still developing in the browser.)
I'm totally new to Angular and have tried to find solutions to this myself - $watch or reload: true seem to be suggested as fixes, but I cannot get them to work in my case.
Route
.state('tab.news', {
url: '/news',
reload: true,
views: {
'news-tab': {
templateUrl: 'templates/news_home.html',
controller: 'newsCtrl'
}
}
})
factory
angular.module('schoolApp.services', [])
.factory('newsService', function($q) {
var newsHeadlines =localStorage.getItem('newsHeadlines') || '{"status":"READFAIL"}'; // get news as a JSON string. if newsHeadlines not found return a JSON string with fail status
var newsHeadlinesObj = JSON.parse(newsHeadlines);// convert to an object
console.log("factory newsService ran");
return {
findAll: function() {
var deferred = $q.defer();
deferred.resolve(newsHeadlinesObj);
return deferred.promise; // or reject(reason) to throw an error in the controller https://docs.angularjs.org/api/ng/service/$q
},
findById: function(newsId) {
var deferred = $q.defer();
var newsItem = newsHeadlinesObj[newsId];
deferred.resolve(newsItem);
return deferred.promise;
}
}
});
Controller
schoolApp.controller('newsCtrl', function($scope, newsService) {
console.log ( 'newsCtrl ran' );
newsService.findAll().then(function (newsHeadlinesObj) {
$scope.newsHeadlinesObj = newsHeadlinesObj;
}, function(error){
console.log(error)
});
})
Looking at my console, the first time I read the news, the factory then controller run, but if I go to pull more data down, then go hack to news, only the controller runs, unless I refresh, then both run again.
I do not need the news view to update 'live' while still on it (but if that can be easilly done all the better) - just to pick up new data when you go back to news after being elsewhere in the app.
Thank you.
Factories return singletons and only run once. The object newsService is cached by angular. The var declarations for newsHeadlines and newsHeadlinesObj will only ever run once; meaning your promise returning methods will always resolve the promise with the same data that was retrieved when your factory was first instantiated. You should put them in a function and call it from your find methods on the singleton object.
.factory('newsService', function($q) {
function getHeadlines() {
var newsHeadlines = localStorage.getItem('newsHeadlines') || '{"status":"READFAIL"}'; // get news as a JSON string. if newsHeadlines not found return a JSON string with fail
return JSON.parse(newsHeadlines);// convert to an object
}
return {
findAll: function() {
var headlines = getHeadlines();
var deferred = $q.defer();
deferred.resolve(headlines);
return deferred.promise; // or reject(reason) to throw an error in the controller https://docs.angularjs.org/api/ng/service/$q
},
findById: function(newsId) {
var headlines = getHeadlines();
var deferred = $q.defer();
var newsItem = headlines[newsId];
deferred.resolve(newsItem);
return deferred.promise;
}
}
});
PS - I'm sure you know and are planning to do things differently later or something, but just in case you don't: Using promises here is pointless and you have no need for $q here. You could simply return the data instead of returning the promises.
I solved this withouut promises, I just used $rootScope in the factory and $scope.$on in the controller; when I change the factory, i use $rootScope.$broadcast to tell the controller that I change it.
.factory('dataFactory', ['$http', '$rootScope', function ($http, $rootScope) {
var dataFactory = {
stock: null,
getStock: getStock
}
function getStock() {
$http.get("/api/itemfarmacia/").then(function success(res) {
dataFactory.stock = res.data;
$rootScope.$broadcast('changingStock'); //Ones who listen this will see it
}, function error(err) {
console.log("Bad request");
})
}
return dataFactory;
}])
and in the controller
.controller('atencion', ["$scope", "$state", "dataFactory", function ($scope, $state, dataFactory) {
$scope.stock = dataFactory.stock; //At first is null
dataFactory.getStock(); //wherever you execute this, $scope.stock will change
$scope.$on('changingStock', function () {//Listening
$scope.stock = dataFactory.stock; //Updating $scope
})
}])
I am using some data which is from a RESTful service in multiple pages.
So I am using angular factories for that. So, I required to get the data once from the server, and everytime I am getting the data with that defined service. Just like a global variables. Here is the sample:
var myApp = angular.module('myservices', []);
myApp.factory('myService', function($http) {
$http({method:"GET", url:"/my/url"}).success(function(result){
return result;
});
});
In my controller I am using this service as:
function myFunction($scope, myService) {
$scope.data = myService;
console.log("data.name"+$scope.data.name);
}
Its working fine for me as per my requirements.
But the problem here is, when I reloaded in my webpage the service will gets called again and requests for server. If in between some other function executes which is dependent on the "defined service", It's giving the error like "something" is undefined. So I want to wait in my script till the service is loaded. How can I do that? Is there anyway do that in angularjs?
You should use promises for async operations where you don't know when it will be completed. A promise "represents an operation that hasn't completed yet, but is expected in the future." (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise)
An example implementation would be like:
myApp.factory('myService', function($http) {
var getData = function() {
// Angular $http() and then() both return promises themselves
return $http({method:"GET", url:"/my/url"}).then(function(result){
// What we return here is the data that will be accessible
// to us after the promise resolves
return result.data;
});
};
return { getData: getData };
});
function myFunction($scope, myService) {
var myDataPromise = myService.getData();
myDataPromise.then(function(result) {
// this is only run after getData() resolves
$scope.data = result;
console.log("data.name"+$scope.data.name);
});
}
Edit: Regarding Sujoys comment that
What do I need to do so that myFuction() call won't return till .then() function finishes execution.
function myFunction($scope, myService) {
var myDataPromise = myService.getData();
myDataPromise.then(function(result) {
$scope.data = result;
console.log("data.name"+$scope.data.name);
});
console.log("This will get printed before data.name inside then. And I don't want that.");
}
Well, let's suppose the call to getData() took 10 seconds to complete. If the function didn't return anything in that time, it would effectively become normal synchronous code and would hang the browser until it completed.
With the promise returning instantly though, the browser is free to continue on with other code in the meantime. Once the promise resolves/fails, the then() call is triggered. So it makes much more sense this way, even if it might make the flow of your code a bit more complex (complexity is a common problem of async/parallel programming in general after all!)
for people new to this you can also use a callback for example:
In your service:
.factory('DataHandler',function ($http){
var GetRandomArtists = function(data, callback){
$http.post(URL, data).success(function (response) {
callback(response);
});
}
})
In your controller:
DataHandler.GetRandomArtists(3, function(response){
$scope.data.random_artists = response;
});
I was having the same problem and none if these worked for me. Here is what did work though...
app.factory('myService', function($http) {
var data = function (value) {
return $http.get(value);
}
return { data: data }
});
and then the function that uses it is...
vm.search = function(value) {
var recieved_data = myService.data(value);
recieved_data.then(
function(fulfillment){
vm.tags = fulfillment.data;
}, function(){
console.log("Server did not send tag data.");
});
};
The service isn't that necessary but I think its a good practise for extensibility. Most of what you will need for one will for any other, especially when using APIs. Anyway I hope this was helpful.
FYI, this is using Angularfire so it may vary a bit for a different service or other use but should solve the same isse $http has. I had this same issue only solution that fit for me the best was to combine all services/factories into a single promise on the scope. On each route/view that needed these services/etc to be loaded I put any functions that require loaded data inside the controller function i.e. myfunct() and the main app.js on run after auth i put
myservice.$loaded().then(function() {$rootScope.myservice = myservice;});
and in the view I just did
ng-if="myservice" ng-init="somevar=myfunct()"
in the first/parent view element/wrapper so the controller can run everything inside
myfunct()
without worrying about async promises/order/queue issues. I hope that helps someone with the same issues I had.
how do you bootstrap a controller that loaded asynchronously via require.js?
if I have something like that:
$routeProvider.when('/',
{
templateUrl:'view1.html',
controller:'ctrl',
resolve:{
load:function($q){
var dfrd = $q.defer();
require(['view1-script'],function(){
dfrd.resolve();
})
return dfrd.promise;
}
}
})
why angular still won't find the controller? I am resolving the route after it loads the script
check out this plunkr
try calling $controllerProvider.register to create your controller. I would also call $apply() on the $rootScope after resolving the deferred because without it, the view does not seem to appear:
load: function($q, $rootScope){
var dfrd = $q.defer();
require(['view1'],function(){
dfrd.resolve();
$rootScope.$apply();
})
return dfrd.promise;
}
http://plnkr.co/edit/fe2Q3BhxPYnPmeiOORHP
in addition, here is a good post: http://weblogs.asp.net/dwahlin/archive/2013/05/22/dynamically-loading-controllers-and-views-with-angularjs-and-requirejs.aspx
It's been 3 years, but just in case anyone still interested, a few months ago I wrote a post about a similar technique to do it.
The most important part is that second parameter of the method $routeProvider.when(route, ctrl) method can handle promises, so you can simply emulate it:
function controllerFactory(ctrl) {
return {
then: function (done) {
var self = this;
require(['./controller/' + ctrl], function (ctrl) {
self.controller = ctrl;
self.resolve = ctrl.resolve;
self.templateUrl = ctrl.templateUrl;
done();
});
}
};
}
And you can end up writing your route definition like this:
$routeProvider.
when('/some/route', controllerFactory('some/route')).
when('/other/route', controllerFactory('other/route'))