I'm having issues creating a Youtube Service which does a simple search based on a 'search term'. What I would like to do is allow controllers to call into my youtube service (i.e. searchVideo("dogs")) and get a video-id, as a string, back.
At this time, I'm noticing that the requestVideoId is being returned before the request can finish executing. Being unfamiliar with JS, I would like an example of how I can have my searchVideo() function return the requestVideoId correctly (after the request has finished).
(function () {
'use strict';
var serviceId = 'youtubeService';
angular.module('app').factory(serviceId, ['common', youtubeService]);
function youtubeService(common) {
var service = {
searchVideo: searchVideo
};
return service;
function searchVideo(searchText) {
var requestVideoId = "";
var request = gapi.client.youtube.search.list({
q: searchText,
part: 'snippet'
});
request.execute(function(response) {
var responseList = response.result;
console.log(responseList.items[0].id.videoId);
requestVideoId = responseList.items[0].id.videoId;
});
console.log(requestVideoId);
return requestVideoId;
}
}
})();
I've checked this question... YouTube Asynchronous function ... But I want to keep the searchVideo() function. I could be missing something. Thanks ahead of time.
searchVideo should return a promise that gets resolved in future. Something like:
function searchVideo(searchText) {
var defer=$q.defer();
var requestVideoId = "";
var request = gapi.client.youtube.search.list({
q: searchText,
part: 'snippet'
});
request.execute(function(response) {
var responseList = response.result;
console.log(responseList.items[0].id.videoId);
requestVideoId = responseList.items[0].id.videoId;
defer.resolve(requestVideoId);
});
console.log(requestVideoId);
return defer.promise;
}
Now you can use the promise api, to register a callback when the promise is resolved:
youtubeService.searchVideo('test')
.then(function(requestVideoId) { // can access the id here});
Learn about what promises are and how promises work in Angular.
Related
I´m trying to understand how promises work in Angular 1.0.7, but the syntax and the concept are difficult to me. I created a related previous post Nesting promises with $resources in AngularJS 1.0.7 that is working fine. However, when I try to do the same but replacing the $resource with an $http service, then it´s not working for me as expected.
The code never wait for the promise to come and I don´t get any result.
I attach the code:
// URL has to be parsed to get the destination, language and boatType
var parseURL = function() {
var language = $routeParams.language;
var url = $location.path();
var deferred = $q.defer();
var promise = deferred.promise;
promise.then(function success(result) {
var destination = result;
console.log("destination:" + destination);
searchBoats(destination);
});
parseDestination(url, language).then(deferred.resolve);
};
parseURL();
var parseDestination = function(url, language) {
console.log("parseDestination.begin");
var departure = UrlService.parseUrlDeparture(url);
var deferred = $q.defer(),
promise = deferred.promise;
TranslationService.getTranslatedDeparture(departure, language, API_SERVER_URL, deferred.resolve, deferred.reject);
return promise;
};
// The function in the service
getTranslatedDeparture: function(destination, language, api) {
var defered = $q.defer();
var destinationPromise = defered.promise;
$http.get("http://" + api + "/translatedDepartures?departure=" + destination + ";lang=" + language + ";matchStart=" + true).then(
//var destination = result.data.map(function (source) { return source.element_translation; });
defered.resolve
);
return destinationPromise;
}
You are using promises wrong in just about every way imaginable. Promises are intended to be chained and create new promises using .then(). And doing this will fix your bugs and cut the length of your code in half. As long as you have a promise to start with (which you do because $http.get returns a promise), you don't need, and shouldn't use, $q.defer():
// URL has to be parsed to get the destination, language and boatType
var parseURL = function() {
var language = $routeParams.language;
var url = $location.path();
parseDestination(url, language)
.then(function (result) {
var destination = result;
console.log("destination:", destination);
searchBoats(destination);
});
};
parseURL();
var parseDestination = function(url, language) {
console.log("parseDestination.begin");
var departure = UrlService.parseUrlDeparture(url);
return TranslationService.getTranslatedDeparture(departure, language, API_SERVER_URL);
};
// The function in the service
getTranslatedDeparture: function(destination, language, api) {
var url = "http://" + api + "/translatedDepartures?departure=" + destination + ";lang=" + language + ";matchStart=" + true;
return $http.get(url)
.then(function (result) { return result.data; });
}
I have been searching for an answer to this, and cannot seem to find anything. I have a service, in the first block I am successfully logging a url that I then need to pass into my getData() function. But it comes back undefined, I have tried the method below, and I tried moving the first $http.get into the controller where I am calling it, as well as moving the first $http.get into the getData() function. Am I going about this all wrong?
di.service('testService', function($http) {
$http.get('https://us.api.data/tichondrius?locale=en_US&apikey=xxxxxxxx').
then(function(response) {
var urlToJsonFileUncut = response.data.files[0].url;
console.log(urlToJsonFileUncut);
urlToJsonFile = urlToJsonFileUncut.slice(7);
console.log(urlToJsonFile);
return urlToJsonFile;
});
this.getData = function(urlToJsonFile) {
console.log(urlToJsonFile);
return $http.get('http://localhost:1337/' + urlToJsonFile).
then(function(response) {
console.log(response.data.realms[0].name);
return response.data.realms[0].name;
});
}});
$http is an async request. so you need to chain it inside the first request to ensure the value of first response is available when second request is called.
di.service('testService', function($http) {
var getData = function () {
return $http.get('https://us.api.data/tichondrius?locale=en_US&apikey=xxxxxxxx').
then(function(response) {
var urlToJsonFileUncut = response.data.files[0].url;
console.log(urlToJsonFileUncut);
var urlToJsonFile = urlToJsonFileUncut.slice(7);
console.log(urlToJsonFile);
$http.get('http://localhost:1337/' + urlToJsonFile).
then(function(response) {
console.log(response.data.realms[0].name);
return response.data.realms[0].name;
});
});
}
return { getData: getData; }
});
I would suggest you to use a factory instead of a service
Check out the below code
di.factory('testService', function ($http) {
var variable_name;
var serviceMethodName = function () {
$http.get('https://us.api.data/tichondrius?locale=en_US&apikey=xxxxxxxx').
then(function (response) {
var urlToJsonFileUncut = response.data.files[0].url;
console.log(urlToJsonFileUncut);
urlToJsonFile = urlToJsonFileUncut.slice(7);
console.log(urlToJsonFile);
variable_name = urlToJsonFile; //added
});
}
//modified parameter in below method
var getData = function (variable_name) {
var urlToJsonFile = variable_name; //added
console.log(urlToJsonFile);
return $http.get('http://localhost:1337/' + urlToJsonFile).
then(function (response) {
console.log(response.data.realms[0].name);
return response.data.realms[0].name;
});
}
//Exposes the two methods and accessbile through out the app unless it is modified
return {
serviceMethodName: serviceMethodName,
getData:getData
}
});
I have an factory that gets data from my backend:
as.factory("abbdata", function GetAbbData($http,$rootScope,$routeParams,$q) { //$q = promise
var deffered = $q.defer();
var data = [];
var abbdata = {};
abbdata.async = function () {
$http.get($rootScope.appUrl + '/nao/summary/' + $routeParams['id']).success(function(d) {
data = d.abbData;
deffered.resolve();
});
return deffered.promise;
};
abbdata.data = function() {
return data;
};
return abbdata;
});
A call my factory like this in my controller:
abbdata.async().then(function() {
$scope.abbData = abbdata.data(); //Contains data
});
When I do a console.log($scope.abbData) outside my service call, just underneath, the result Is undifined. Why? Should not the $scope.abbData contain the data from my service after I call it?
EDIT:
You need to pass the data that should be returned into the resolve function like this:
deffered.resolve(data);
EDIT:
To get the data in the controller do this:
abbdata.async().then(function(data) {
$scope.abbData = data; //Contains data
});
Why don't you simply return that value from the async call in the first place?
You can chain promises so by attaching a success handler in your factory and returning a value from that you can simplify your code to:
as.factory("abbdata", function GetAbbData($http,$rootScope,$routeParams) {
return {
async: function () {
return $http.get($rootScope.appUrl + '/nao/summary/' + $routeParams['id']).success(function(d) {
return d.data.abbData;
});
}
}
});
And then use it like
abbdata.async().then(function(data) {
$scope.abbData = data; //Contains data
});
if you console.log($scope.abbData) outside the service call it should show undefined, since the call is asynchronous.
abbdata.async().then(function() {
$scope.abbData = abbdata.data(); //Contains data
});
console.log($scope.abbData) // this should show undefined
The console.log($scope.abbData) just after setting the abbData should show the data
abbdata.async().then(function() {
$scope.abbData = abbdata.data(); //Contains data
console.log($scope.abbData) // this should show the data
});
EDIT
you can use abbData from your service call like for example
angular.module('myApp', []).controller('HomeCtrl', function($scope, abbdata){
var updateUI;
$scope.abbData = [];
abbdata.async().then(function() {
$scope.abbData = abbdata.data(); //Contains data
updateUI();
});
updateUI = function(){
//do something with $scope.abbData
}
});
EDIT 2
On response to your query, I would do something like,
angular.module('myApp', [])
.controller('JobsCtrl', function($scope, $jobService) {
$scope.jobs = [];
$jobService.all().then(function(jobs) {
$scope.jobs = jobs;
});
})
.service('$jobService', function ($q, $http) {
return {
all: function () {
var deferred = $q.defer();
$http({
url: 'http://url',
method: "GET"
}).success(function (data) {
deferred.resolve(data);
}).error(function () {
deferred.reject("connection issue");
});
return deferred.promise;
}
}
});
associated view
<body ng-app = "myApp">
<div ng-controller = "JobsCtrl">
<div ng-repeat="job in jobs track by job.id">
<a href="#/tab/jobs/{{job.id}}" class="item item-icon-right">
<h2>{{job.job_name}}</h2>
<p>DUE DATE: {{job.job_due_date}}</p>
</a>
</div>
<div>
</body>
Here the service an all function which returns a promise, i.e. it will notify when data is fetched.
in the controller the service is called and as soon the service call is resolved the $scope.jobs is assigned by the resolved data.
the $scope.jobs is used in the angular view. as soon as the jobs data are resolved, i.e. $scope.jobs is assigned, the view is updated.
hope this helps
I had a quick look, I have 2 ideas:
First theory: your service is returning undefined.
Second theory: you need to run $scope.$apply();
See this fiddler: https://jsfiddle.net/Lgfxtfm2/1/
'use strict';
var GetAbbData = function($q) {
//$q = promise
var deffered = $q.defer();
var data = [];
var abbdata = {};
abbdata.async = function () {
setTimeout(function() {
//1: set dummy data
//data = [200, 201];
//2: do nothing
//
//3: set data as undefined
//data = undefined;
deffered.resolve();
}, 100);
return deffered.promise;
};
abbdata.data = function() {
return data;
};
return abbdata;
};
var abbdata = GetAbbData(Q)
abbdata.async().then(function() {
console.log(abbdata.data()); //Contains data
});
I have stripped away a lot of dependencies and replaced $q with Q just for my own ease.
In the above example, I first attempted to run the code with dummy data, the console output the expected data, then I tried to not assign the data, and I get an empty array. This is why I assume that if you are seeing 'undefined' you must be explicitly setting the value to 'undefined'.
That aside, I also noticed that you were testing the result by reading directly from $scope. I know that when not inside the angular scope, doing operations on the $scope object does not necessarily happen in a timely manner, and typing $scope.$apply() usually fixes this. Usually, when using $http, angular keeps you in the appropriate scope, but you are creating your own promise using $q so this could be another potential issue.
Finally, the other two answers have pointed out that you are not using promises in the standard way. Although your code works fine, it is not normal to set your data directly onto your service and retrieve it from there. You can keep your service stateless by simply resolving your promise with the data that you want to process in the then method as shown by the answers by Anzeo and Markus.
I hope I was able to find the solution, good luck.
Dipun
as.factory("abbdata", function GetAbbData($http,$rootScope,$routeParams,$q) { //$q = promise
var deffered = $q.defer();
var data = [];
var abbdata = {};
abbdata.async = function () {
$http.get($rootScope.appUrl + '/nao/summary/' + $routeParams['id']).success(function(d) {
data = d.abbData;
deffered.resolve(data);
});
return deffered.promise;
};
abbdata.data = function() {
return data;
};
return abbdata;
});
I have the following situation: When my app first runs, in the .run function, I issue an http request to get a list of "monitors". Once received, it populates a monitors array in a service. This service is used by many controllers to share data. This service is called ZMDataModel. ZMDataModel offers a function called isMonitorsLoaded(). When this returns 1, I know the monitors array is populated (and that the http call is complete)
Now, I have a page called Monitors, the controller for which is zmApp.MonitorCtrl, shown below. What I need to do, in this MonitorCtrl is to basically, right at the start, do an equivalent of:
while (ZMData.isMonitorsLoaded()!=1);
Now I obviously can't do that because it locks my browser up, and the browser never gets a chance to set isMonitorLoaded to 1 in the first place, so it becomes an endless loop.
I understand I need to put in a timeout somehow, but can't quite follow what I need to do in the controller. My controller code is below:
angular.module('zmApp.controllers').controller('zmApp.MonitorCtrl', function($scope, $http, ZMHttpFactory, ZMDataModel) {
$scope.monitors = [];
console.log("***Waiting for Monitors to load before I proceed");
// I can't do a tight loop waiting for ZMDataModel.isMonitorsLoaded
// so some timeout?
$scope.monitors = ZMDataModel.getMonitors();
console.log("I GOT " + $scope.monitors);
$scope.doRefresh = function() {
console.log("***Pull to Refresh");
$scope.monitors = [];
ZMHttpFactory.getMonitors().then(function(data) {
$scope.monitors = data;
$scope.$broadcast('scroll.refreshComplete');
console.log("I GOT " + $scope.monitors);
});
};
});
You can use $rootScope.$emit('eventName') which works like a broadcast of events for anyone who is subscribe to them whit $rootScope.$on('eventName'):
// In your monitor loaded method:
onload: function(){
//Here you can pass optional information to the listeners
// for Example an array of monitor or an object
$rootScope.$emit('MONITORS_LOADED',{
monitors: getMonitors()
});
}
// In your controller:
angular.module('zmApp.controllers').controller('zmApp.MonitorCtrl', function($rootScope, $scope, $http, ZMHttpFactory, ZMDataModel) {
$scope.monitors = [];
$rootScope.$on('MONITOR_LOADED', function(event, data) {
$scope.monitors = data;
// or
// $scope.monitors = ZMDataModel.getMonitors();
console.log("I GOT " + $scope.monitors);
}
$scope.doRefresh = function() {
//...
});
};
});
Why not using a promise which will resolve when your monitor servers is loaded? You can set up your service as:
angular.module('myApp')
.service ('ZMDataModel', ['$http', function ($http) {
function MyServices () {
var _deferred;
var _isLoading;
var me = this;
this.isLoaded = false;
this.load = function (reload) {
if (!_deferred || (!_isLoading && reload)) {
this.isLoaded = false;
_deferred = $q.defer();
_isLoading = true;
// make your call
$http ({get : 'http://your-site.com'})
.then (
function success (rawData) {
me.isLoaded = true;
// parse your data
me.monitors = rawData;
_deferred.resolve(me);
},
function fail (error) {
_deferred.reject(error);
_deferred = null;
if (onFail) {
me.monitors = [];
}
}
)
.finally (
function () {
_isLoading = false;
}
);
}
return _deferred.promise;
};
}
return MyServices;
}
]);
Now you can use your service everywhere like this:
angular.module('zmApp.controllers').controller('zmApp.MonitorCtrl', ['$scope', 'ZMDataModel', function($scope, ZMDataModel) {
$scope.monitors = [];
console.log("***Waiting for Monitors to load before I proceed");
// I can't do a tight loop waiting for ZMDataModel.isMonitorsLoaded
// so some timeout?
ZMDataModel.load ().then (
function (response) {
$scope.monitors = ZMDataModel.monitors;
}
);
$scope.doRefresh = function() {
console.log("***Pull to Refresh");
$scope.monitors = [];
ZMDataModel.load (true).then (
function (response) {
$scope.monitors = ZMDataModel.monitors;
}
);
};
}]);
It doesn't matter if an other controller loads the service first. As long as you use the load function without the 'refresh' flag set to true, the service won't load again
I'm new to AngularJS and am still trying to wrap my head around using services to pull data into my application.
I am looking for a way to cache the result of a $http.get() which will be a JSON array. In this case, it is a static list of events:
[{ id: 1, name: "First Event"}, { id: 2, name: "Second Event"},...]
I have a service that I am trying to use to cache these results:
appServices.service("eventListService", function($http) {
var eventListCache;
this.get = function (ignoreCache) {
if (ignoreCache || !eventListCache) {
eventListCache = $http.get("/events.json", {cache: true});
}
return eventListCache;
}
});
Now from what I can understand I am returning a "promise" from the $http.get function, which in my controller I add in a success callback:
appControllers.controller("EventListCtrl", ["$scope", "eventListService",
function ($scope, eventListService) {
eventListService.get().success(function (data) { $scope.events = data; });
}
]);
This is working fine for me. What I'd like to do is add an event to the eventListService to pull out a specific event object from eventListCache.
appServices.service("eventListService", function($http) {
var eventListCache;
this.get = function (ignoreCache) { ... }
//added
this.getEvent = function (id) {
//TODO: add some sort of call to this.get() in order to make sure the
//eventListCache is there... stumped
}
});
I do not know if this is the best way to approach caching or if this is a stupid thing to do, but I am trying to get a single object from an array that may or may not be cached. OR maybe I'm supposed to call the original event and pull the object out of the resulting array in the controller.
You're on the right track. Services in Angularjs are singeltons, so using it to cache your $http request is fine. If you want to expose several functions in your service I would do something like this. I used the $q promise/deferred service implementation in Angularjs to handle the asynchronus http request.
appServices.service("eventListService", function($http, $q) {
var eventListCache;
var get = function (callback) {
$http({method: "GET", url: "/events.json"}).
success(function(data, status) {
eventListCache = data;
return callback(eventListCache);
}).
}
}
return {
getEventList : function(callback) {
if(eventListCache.length > 0) {
return callback(eventListCache);
} else {
var deferred = $q.defer();
get(function(data) {
deferred.resolve(data);
}
deferred.promise.then(function(res) {
return callback(res);
});
}
},
getSpecificEvent: function(id, callback) {
// Same as in getEventList(), but with a filter or sorting of the array
// ...
// return callback(....);
}
}
});
Now, in your controller, all you have to do is this;
appControllers.controller("EventListCtrl", ["$scope", "eventListService",
function ($scope, eventListService) {
// First time your controller runs, it will send http-request, second time it
// will use the cached variable
eventListService.getEventList(function(eventlist) {
$scope.myEventList = eventlist;
});
eventListService.getSpecificEvent($scope.someEventID, function(event) {
// This one is cached, and fetched from local variable in service
$scope.mySpecificEvent = event;
});
}
]);
You are on the right track. Here's a little help:
appServices.service("eventListService", function($http, $q) {
var eventListCache = [];
function getList(forceReload) {
var defObj = $q.defer(), listHolder;
if (eventListCache.length || forceReload) {
listHolder= $http.get("/events.json", {cache: true});
listHolder.then(function(data){
eventListCache = data;
defObj.resolve(eventListCache);
});
} else {
defObj.resolve(eventListCache);
}
return defObj.promise;
}
function getDetails(eventId){
var defObj = $q.defer();
if(eventId === undefined){
throw new Error('Event Id is Required.');
}
if(eventListCache.length === 0){
defObj.reject('No Events Loaded.');
} else {
defObj.resolve(eventListCache[eventId]);
}
return defObj.promise;
}
return {
eventList:getList,
eventDetails:getDetails
};
});
Then, in your controller, you handle it like this:
appControllers.controller("EventListCtrl", ["$scope", "eventListService",
function ($scope, eventListService) {
var eventList = eventListService.getList();
eventList.then(function(data){
$scope.events = data;
});
$scope.getEventsList = function(reloadList){
eventList = eventListService.getList(reloadList);
eventList.then(function(data){
$scope.events = data;
});
};
$scope.getEventDetails = function(eventID){
var detailsPromise = eventListService.getDetails(eventID);
detailsPromise.then(function(data){
$scope.eventDetails = data;
}, function(reason){
window.alert(reason);
});
}
}
]);
This way, your events are loaded when the controller first loads, and then you have the option to request a new list by simply passing in a boolean. Getting event details is also handled by an internal promise to give you some error handling without throwing a disruptive error.