Right now I have an Angular service named chartModelService with this method on it:
this.fetchChartModel = function() {
return $http.get(fullUrlPath);
};
And I have an Angular controller that consumes this data:
chartModelService.fetchChartModel().then(function(rawData) {
var chartData = chartModelService.translate(rawData);
$scope.populateChart(chartData);
});
I would like to perform the chartModelService.translate(data) within the chartModelService instead of the controller using the service. In other words, I would like to change to controller to the code below, where the chartData it receives has already been translated:
chartModelService.fetchChartModel().then(function(chartData) {
$scope.populateChart(chartData);
});
How can I change chartModelService.fetchChartModel() so that it performs translate() before returning the promised data?
Change this:
this.fetchChartModel = function() {
return $http.get(fullUrlPath);
};
To This:
this.fetchChartModel = function() {
var defObj = $q.defer();
var fetch = $http.get(fullUrlPath);
fetch.then(function(data){
defObj.resolve(chartModelService.translate(data));
});
return defObj.promise;
};
(with the appropriate DI on your service, of course)
This will init the data fetch and return a promise that contains your translated data when it has been fulfilled.
just move your code ;)
this.fetchChartModel = function() {
return $http.get(fullUrlPath)
.then(function(rawData) {
return $q.resolve(chartModelService.translate(rawData);)
});
};
that should do it just make sure you include $q service in your dependencies
For me didn't work $q.resolve, but you can use instead $q.when
this.fetchChartModel = function() {
return $http.get(fullUrlPath)
.then(function(rawData) {
return $q.when(chartModelService.translate(rawData));
});
};
Related
I'm trying to use AJAX to bring in some data and then modify it before sending the result to a controller. I had this all working in the controller but I was told that it's better to do it in a service.
app.factory('GameweekJsonFactory', ['$http',function($http){
var promise;
var service = {
async: function() {
if ( !promise ) {
promise = $http.get('jsons/gameweeks.json').then(function(response){
return response.data
});
}
return promise;
}
}
return service;
}]);
Then in another factory I wanted to change the value of a variable dependent on the response of the AJAX call. (Say for example if it's gameweek 6 going by date, I want gwk to equal 6).
app.factory('GameweekFactory',['GameweekJsonFactory',function(GameweekJsonFactory) {
var gwk;
var obj = {
foo: function(){
GameweekJsonFactory.async().then(function(d){
//Code here to find out the current gameweek.
gwk = 6;
return gwk;
});
}
};
return gwk;
}]);
controller: function(GameweekFactory){
console.log('Controller Log '+GameweekFactory)
},
How do I then modify the original gwk variable in the factory's scope? I can't work it out and I've been googling for ages!
You are not defining the factory correctly. AngularJS factories are functions that return a value, and in this case it's an object with a method named foo.
Promises are chainable. The value returned by a success callback is passed into the next success callback. So you can manipulate the value (add gwk) and return that value.
app.factory('GameweekFactory',[
'GameweekJsonFactory',
function(GameweekJsonFactory) {
return {
foo: function(){
return GameweekJsonFactory.async().then(function(d){
//Code here to find out the current gameweek.
d.gwk = 6;
return d;
});
}
};
}]);
controller: function(GameweekFactory){
GameweekFactory.foo().then(function(d){
console.log('Controller Log '+d.gwk)
});
}
In one of my factories I need to set a variable when data is fetched (through $http) so I can access it in my controller (the intention is to display a spinner image until the data is loaded).
.factory('LoadData', function LoadData($http, $q){
return {
getMyData: function(){
return $http(
// implementation of the call
).then(
function(response){
var myData = response.data;
// this should be reference to the other method (getLoadStatus)
// where I want to change its value to "true"
// this doesn't work - "this" or "self" don't work either because we're in another function
LoadData.getLoadStatus.status = true;
}
);
},
// by calling the below method from my controller,
// I want to mark the completion of data fetching
getLoadStatus = function(){
var status = null;
return status;
}
}
}
I hope you got the idea - how could this be accomplished? Also, I'm open to any suggestions which are aimed towards a better approach (I want to pick up best practice whenever possible).
Status is essentially a private variable; use it as:
.factory('LoadData', function LoadData($http, $q){
var status = null; // ESSENTIALLY PRIVATE TO THE SERVICE
return {
getMyData: function(){
return $http(...).then(function(response){
...
status = true;
});
},
getLoadStatus = function(){
return status;
}
};
})
There are several ways.
Here's one which I prefer to use:
.factory('LoadData', function LoadData($http, $q){
var status = false;
var service = {
getMyData: getMyData,
status: status
};
return service;
function getMyData() {
return $http(
// implementation of the call
).then(
function(response){
var myData = response.data;
status = true;
}
);
}
}
This provides good encapsulation of your methods and gives you a clean interface to export. No need for the getter method if you don't want it.
Inspiration via John Papa's Angular style guide (found here).
You could simply store variable flag in closure:
.factory('LoadData', function LoadData($http, $q) {
var status = false;
return {
getMyData: function() {
status = false;
return $http(/* implementation of the call */).then(function(response) {
status = true;
return response.data;
});
},
getLoadStatus: function() {
return status;
}
}
});
Also if getMyData loads fresh data every time, it's important to reset status to false before each request.
I actually decide to use a promise in my controller after calling this service and when data is returned, I am simply making the status true. It seems that is best practice to have a promise returned when calling a service so I should be good.
This is the function that I am working with to call my factory
var myService = function($http) {
return {
bf: null,
initialize: function() {
this.promise = $http.get(this.server + "/requestkey").success(function(data) {
myService.bf = new Blowfish(data.key);
});
}
}
And I am creating this object using
TicTacTorrent.service('AService', ['$http', myService]);
However, when calling AService.initialize() it creates the promise object like it should, but it doesn't update the BF object. I'm confused as to how to update the bf object to be the new value. How would I reference myService.bf since this.bf would create a local instance for .success function?
Try this:
var myService = function($http) {
this.bf = null;
return {
bf: this.bf,
initialize: function() {
this.promise = $http.get(this.server + "/requestkey").success(function(data) {
myService.bf = new Blowfish(data.key);
});
}
}
Where do you want to initialize?
Have you seen the $provider example code?
Search for "provider(name, provider)" and check if it suits your need.
Otherwise I'm unsure what the code you'vew written will run like.
I usually write factories like this:
angular.module('app').factory('myService', ['$http', function($http) {
var publicObj = {};
publicObj.bf = ""; // Just to make sure its initialized correctly.
publicObj.initialize = function() {snip/snap... myService.bf = new Blowfish(data.key);};
return publicObj;
}]);
The difference might be that you previous code returned an inline anonymous object which might have a hard time referring to itself. But by that logic it should work by just making myService return a predeclared var and returning that.
I'm attempting to use Angularjs to gather data from the USGS Earthquake feed. Typically you would need to tack ?callback=JSON_CALLBACK on to the end of the URL for Angular to use it, however the USGS feed does not recognize this option.
The URL I'm using is http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojsonp and adding ?callback=JSON_CALLBACK (eg. http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojsonp?callback=JSON_CALLBACK) returns a dataset wrapped in a function called eqfeed_callback.
Is there any way to use this data? I've got an eqfeed_callback function but it's not in scope which makes using Angular pointless.
Here's the code that I've got as it stands:
function QuakeCtrl($scope, $http) {
$scope.get_quakes = function() {
var url = 'http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojsonp';
$http.jsonp(url)
}
}
function eqfeed_callback(data) {
return data;
}
Is there any way to either get the data back into the scope, or get angular to use the eqfeed_callback function internally?
Another option would be defining the eqfeed_callback within the scope like this:
function QuakeCtrl($scope, $http) {
$scope.data = null;
$scope.get_quakes = function() {
var url = 'http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojsonp';
$http.jsonp(url)
}
window.eqfeed_callback = function(data) {
$scope.data = data
}
}
The only idea that comes to mind is (blech) to use a global, and then to manually trigger an Angular update, e.g.:
var callback$scope;
function QuakeCtrl($scope, $http) {
$scope.get_quakes = function() {
var url = 'http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojsonp';
callback$scope = $scope;
$http.jsonp(url)
}
}
function eqfeed_callback(data) {
if (callback$scope) {
// 1. Use callback$scope here
// 2. Set callback$scope to undefined or null
// 3. Trigger an Angular update (since it won't be automatic)
}
}
Not pretty, but...
Expanding on #MichaelVanRijn's answer:
In order to keep the "global peace", define the global function when you need it and nullify it right after.
.controller('QuakeCtrl', function($window, $scope, $http) {
$scope.get_quakes = function() {
$window.eqfeed_callback = function(data){
console.log("quake data", data)
};
// load your jsonp data
$http.jsonp('http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojsonp')
.then(function(success) {
console.log(success);
$window.eqfeed_callback = null;
}, function(fail) {
console.log(fail);
$window.eqfeed_callback = null;
})
}
})
I'm having trouble trying to initialize a filter with asynchronous data.
The filter is very simple, it needs to translate paths to name, but to do so it needs a correspondance array, which I need to fetch from the server.
I could do things in the filter definition, before returning the function, but the asynchronous aspect prevents that
angular.module('angularApp').
filter('pathToName', function(Service){
// Do some things here
return function(input){
return input+'!'
}
}
Using a promise may be viable but I don't have any clear understanding on how angular loads filters.
This post explains how to achieve such magic with services, but is it possible to do the same for filters?
And if anyone has a better idea on how to translate those paths, I'm all ears.
EDIT:
I tried with the promise approch, but something isn't right, and I fail to see what:
angular.module('angularApp').filter('pathToName', function($q, Service){
var deferred = $q.defer();
var promise = deferred.promise;
Service.getCorresp().then(function(success){
deferred.resolve(success.data);
}, function(error){
deferred.reject();
});
return function(input){
return promise.then(
function(corresp){
if(corresp.hasOwnProperty(input))
return corresp[input];
else
return input;
}
)
};
});
I'm not really familliar with promises, is it the right way to use them?
Here is an example:
app.filter("testf", function($timeout) {
var data = null, // DATA RECEIVED ASYNCHRONOUSLY AND CACHED HERE
serviceInvoked = false;
function realFilter(value) { // REAL FILTER LOGIC
return ...;
}
return function(value) { // FILTER WRAPPER TO COPE WITH ASYNCHRONICITY
if( data === null ) {
if( !serviceInvoked ) {
serviceInvoked = true;
// CALL THE SERVICE THAT FETCHES THE DATA HERE
callService.then(function(result) {
data = result;
});
}
return "-"; // PLACEHOLDER WHILE LOADING, COULD BE EMPTY
}
else return realFilter(value);
}
});
This fiddle is a demonstration using timeouts instead of services.
EDIT: As per the comment of sgimeno, extra care must be taken for not calling the service more than once. See the serviceInvoked changes in the code above and the fiddles. See also forked fiddle with Angular 1.2.1 and a button to change the value and trigger digest cycles: forked fiddle
EDIT 2: As per the comment of Miha Eržen, this solution does no logner work for Angular 1.3. The solution is almost trivial though, using the $stateful filter flag, documented here under "Stateful filters", and the necessary forked fiddle.
Do note that this solution would hurt performance, as the filter is called each digest cycle. The performance degradation could be negligible or not, depending on the specific case.
Let's start with understanding why the original code doesn't work. I've simplified the original question a bit to make it more clear:
angular.module('angularApp').filter('pathToName', function(Service) {
return function(input) {
return Service.getCorresp().then(function(response) {
return response;
});
});
}
Basically, the filter calls an async function that returns the promise, then returns its value. A filter in angular expects you to return a value that can be easily printed, e.g string or number. However, in this case, even though it seems like we're returning the response of getCorresp, we are actually returning a new promise - The return value of any then() or catch() function is a promise.
Angular is trying to convert a promise object to a string via casting, getting nothing sensible in return and displays an empty string.
So what we need to do is, return a temporary string value and change it asynchroniously, like so:
JSFiddle
HTML:
<div ng-app="app" ng-controller="TestCtrl">
<div>{{'WelcomeTo' | translate}}</div>
<div>{{'GoodBye' | translate}}</div>
</div>
Javascript:
app.filter("translate", function($timeout, translationService) {
var isWaiting = false;
var translations = null;
function myFilter(input) {
var translationValue = "Loading...";
if(translations)
{
translationValue = translations[input];
} else {
if(isWaiting === false) {
isWaiting = true;
translationService.getTranslation(input).then(function(translationData) {
console.log("GetTranslation done");
translations = translationData;
isWaiting = false;
});
}
}
return translationValue;
};
return myFilter;
});
Everytime Angular tries to execute the filter, it would check if the translations were fetched already and if they weren't, it would return the "Loading..." value. We also use the isWaiting value to prevent calling the service more than once.
The example above works fine for Angular 1.2, however, among the changes in Angular 1.3, there is a performance improvement that changes the behavior of filters. Previously the filter function was called every digest cycle. Since 1.3, however, it only calls the filter if the value was changed, in our last sample, it would never call the filter again - 'WelcomeTo' would never change.
Luckily the fix is very simple, you'd just need to add to the filter the following:
JSFiddle
myFilter.$stateful = true;
Finally, while dealing with this issue, I had another problem - I needed to use a filter to get async values that could change - Specifically, I needed to fetch translations for a single language, but once the user changed the language, I needed to fetch a new language set. Doing that, proved a bit more tricky, though the concept is the same. This is that code:
JSFiddle
var app = angular.module("app",[]);
debugger;
app.controller("TestCtrl", function($scope, translationService) {
$scope.changeLanguage = function() {
translationService.currentLanguage = "ru";
}
});
app.service("translationService", function($timeout) {
var self = this;
var translations = {"en": {"WelcomeTo": "Welcome!!", "GoodBye": "BYE"},
"ru": {"WelcomeTo": "POZHALUSTA!!", "GoodBye": "DOSVIDANYA"} };
this.currentLanguage = "en";
this.getTranslation = function(placeholder) {
return $timeout(function() {
return translations[self.currentLanguage][placeholder];
}, 2000);
}
})
app.filter("translate", function($timeout, translationService) {
// Sample object: {"en": {"WelcomeTo": {translation: "Welcome!!", processing: false } } }
var translated = {};
var isWaiting = false;
myFilter.$stateful = true;
function myFilter(input) {
if(!translated[translationService.currentLanguage]) {
translated[translationService.currentLanguage] = {}
}
var currentLanguageData = translated[translationService.currentLanguage];
if(!currentLanguageData[input]) {
currentLanguageData[input] = { translation: "", processing: false };
}
var translationData = currentLanguageData[input];
if(!translationData.translation && translationData.processing === false)
{
translationData.processing = true;
translationService.getTranslation(input).then(function(translation) {
console.log("GetTranslation done");
translationData.translation = translation;
translationData.processing = false;
});
}
var translation = translationData.translation;
console.log("Translation for language: '" + translationService.currentLanguage + "'. translation = " + translation);
return translation;
};
return myFilter;
});