I have an application where some data must be loaded(ajax) before actual application is rendered. I would like to show some splash screen to provide feedback to user, that something is still loading.
Where/How should I call initialize method of that services?
var app = angular.module('App', []);
app.service('DataSet1', function(){
this.initialize = function(){
//.. returns promiese
}
});
app.service('DataSet2', function(){
this.initialize = function(){
//.. returns promiese
}
});
app.service('DataSet3', function(){
this.initialize = function(){
//.. returns promiese
}
});
I would appretiate any help how to achieve this
edit: adding jsbin example
new answer
jsbin example
This new approach, by passing in {showLoader: true} as part of the request option, you can specify which requests to show the "loading..." panel for.
This leverages httpInterceptor and factory to keep track of pending requests.
old answer (loading will show for any pending request)
http.pendingRequests.length !== 0;
Be sure http is exposed in your scope null.
<spinner ng-if="http.pendingRequests.length"></spinner>
Related
I am beginning to use Angularjs. I have this working properly.
There is a dynamically created sidebar listing all the styles.
There is a dynamically created navbar for sizes associated with that style.
When clicked style is "selected" properly.
When clicked size is "selected" properly.
I want these two selections to be parameters in a url that I GET and display within a specific DIV. So the url would look like ...http://xxxddd.com/inventory/{{style}}/{{size}}
I have absolutely no idea where to go from here.
<script>
var app = angular.module("alt", []);
app.controller('StyleController', function(){
this.style = '';
this.selectStyle = function(newValue){
this.style = newValue;
};
this.isSelected = function(styleName){
return this.style === styleName;
};
});
app.controller('SizeController', function(){
this.size = '';
this.selectSize = function(newValue){
this.size = newValue;
};
this.isSelected = function(sizeName){
return this.size === sizeName;
};
});
</script>
I am very new at angular. I hope someone can help me. This is the main hangup with my project right now.
Thanks in advance.
You will want to create a service that utilizes AngularJS' $http library to make a GET request. Look at the documentation and see how to pass parameters (style/size).
You will then want to use AngularJS' Dependency Injection (DI) to inject your service into your controller and call its method which has the $http call.
How can I make an Angular service code "look synchronous"?
My questions arose when I cleaned my controller and put the business logic code into a service instead. So far so good. Now I would like to "wait" in the service function until all asynchronous calls have returned and then return. How can I do that?
To illustrate my problem, suppose you have a controller code which just:
requests some data from the backend
does some processing with the data and
hands the data over to the scope
Like that:
DataController before refactoring:
$scope.submitForm = function() {
RestBackend.query('something').then(function(data) {
// do some additional things ...
...
$scope.data = data;
});
};
Pretty straightforward. Fetch data and fill scope.
After refactoring into controller + service, I ended up with:
DataController refactored:
$scope.submitForm = function() {
DataService.getData().then(function(data) {
$scope.data = data;
});
};
DataService refactored:
this.query = function() {
var dataDefer = $q.defer();
RestBackend.query('something').then(function(data) {
// do some additional things ...
...
dataDefer.resolve(data);
});
return dataDefer.promise;
};
I dislike the fact that I have to work with a promise in the controller also. I like promises but I want to keep the controller agnostic of this "implementation detail" of the service. This is what I would like the controller code to look like:
DataController (as it should be):
$scope.submitForm = function() {
$scope.data = DataService.getData();
};
You get the point? In the controller I don't want to care about promise or not. Just wait for the data to be fetched and then use it. Thus, I am looking for a possibility to implement the service like this:
query the data (asynchronously)
do not return until the data has been fetched
return the fetched data
Now item 2. is not clear to me: How can I "wait until data has been fetched" and only proceed afterwards? The goal is that the service function looks synchronous.
I too think your solution is fine.
Returning a promise is not an implementation detail of the service. It is part of the service's API (the "contract" between the service and the service-consumer).
The controller expects a promise that resolves with the data and handles that as it sees fit.
How that promise is constructed, how the data is fetched etc, these are the implementation details.
You can swap the service at any time with one that does totally different things as long as it fulfills the contract (i.e. returns a promise that resolves with the data onve ready).
That said, if you only use the data in the view (i.e. do not directly manipulate it in the controller right after it is fetched), which seems to be the case, you can use ngResources approach:
Return an empty array and populate it with the data once it is fetched:
$scope.data = DataService.getData();
// DataService refactored:
this.getData = function () {
var data = [];
RestBackend.query('something').then(function(responseData) {
// do some additional things ...
...
angular.forEach(responseData, function (item) {
data.push(item);
});
});
return data;
};
BTW, in your current (fine) setup, you need $q.defer(). You can just use promise-chaining:
this.query = function() {
return RestBackend.query('something').then(function(data) {
// do some additional things ...
...
return data;
});
};
I think what you have is a very good solution. You should not have to wait for promise to be resolved, it defeats the purpose of async javascript. Just ask yourself why do you need to make it run sync?
If you rely in html on promise to be resolve you can do something like this
<div class="alert alert-warning text-center" data-ng-hide="!data.$resolved">
Got data from service.
</div>
As you use ngRoute, I would recommend you to resolve you data in your route config, and the view will be loaded once all your data will be resolved.
$routeProvider
.when('/your-url', {
templateUrl: 'path/to/your/template.html',
controller: 'YourCtrl',
// that's the point !
resolve: {
superAwesomeData: function (DataService) {
return DataService.getData();
}
}
});
Now, superAwesomeData can be injected in your controller and it will contains the data, resolved.
angular.module('youModule')
.controller('YourCtrl', function (superAwesomeData) {
// superAwesomeData === [...];
});
I wonder if I can call controller method from service.
I know that Service is singleton and I can't inject $scope to the service.
In my case I manage Google Maps in service and want to open modal Dialog when user right clicks on Polygon.
As I know, to open/create new instance of dialog, somehow Service must notify controller to do that.
This is a template with controller + method and service: Template
var myApp = angular.module('myApp', []);
function MyCtrl($scope, gridService, $timeout) {
// how to call "foo" method from service?
$scope.foo = function(){
alert('called from service');
};
}
myApp.service('gridService', ['$timeout', function ( $timeout) {
var grid = {
fetching: false,
pristine: true,
pageType: 'Edit'
}
return {
gridSetup: function () {
return grid;
},
setGridSetup: function (newGrid) {
}
}
}]);
Thanks,
The answer is simple: you don't.
The service exists to manipulate data, nothing else. And it really shouldn't care "why" it's doing what it's doing. You ask the service to do something and wait for a response.
Personally I prefer using promises to resolve async operations (i.e. to notify the controller about a state change) since it's heavily supported by many angular services like $http.
But feel free to use callbacks of you wish.
Usually you do not need to call controller from the service - in general the single service could be used by any controller so service shall know nothing about them. In most cases controller calls to the service in react to some user's action and then you need to update view from controller somehow when service did its work (get response from server, etc.). I see following general ways how to do it.
1. Use callbacks.
//controller
$scope.onButtonClick = function() {
$scope.label = "wait, loading...";
function onSuccess(result) {
$scope.label = "done! result is " + result;
}
myService.doSomeWork(param1, param2, onSuccess);
}
//service
doSomeWork: function(param1, param2, onSuccess) {
$.get({...}, onSuccess);
}
So you provide a callback for each action.
2. Subscribe on events
You may use jQuery for events subscribing/triggering
//controller
$(myService).on('update', function() {
$scope.lastUpdateTime = new Date();
});
$scope.onButtonClick = function() {
myService.doUpdate();
}
//service
doUpdate: function() {
$.get({...}, function onOk() {
$(this).trigger('update');
});
}
3. Use promises
A lot of built-in angular services return promise objects, you may use them too:
//controller
myService.doSomething(param1).then(function(result) {
$scope.result = result;
});
//service
doSomething: function(param1) {
return $http.get({...});
}
4. Share some data
An example is $resource service - for example when you call query method it returns empty array-like object that could be safely put to scope and then fills it with values when http request is done.
//controller
$scope.options = myService.options;
$scope.onClick = function() { myService.update() }
//service
options: [],
update: function() {
var self = this;
$http.get({...}).success(function(res) {
self.options.splice(0, self.options.length); //to keep same array
self.options.push.apply(self.options, res.data.options);
});
}
In all these cases services and controllers are separated, services could be used with any controller and you may easily write unit-tests on services that will not break if you change your controller/view part somehow.
A possible solution would be to have a dialog service which you can inject into the grid service. So when the user right clicks on the polygon the handler would call open on the dialog service.
Take a look at the modal service on angular ui as an example.
I'm trying to developpe a chrome extension with angularjs and I have a strange behaviour when I try to initialize the $scope with the url of the active tab.
Here the code of my controller:
var app = angular.module('app', ['app.service']);
app.controller('ItemCtrl', function ($scope, chromeHelper) {
$scope.website = "No result!";
// Does not work until I click on something :-/
chromeHelper.getActiveTabDomain(function (domain) {$scope.website = domain; });
});
So when I try to initialize directly the $scope.website member it doesn't succeed but when I click on the button aftewards $scope.website then updates.
I really don't understand why.
Here is the code of my Chromehelper service:
var service = angular.module('app.service', []);
service.factory('chromeHelper', function() {
var chromeHelper = {};
chromeHelper.getActiveTabDomain = function (callback){
chrome.tabs.query({'active': true}, function(tabs){
if(tabs && tabs.length > 0) callback(getDomainFrom(tabs[0].url));
});
};
return chromeHelper;
});
function getDomainFrom(url) {
return url.match(/:\/\/(.[^/]+)/)[1];
}
Thank you very much in advance!
The OP solved the problem (see comment above) by adding $scope.$apply() at the end of the callback:
// Does not work until I click on something :-/
chromeHelper.getActiveTabDomain(function(domain) {
$scope.website = domain;
$scope.$apply(); // <-- adding this line did the trick
});
A short explanation for anyone landing on this page with a similar problem:
From the AngularJS docs on 'scope' (more specifically from the section titled 'Scope Life Cycle'):
Model mutation
For mutations to be properly observed, you should make them only within the scope.$apply(). (Angular APIs do this implicitly, so no extra $apply call is needed when doing synchronous work in controllers, or asynchronous work with $http or $timeout services.
See, also, this short demo.
I am writing a small Angular web application and have run into problems when it comes to loading the data. I am using Firebase as datasource and found the AngularFire project which sounded nice. However, I am having trouble controlling the way the data is being displayed.
At first I tried using the regular implicit synchronization by doing:
angularFire(ref, $scope, 'items');
It worked fine and all the data was displayed when I used the model $items in my view. However, when the data is arriving from the Firebase data source it is not formatted in a way that the view supports, so I need to do some additional structural changes to the data before it is displayed. Problem is, I won't know when the data has been fully loaded. I tried assigning a $watch to the $items, but it was called too early.
So, I moved on and tried to use the angularfireCollection instead:
$scope.items = angularFireCollection(new Firebase(url), optionalCallbackOnInitialLoad);
The documentation isn't quite clear what the "optionalCallbackOnInitialLoad" does and when it is called, but trying to access the first item in the $items collection will throw an error ("Uncaught TypeError: Cannot read property '0' of undefined").
I tried adding a button and in the button's click handler I logged the content of the first item in the $items, and it worked:
console.log($scope.items[0]);
There it was! The first object from my Firebase was displayed without any errors ... only problem is that I had to click a button to get there.
So, does anyone know how I can know when all the data has been loaded and then assign it to a $scope variable to be displayed in my view? Or is there another way?
My controller:
app.controller('MyController', ['$scope', 'angularFireCollection',
function MyController($scope, angularFireCollection) {
$scope.start = function()
{
var ref = new Firebase('https://url.firebaseio.com/days');
console.log("start");
console.log("before load?");
$scope.items = angularFireCollection(ref, function()
{
console.log("loaded?");
console.log($scope.items[0]); //undefined
});
console.log("start() out");
};
$scope.start();
//wait for changes
$scope.$watch('items', function() {
console.log("items watch");
console.log($scope.items[0]); //undefined
});
$scope.testData = function()
{
console.log($scope.items[0].properties); //not undefined
};
}
]);
My view:
<button ng-click="testData()">Is the data loaded yet?</button>
Thanks in advance!
So, does anyone know how I can know when all the data has been loaded
and then assign it to a $scope variable to be displayed in my view? Or
is there another way?
Remember that all Firebase calls are asynchronous. Many of your problems are occurring because you're trying to access elements that don't exist yet. The reason the button click worked for you is because you clicked the button (and accessed the elements) after they had been successfully loaded.
In the case of the optionalCallbackOnInitialLoad, this is a function that will be executed once the initial load of the angularFireCollection is finished. As the name implies, it's optional, meaning that you don't have to provide a callback function if you don't want to.
You can either use this and specify a function to be executed after it's loaded, or you can use $q promises or another promise library of your liking. I'm partial to kriskowal's Q myself. I'd suggest reading up a bit on asynchronous JavaScript so you get a deeper understanding of some of these issues.
Be wary that this:
$scope.items = angularFireCollection(ref, function()
{
console.log("loaded?");
console.log($scope.items[0]); //undefined
});
does correctly specify a callback function, but $scope.items doesn't get assigned until after you've ran the callback. So, it still won't exist.
If you just want to see when $scope.items has been loaded, you could try something like this:
$scope.$watch('items', function (items) {
console.log(items)
});
In my project I needed to know too when the data has been loaded. I used the following approach (implicit bindings):
$scope.auctionsDiscoveryPromise = angularFire(firebaseReference.getInstance() + "/auctionlist", $scope, 'auctionlist', []);
$scope.auctionsDiscoveryPromise.then(function() {
console.log("AuctionsDiscoverController auctionsDiscoveryPromise resolved");
$timeout(function() {
$scope.$broadcast("AUCTION_INIT");
}, 500);
}, function() {
console.error("AuctionsDiscoverController auctionsDiscoveryPromise rejected");
});
When the $scope.auctionsDiscoveryPromise promise has been resolved I'm broadcasting an event AUCTION_INIT which is being listened in my directives. I use a short timeout just in case some services or directives haven't been initialized yet.
I'm using this if it would help anyone:
function getAll(items) {
var deferred = $q.defer();
var dataRef = new Firebase(baseUrl + items);
var returnData = angularFireCollection(dataRef, function(data){
deferred.resolve(data.val());
});
return deferred.promise;
}