I have a problem when calling a service created using .factory in my controller.
The code looks like the following.
Factory (app.js):
.factory('Database',function($http){
return {
getDatabase: function(){
var database = {};
$http.get('http://localhost:3001/lookup').
success(function(data){
database.companyInfo = data.info.companyInfo;
});
}).
error(function(data){
console.log('Error' + data);
});
return database;
}
};
})
Controller:
angular.module('webClientApp')
.controller('MainCtrl', function (Database,Features,$scope,$http) {
$scope.databaseString = [];
$scope.quarters = ['Q1','Q2','Q3','Q4'];
$scope.years = ['2004','2005','2006','2007','2008','2009','2010',
'2011','2012','2013','2014'];
$scope.features = Features.getFeatures();
$scope.database = Database.getDatabase();
console.log($scope.database);
Now when I inspect the element in Firebug I get the console.log($scope.database) printed out before the GET statement result. $scope.database is shown as an Object {} with all the proprieties in place.
However if I try to use console.log($scope.database.companyInfo) I get an undefined as result, while instead I should get that data.info.companyInfo' that I passed from theDatabase` service (in this case an array).
What is the problem here? Can someone help me?
(If you need clarifications please let me know..)
The $http.get() call is asynchronous and makes use of promise objects. So, based on the code you provided it seems most likely that you are outputting the $scope.database before the success method is run in the service.
I build all my service methods to pass in a success or failure function. This would be the service:
.factory('Database',function($http){
return {
getDatabase: function(onSuccuess,onFailure){
var database = {};
$http.get('http://localhost:3001/lookup').
success(onSuccess).
error(onFailure);
}
};
})
This would be the controller code:
angular.module('webClientApp')
.controller('MainCtrl', function (Database,Features,$scope,$http) {
$scope.databaseString = [];
$scope.quarters = ['Q1','Q2','Q3','Q4'];
$scope.years = ['2004','2005','2006','2007','2008','2009','2010',
'2011','2012','2013','2014'];
$scope.features = Features.getFeatures();
Database.getDatabase(successFunction,failureFunction);
successFunction = function(data){
$scope.database = data.info.companyInfo;
console.log($scope.database);
});
failureFunction = function(data){
console.log('Error' + data);
}
Change your code in the following way:
.factory('Database',function($http){
return {
getDatabase: function(){
return $http.get('http://localhost:3001/lookup');
}
};
})
Then get the response in controller(Promise chain)
Database.getDatabase()
.then(function(data){
//assign value
})
.catch(function(){
})
Related
I am trying to pass a JSON string value that is stored in one controller to another. I am using a custom service to pass the data, but it doesn't seem to be passing the value.
The First controller where the JSON string value is stored:
filmApp.controller('SearchController',['$scope', '$http','$log','sharedService',function($scope,$http,$log,sharedService){
$scope.results = {
values: []
};
var vm = this;
vm.querySearch = querySearch;
vm.searchTextChange = searchTextChange;
vm.selectedItemChange = selectedItemChange;
function querySearch(query) {
return $http.get('https://api.themoviedb.org/3/search/movie?include_adult=false&page=1&primary_release_year=2017', {
params: {
'query': query,
'api_key': apiKey
}
}).then(function(response) {
var data = response.data.results.filter(function(obj) {
return obj.original_title.toLowerCase().indexOf(query) != -1;
})
return data;
for (var i = 0; i < data.results.length; i++) {
$scope.results.values.push({title: data.results[i].original_title});
// $log.info($scope.results.values);
}
return $scope.results.values;
})
};
function searchTextChange(text) {
// $log.info('Search Text changed to ' + text);
}
function selectedItemChange(item) {
$scope.value = JSON.stringify(item);
return sharedService.data($scope.value);
}
}]);
The custom Angular service - The value is received here:
filmApp.service('sharedService',function($log){
vm = this;
var value = [];
vm.data = function(value){
$log.info("getValue: " + value); // received value in log
return value;
}
});
The Second controller that wants to receive the JSON value from the First controller:
filmApp.controller('singleFilmController',['$scope', '$http','$log','sharedService',function($scope,$http,$log,sharedService){
var value = sharedService.data(value);
$log.info("Data: " + value);
}]);
The value is received in the service but the second controller can't seem to access it. Not sure why it is happening as I'm returning the value from the data() method from the service. Also, the selectedItemChange method is used by the md-autocomplete directive.
A good approach would be using a Factory/Service. take a look at this: Share data between AngularJS controllers
Technically, you can resolve this by simply changing your service definition to
(function () {
'use strict';
SharedService.$inject = ['$log'];
function SharedService($log) {
var service = this;
var value = [];
service.data = function (value) {
$log.info("getValue: " + value); // received value in log
service.value = value;
return service.value;
};
});
filmApp.service('SharedService', SharedService);
}());
But it is a very poor practice to inject $http directly into your controllers. Instead, you should have a search service that performs the queries and handle the caching of results in that service.
Here is what that would like
(function () {
'use strict';
search.$inject = ['$q', '$http'];
function search($q, $http) {
var cachedSearches = {};
var lastSearch;
return {
getLastSearch: function() {
return lastSearch;
},
get: function (query) {
var normalizedQuery = query && query.toLowerCase();
if (cachedSearches[normalizedQuery]) {
lastSearch = cachedSearches[normalizedQuery];
return $q.when(lastSearch);
}
return $http.get('https://api.themoviedb.org/3/search/movie?' +
'include_adult=false&page=1&primary_release_year=2017', {
params: {
query: query,
api_key: apiKey
}
}).then(function (response) {
var results = response.data.results.filter(function (result) {
return result.original_title.toLowerCase().indexOf(normalizedQuery) !== -1;
}).map(function (result) {
return result.original_title;
});
cachedSearches[normalizedQuery] = results;
lastSearch = results;
return results;
}
});
}
}
filmApp.factory('search', search);
SomeController.$inject = ['$scope', 'search'];
function SomeController($scope, search) {
$scope.results = [];
$scope.selectedItemChange = function (item) {
$scope.value = JSON.stringify(item);
return search.get($scope.value).then(function (results) {
$scope.results = results;
});
}
}
filmApp.controller('SomeController', SomeController);
}());
It is worth noting that a fully fledged solution would likely work a little differently. Namely it would likely incorporate ui-router making use of resolves to load the details based on the selected list item or, it could equally well be a hierarchy of element directives employing databinding to share a common object (nothing wrong with leveraging two-way-binding here).
It is also worth noting that if I were using a transpiler, such as TypeScript or Babel, the example code above would be much more succinct and readable.
I'm pretty new to AngularJS but I'm pretty sure all you are doing is two distinct function calls. The first controller passes in the proper value, the second one then overwrites that with either null or undefined
I usually use events that I manually fire and catch on my controllers. To fire to sibling controllers, use $rootScope.$emit()
In the other controller then, you would catch this event using a $rootscope.$on() call
This article helped me a decent amount when I was working on this in a project.
(function () {
angular.module("app").controller('DashboardController', ['$q', 'dashboardService', function ($scope, $q,dashboardService) {
var DashboardController = this;
dashboardService.loadFromServer(DashboardController );
console.log("DashboardController ", DashboardController);
}])
})();
angular.module("app").service('dashboardService', ['$http', '$q', function ($http, $q) {
return {
loadFromServer: function (controller) {
var getDashboardEntries = $http.get('http://someUrl');
var getEmailData = $http.get('http://someOtherUrl');
var getSidebarData = $http.get('http://yetAnotherUrl');
return $q.all([getDashboardEntries, getSidebarData, getEmailData])
.then(function (results) {
controller.dashboardData = results[0].data;
controller.chartData = results[1].data;
controller.emailData = results[2].data;
});
},
};
}]);
1.The service returns the three bits of data and this is the results when logged using:
console.log("DashboardController ", DashboardController);
When I try to drill down on the data in this manner it logs "undefined"
console.log("DashboardController "DashboardController.dashboardData);
console.log("DashboardController "DashboardController.chartData);
console.log("DashboardController "DashboardController.emailData);
Do you realize that console.log is executed right after invoking loadFromServer before the server has chance to respond and promise resolves? The actual order is:
loadFromServer
console.log
promise success method - where you actually have your data
Change your controller's code to this:
dashboardService.loadFromServer(DashboardController ).then(function() {
console.log("DashboardController ", DashboardController);
});
What would be even better is to construct some object from parts of responses and assign it in the controller itself - not the service. In current implementation if you wanted to have another controller then service would assign response parts to same fields. I'd propose sth like this:
return $q.all([getDashboardEntries, getSidebarData, getEmailData])
.then(function (results) {
var data = {
dashboardData = results[0].data;
chartData = results[1].data;
emailData = results[2].data;
};
return data;
});
and then in controller:
dashboardService.loadFromServer().then(function(data) {
DashboardController.dashboardData = data.dashboardData;
DashboardController.chartData = data.chartData;
DashboardController.emailData = data.emailData;
});
In this solution the controller decides what to do with data, not the other way around.
I created a small example to learn about how to handle exceptions with promises particular in focus of my used MVC structure. This structure uses a Data Access Object (DAO) in addition to the 3 known components Model, View and Controller to be more independent from changes at the backend. Due to the conversion in the DAO you can use different methods to retrieve data from the backend. But I guess that's no new concept to you guys at all.
Well, what I want to know is this: If an exception occurs at the MovieInfoService, the code jumps to the getMovieFailed method and returns $q.reject(e). But what happens next? Does the DAO receive something and how does it gets processed?
I am pretty unfamiliar with the concept of error and exception handling in angular. Therefore I have no clue how to handle such problems with promises. Can you guys help me out and provide me with some useful hints, tips or advice how to handle such problems in order to show these problems on the users view to inform him? Actually even an alert() will be enough - I just need to understand the basic idea/concept of handling exceptions with promises.
Here is some sample code of mine:
app.controller('MainController', ['$scope', 'MovieInfoDAO', function ($scope, MovieInfoDAO)
{
var vm = this;
vm.movie = {};
MovieInfoDAO.getMovie()
.then(function(data){
vm.movie = data;
});
activate();
//////////
function activate() {
return getMovieDetails().then(function(){
console.log('Starting Movie Search');
});
}
function getMovieDetails() {
return MovieInfoDAO.getMovie().then(function(data) {
vm.movie = data;
return vm.movie;
}
}
}]);
app.service("MovieInfoDAO", ['$q', 'MovieInfoService', function($q, MovieInfoService)
{
this.getMovie = function()
{
return MovieInfoService.getMovie().then(function(returnData){
var arrInfoItems = [];
for(var i = 0, length = returnData.length; i < length; i++){
var item = new MovieInfoModel(returnData[i]);
arrInfoItems.push(item);
}
return arrInfoItems;
});
};
}]);
app.service('MovieInfoService', ['$http', '$q', function($http, $q) {
this.getMovie = function()
{
return $http({
method: "GET",
data: { },
url: "http://www.omdbapi.com/?t=Interstellar&y=&plot=short&r=json"
})
.then(getMovieCompleted);
.catch(getMovieFailed);
};
function getMovieCompleted(response) {
return response.data;
}
function getMovieFailed(e) {
var newMessage = 'Failed to retrieve Infos from the Server';
if (e.data $$ e.data.description) {
newMessage = newMessage + '\n' + e.data.description;
}
e.data.description = newMessage;
return $q.reject(e);
}
return this;
}]);
function MovieInfoModel(arrParameter){
Model.call(this);
this.title = arrParameter.Title;
this.actors = arrParameter.Actors;
this.plot = arrParameter.Plot;
this.constructor(arrParameter);
}
HTML
<div>
<h1>{{Main.movie.title}}</h1>
<span>{{Main.movie.plot}}</span>
<br/>
<h3>Actors:</h3>
<span>{{Main.movie.actors}}</span>
</div>
I always create my service for ajax calls which is encapsulating $http service. In such service we can create one error handler for all requests. It enables to create many error types and check http response codes like 404,500. Some short example how it can look like, I added only get method:
var app=angular.module('app', []);
app.service('$myAjax', function($http) {
this.get = function (url,success,error) {
return $http.get(url).then(function(response){
if (response.data.status===1)//status is example success code sended from server in every response
return success(response.data);
else{
//here error handler for all requests
console.log("Something is wrong our data has no success on true.");
//error callback
return error(response.data);
}
},function(response){
//this code will run if response has different code than 200
//here error handler for all requests
console.log("Something is very wrong. We have different then 200 http code");
//error callback
return error(response.data);
});
}
});
//TEST
app.controller("Test",function($myAjax){
$myAjax.get('http://jsfiddle.net/echo/jsonp/',function(response){
//here success
return 1;
},function(response){
//here error
return 0;
}).then(function(data){
console.log("I am next promise chain");
console.log("Data from callbacks:"+data);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="Test">
</div>
</div>
I show error even if http code is 200 when data has no status parameter on 1, this is second and internal error handling proposition. Thanks that parameter We can for example send many status information and create for them callbacks. Status 2 can mean - no rights, status 3 - no data etc..
If your intensions is to return the array arrInfoItems in MovieInfoDAO.getMovie(), instead of:
return arrInfoItems;
use a promise, e.g.:
app.service("MovieInfoDAO", ['$q', 'MovieInfoService', function($q, MovieInfoService)
{
this.getMovie = function()
{
var def = $q.defer();
MovieInfoService.getMovie()
.then(function(returnData){
var arrInfoItems = [];
for(var i = 0, length = returnData.length; i < length; i++){
var item = new MovieInfoModel(returnData[i]);
arrInfoItems.push(item);
}
def.resolve(arrInfoItems);
}, def.reject);
return def.promise;
};
}]);
Usage
app.controller('MainController', ['$scope', 'MovieInfoDAO', function ($scope, MovieInfoDAO)
{
var vm = this;
vm.movie = {};
MovieInfoDAO.getMovie()
.then(function(data){
vm.movie = data;
}, function(error){
// Handle error here, can be placed in a vm.error variable
});
}]);
<script>
var app = angular.module('myApp', ['ngMaterial']);
app.factory('factoryProvider', function ($http, $q) {
var facObj = {};
facObj.getLastWorkplace = $http.get('plugins/wcf-service/ServiceProvider.svc/getLastWorkPlacesJSON')
.then(function (response) {
return response.data;
});
return facObj;
});
app.controller('dashboardController', function ($scope, factoryProvider) {
factoryProvider.getLastWorkplace.then(function (successResponse) {
$scope.wp = successResponse;
console.log('inside');
console.log($scope.wp); // Return an object that I want
});
console.log('outside');
console.log($scope.wp); // $scope.wp is empty
});
The outside console log runs first, inside console log is the second. The problem is that $scope.wp can just get data in getLastWorkplace callback functions and it can not bind data to ng-model(using wp.property). How to solve it?
Thanks for your reading
You are assigning $scope.wp twice and the final assignment is the return value of your getLastWorkplace call (which you aren't returning anything.)
Change it to this...
factoryProvider.getLastWorkplace.then(function (successResponse) {
$scope.wp = successResponse;
});
or...
$scope.wp = factoryProvider.getLastWorkplace;
but not both.
I have a factory to get an array with all my clientes from the database.
Then i need to filter this array by the person id and show only it's data in a single page.
I have a working code already, but it's only inside a controller and I want to use it with a factory and a directive since i'm no longer using ng-controller and this factory already make a call to other pages where I need to show client data.
This is what i tried to do with my factory:
app.js
app.factory('mainFactory', function($http){
var getCliente = function($scope) {
return $http.get("scripts/php/db.php?action=get_cliente")
.success( function(data) {
return data;
})
.error(function(data) {
});
};
var getDetCliente = function($scope,$routeParams) {
getCliente();
var mycli = data;
var myid = $routeParams.id;
for (var d = 0, len = mycli.length; d < len; d += 1) {
if (mycli[d].id === myid) {
return mycli[d];
}
}
return mycli;
};
return {
getCliente: getCliente,
getDetCliente: getDetCliente
}
});
app.directive('detClienteTable', function (mainFactory) {
return {
restrict: "A",
templateUrl: "content/view/det_cliente_table.html",
link: function($scope) {
mainFactory.getDetCliente().then(function(mycli) {
$scope.pagedCliente = mycli;
})
}
}
});
detClient.html
<p>{{pagedCliente.name}}</p>
<p>{{pagedCliente.tel}}</p>
<p>{{pagedCliente.email}}</p>
[...more code...]
The problem is, I'm not able to get any data to show in the page, and also, i have no errors in my console.
What may be wrong?
Keep in mind I'm learning AngularJS.
Basically you need to implement a promise chain as look into your code looks like you are carrying getCliente() promise to getDetCliente method. In that case you need to use .then function instead of using .success & .error which doesn't allow you to continue promise chain. There after from getDetCliente function you again need to use .then function that gets call when getCliente function gets resolved his promise. Your code will reformat it using form it and return mycli result.
Code
var getCliente = function() {
return $http.get("scripts/php/db.php?action=get_cliente")
.then( function(res) { //success callback
return res.data;
},function(err){ //error callback
console.log(err)
})
};
var getDetCliente = function(id) {
return getCliente().then(function(data){
var mycli = data;
var myid = id;
for (var d = 0, len = mycli.length; d < len; d += 1) {
if (mycli[d].id === myid) {
return mycli[d];
}
}
return mycli;
})
};
Edit
You shouldn't pass controller $scope to the service that will make tight coupling with you directive and controller, Also you want to pass id parameter of your route then you need to pass it from directive service call
link: function($scope) {
mainFactory.getDetCliente($routeParams.id).then(function(mycli) {
$scope.pagedCliente = mycli;
})
}
You are treating getCliente as a synchronous call in getDetCliente. Interestingly in your directive you understand that the getDetCliente is asynchronous. Change getCliente to this and treat it as an asynchronous call when you call it in getDetCliente:
var getCliente = function($scope) {
return $http.get("scripts/php/db.php?action=get_cliente");
};