The following service are invoked in the controller:
//Controller
$scope.groups = CrudService.getAllGroups();
This service returns all groups from the database. So next, as shown below, the service code:
//Service (CrudService)
function getAllGroups() {
return ResService.group.query(
successResponse,
errorResponse);
}
function successResponse(resp) {
return resp;
}
/*This part doesn't work*/
function errorResponse(error) {
return function (err) {
$scope.msgError = false;
$scope.errStatus = err.status;
$scope.statusText = err.statusText;
$scope.errMsgDetail = err.data.MessageDetail;
};
}
/**************************/
//Nested Service (ResService)
return {
group: $resource(baseUrl + '/api/group/:Id', {
Id: '#Id'
}, {}),
}
How you can see in the service code, the error response won't invoked in the view. I'm using the error messages from the response header respectively the backend. If the request failed then the alert box have to displayed as the following code demonstrates:
<div>
<alert ng-hide="msgError" type="alert alert-danger" close="closeAlert()">
<p><b>{{ statusTitle }}</b>: <i>{{ errStatus }} - {{ statusText }}</i></p>
<p><strong>{{ msgTitle }}</strong>: <i>{{ errMsgDetail }}</i> <i>{{ msgException }}</i></p>
</alert>
</div>
Do anyone has an idea how I can access or correctly define the values in the errorResponse function? Or, is it possible to declare in the service query at all?
When the error function is called it does nothing but return another error function. Another problem is that it doesn't know the variable $scope in its context, so you have to pass it:
$scope.groups = CrudService.getAllGroups($scope);
function getAllGroups($scope) {
return ResService.group.query(
successResponse,
errorResponse($scope));
}
function errorResponse($scope) {
return function(error) {
$scope.msgError = false;
$scope.errStatus = error.status;
$scope.statusText = error.statusText;
$scope.errMsgDetail = error.data.MessageDetail;
}
}
Also there was a typo, you wrote err., but it should be error..
You're passing the object in to a parameter named error, but then you're referencing it as err. It's just a typo.
/*This part doesn't work*/
function errorResponse(error) {
return function (error) {
$scope.msgError = false;
$scope.errStatus = err.status;
$scope.statusText = err.statusText;
$scope.errMsgDetail = err.data.MessageDetail;
};
}
Should be:
/This part doesn't work/
function errorResponse(error) {
return function (error) {
$scope.msgError = false;
$scope.errStatus = error.status;
$scope.statusText = error.statusText;
$scope.errMsgDetail = error.data.MessageDetail;
};
}
Related
I've been trying to code up a search engine using angular js, but I can't copy one array to another. When I initiate the the code (in the service.FoundItems in the q.all then function) new array(foundArray) shows up as an empty array. I searched up how to copy one array to another and tried that method as you can see, but it isn't working. Please help, here is the code, and thank you.
P.S. if you need the html please tell me.
(function () {
'use strict';
angular.module('narrowDownMenuApp', [])
.controller('narrowItDownController', narrowItDownController)
.service('MenuSearchService', MenuSearchService)
.directive('searchResult', searchResultDirective);
function searchResultDirective() {
var ddo = {
templateUrl: 'searchResult.html',
scope: {
items: '<'
},
};
return ddo
}
narrowItDownController.$inject = ['MenuSearchService'];
function narrowItDownController(MenuSearchService) {
var menu = this;
menu.input = "";
menu.displayResult = [];
menu.searchX = function(name) {
menu.displayResult = MenuSearchService.FoundItems(menu.input, name);
console.log(menu.displayResult);
};
}
MenuSearchService.$inject = ['$http', '$q'];
function MenuSearchService($http, $q) {
var service = this;
service.getMatchedMenuItems = function(name, searchTerm) {
var deferred = $q.defer();
var foundItems = [];
var result = $http({
method: "GET",
url: ('https://davids-restaurant.herokuapp.com/menu_items.json'),
params: {
category: name
}
}).then(function (result) {
var items = result.data;
for (var i = 0; i < items.menu_items.length; i++) {
if (searchTerm === ""){
deferred.reject("Please enter search term");
i = items.menu_items.length;
}
else if (items.menu_items[i].name.toLowerCase().indexOf(searchTerm.toLowerCase()) ==! -1){
foundItems.push(items.menu_items[i].name)
deferred.resolve(foundItems);
}else {
console.log("doesn't match search");
}
}
});
return deferred.promise;
};
service.FoundItems = function (searchTerm, name) {
var searchResult = service.getMatchedMenuItems(name, searchTerm);
var foundArray = [];
$q.all([searchResult])
.then(function (foundItems) {
foundArray = foundItems[0].slice(0);
foundArray.reverse();
})
.catch(function (errorResponse) {
foundArray.push(errorResponse);
});
console.log(foundArray);
return foundArray;
};
};
})();
If the goal of the service.FoundItems function is to return a reference to an array that is later populated with results from the server, use angular.copy to copy the new array from the server to the existing array:
service.FoundItems = function (searchTerm, name) {
var foundArray = [];
var searchPromise = service.getMatchedMenuItems(name, searchTerm);
foundArray.$promise = searchPromise
.then(function (foundItems) {
angular.copy(foundItems, foundArray);
foundArray.reverse();
return foundArray;
})
.catch(function (errorResponse) {
return $q.reject(errorResponse);
})
.finally(function() {
console.log(foundArray);
});
return foundArray;
};
I recommend that the promise be attached to the array reference as a property named $promise so that it can be used to chain functions that depend on results from the server.
Frankly I don't recommend designing services that return array references that are later populated with results. If you insist on designing it that way, this is how it is done.
I tried the $promise thing that you recommended. I was wondering how you would get the value from it ie the array.
In the controller, use the .then method of the $promise to see the final value of the array:
narrowItDownController.$inject = ['MenuSearchService'];
function narrowItDownController(MenuSearchService) {
var menu = this;
menu.input = "";
menu.displayResult = [];
menu.searchX = function(name) {
menu.displayResult = MenuSearchService.FoundItems(menu.input, name);
̶c̶o̶n̶s̶o̶l̶e̶.̶l̶o̶g̶(̶m̶e̶n̶u̶.̶d̶i̶s̶p̶l̶a̶y̶R̶e̶s̶u̶l̶t̶)̶;̶
menu.displayResult.$promise
.then(function(foundArray) {
console.log(foundArray);
console.log(menu.displayResult);
}).catch(function(errorResponse) {
console.log("ERROR");
console.log(errorResponse);
});
};
}
To see the final result, the console.log needs to be moved inside the .then block of the promise.
Titus is right. The function always immediately returns the initial value of foundArray which is an empty array. The promise is executed asynchronously so by the time you are trying to change foundArray it is too late. You need to return the promise itself and then using .then() to retrieve the value just like you are currently doing inside the method.
From just quickly looking at your code I think you made have a simple error in there. Are you sure you want
foundArray = foundItems[0].slice(0);
instead of
foundArray = foundItems.slice(0);
Factory that gets the rates using REST
.factory('rateResource', ['$resource', function ($resource) {
return $resource('/rates/:currency/:effectiveDate', {
currency: '#currency',
effectiveDate: '#effectiveDate'
});
}])
Service that calls the factory to get resource and retrieve the rates and also errors if there are any.
.service('RateService', ['rateResource', '$rootScope',
function (rateResource, $rootScope) {
var self = this;
self.rates = [];
self.search = function (baseCurrency, effectiveDate) {
self.rates = [];
var error = null;
self.baseCurrency = baseCurrency;
self.effectiveDate = effectiveDate;
if (baseCurrency) {
rateResource.query({currency: baseCurrency, effectiveDate: effectiveDate})
.$promise.then(function (rates) {
if (rates) {
angular.forEach(rates, function (rate) {
rate.maintTs = $rootScope.formatTimestampToHHMMSS(rate.maintTs);
rate.editable = false;
self.rates.push(rate);
});
self.processing = false;
}
}, function (response) {
self.processing = false;
error = 'Processing failed due to '+response.status;
console.log(error);
})
return {
rates: self.rates,
errors: error
}
}
}
}]);
Controller calls the service for rates.
.controller('RateController', ['RateService',
function (rateService) {
var self = this;
self.baseCurrency = rateService.getBaseCurrency();
self.effectiveDate = rateService.getEffectiveDate();
self.rates = rateService.getRates();
//make the call from the controller
self.search = function () {
var response = rateService.search(self.baseCurrency, self.effectiveDate.yyyyMMdd());
self.rateRecords = response.rates;
self.errors = response.errors;
}
}])
rates are showing up fine in the controller after the promise is fulfilled. However, upon receiving errors, they are not getting transferred from service to controller. ( I changed the REST URL to make service return a 404 response ). What am I doing wrong?
Currently you are returning asynchronous call response from outside of async function, you shouldn't do that technically. Asynchronous code always get there responses inside their promise/callback function.
But in your case it is working because you are returning object with it reference. If you look at below code, return statement has carried self.rates object reference, so while returning even if it is blank, it is going to get updated value once self.rates gets filled up. So then you don't need to worry about the rates updation. But same thing for error would not work because it is of primitive datatype like var error = null, so when you are returning value it would be null(as async response haven't completed).
return {
rates: self.rates, //passed by reference
errors: error //passed by primitive type
}
So for solving this issue you could also make the error to an object type, so that it reference will get passed with reference object, like var errors = [] & when error occur push the error message into that array using .push
But I'd not recommend above way to go for, I'd rather take use of promise pattern and will maintain proper code call stack. Basically for that you need to return promise from search method & then put .then function to wait till resolve that function.
Service
.service('RateService', ['rateResource', '$rootScope',
function(rateResource, $rootScope) {
var self = this;
self.rates = [];
self.search = function(baseCurrency, effectiveDate) {
self.rates = [];
var error = null;
self.baseCurrency = baseCurrency;
self.effectiveDate = effectiveDate;
if (baseCurrency) {
return rateResource.query({
currency: baseCurrency,
effectiveDate: effectiveDate
})
.$promise.then(function(rates) {
if (rates) {
angular.forEach(rates, function(rate) {
rate.maintTs = $rootScope.formatTimestampToHHMMSS(rate.maintTs);
rate.editable = false;
self.rates.push(rate);
});
self.processing = false;
}
//returning object from promise
return {
rates: self.rates,
errors: error
}
}, function(response) {
self.processing = false;
error = 'Processing failed due to ' + response.status;
console.log(error);
//returning object from promise
return {
rates: self.rates,
errors: error
}
})
}
}
}
])
Controller
//make the call from the controller
self.search = function() {
var response = rateService.search(self.baseCurrency, self.effectiveDate.yyyyMMdd()).then(function() {
self.rateRecords = response.rates;
self.errors = response.errors;
});
}
I'm trying to access a return promise data, but it always give me undefined even when i'm accessing to the correct element.
here is why i have to do it that way .
controller.js
// can't access the data "tastes" in success outside from the .success call
// so i return the entire http call and access it later
function loadContacts() {
var contacts;
return Account.getTastes()
.success(function(tastes) {
contacts= tastes[0].tastes;
return contacts.map(function (c, index) {
var colors = ["1abc9c", "16a085", "f1c40f", "f39c12", "2ecc71", "27ae60", "e67e22","d35400","3498db","2980b9","e74c3c","c0392b","9b59b6","8e44ad","34495e","2c3e50"];
var color = colors[Math.floor(Math.random() * colors.length)];
var cParts = c.split(' ');
var contact = {
name: c,
image: "http://dummyimage.com/50x50/"+color+"/f7f7f7.png&text="+c.charAt(0)
};
contact._lowername = contact.name.toLowerCase();
return contact;
});
})
.error(function(error) {
console.log("Error:"+error)
});
}
/**
* Search for contacts.
*/
function querySearch (query) {
var results = query ?
$scope.allContacts.filter(createFilterFor(query)) : [];
return results;
}
/**
* Create filter function for a query string
*/
function createFilterFor(query) {
var lowercaseQuery = angular.lowercase(query);
return function filterFn(contact) {
return (contact._lowername.indexOf(lowercaseQuery) != -1);;
};
}
$scope.allContacts = loadContacts();
console.log($scope.allContacts)
$scope.tags = [$scope.allContacts[0],$scope.allContacts[1]];
// since $scope.allContacts is undefined thus,
//i'm having error for trying to access element[0] of undefined
the console.log($scope.allContacts) result is
but when i try to access it like $scope.allContacts.$$state.value.data i get undefined as return. may i know what is the solution for this ?
in my html
<md-contact-chips required ng-model="tags" md-contacts="querySearch($query)" md-contact-name="name" md-contact-image="image" filter-selected="true" placeholder="Thai, chinese, cheap, cafe and etc">
</md-contact-chips>
UPDATE: 1(SOLVED)
i'm getting this error after i change my code to
$scope.allContacts.then(function(contacts){
$scope.tags = [contacts[0],contacts[1]];
})
Error: [ngRepeat:dupes] Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: $chip in $mdChipsCtrl.items, Duplicate key: undefined:undefined, Duplicate value: undefined
i'm not even using ng-repeat why am i still getting ng-repeat error ?
UPDATE: 2
the chips is now being displayed however i can't search, when i try to search on the md-contact-chips this happened
You should be able to access contacts like so...
$scope.allContacts.then(function(contacts){
console.log(contacts[0]);
console.log(contacts[1]);
})
if you change
Account.getTastes().success
to
Account.getTastes().then
final results
function loadContacts() {
var contacts;
return Account.getTastes()
.then(function(res) {
contacts = res.data[0].tastes;
return contacts.map(function (c, index) {
var colors = ["1abc9c", "16a085", "f1c40f", "f39c12", "2ecc71", "27ae60", "e67e22","d35400","3498db","2980b9","e74c3c","c0392b","9b59b6","8e44ad","34495e","2c3e50"];
var color = colors[Math.floor(Math.random() * colors.length)];
var cParts = c.split(' ');
var contact = {
name: c,
image: "http://dummyimage.com/50x50/"+color+"/f7f7f7.png&text="+c.charAt(0)
};
contact._lowername = contact.name.toLowerCase();
return contact;
});
})
.error(function(error) {
console.log("Error:"+error)
});
}
$scope.allContacts = loadContacts();
console.log($scope.allContacts)
$scope.allContacts.then(function(contacts){
console.log(contacts[0]);
console.log(contacts[1]);
})
The main problem here seems to be that when you try to access the value, the promise is not resolved yet, so I guess it should be enough to wrap
$scope.tags = [$scope.allContacts[0],$scope.allContacts[1]];
into something like
$scope.allContacts.then(function(){
$scope.tags = [$scope.allContacts[0],$scope.allContacts[1]];
}
var app = angular.module('app',['ui.bootstrap']);
app.controller("ListCtrl", function ($scope, $http) {
$scope.submit = function () {
$scope.loading = true;
$scope.error = false;
$http.get('http://www.omdbapi.com/?s=' + $scope.search + '&r=json')
.then(function (res) {
var titles = [];
angular.forEach(res.data.Search, function(item){
$http.get('http://www.omdbapi.com/?t=' + item.Title + '&y=&plot=full&r=json').then(function(res){
if (res.data.Poster === "N/A") {
res.data.Poster = "http://placehold.it/350x450/FF6F59/FFFFFF&text=Image+not+Available!!";
}
titles.push(res.data);
});
});
$scope.movie = titles;
$scope.results = true;
$scope.error = false;
$scope.loading = false;
if (titles.length==0) { // not working
$scope.results = false;
$scope.error = true;
}
})
I have been tried several things like :
Object.getOwnPropertyNames(titles).length === 0)
obj == null
None of them seems to work...
This is happening because of incorrect scope:
var titles = []; is defined inside the .then
and you are checking the length outside of .then
since titles is not available outside .then it would not work. (undefined.length==0)
Solution:
.then(function (res) {
var titles = [];
angular.forEach(res.data.Search, function(item){
$http.get('http://www.omdbapi.com/?t=' + item.Title + '&y=&plot=full&r=json').then(function(res){
if (res.data.Poster === "N/A") {
res.data.Poster = "http://placehold.it/350x450/FF6F59/FFFFFF&text=Image+not+Available!!";
}
titles.push(res.data);
});
$scope.movie = titles;
$scope.results = true;
$scope.error = false;
$scope.loading = false;
if (titles.length==0) { // now this will work
$scope.results = false;
$scope.error = true;
}
});//titles will not be available after this.
$http.get() is async so the statement if (titles.length==0) { gets executed right away.
Have a counter to determine when all the Promises get resolved and then perform the check. Move the if statement inside the callback.
var count = res.data.Search.length;
angular.forEach(res.data.Search, function(item){
$http.get('http://www.o....rest of code').then(function(res) {
// rest of code
titles.push(res.data);
if (!count-- && !titles.length) {
$scope.results = false;
$scope.error = true;
}
}
});
});
In your case, the check
titles.length
will be executed before the
titles.push
because you use an asynchronous request which will return later.
You need to wrap your statements into the answer-block of the request.
Just as an aside, but beneficial for my practice and your future help:
Part of the problem you were having was scope-management (JS-scope, not Angular $scope), part of it was concurrency-management, and part of it appeared to be plain old scope-formatting making it hard to see where all control blocks start and end (that gets miserable when it's not just if/else, but with callbacks/promises, too).
This is a small example of how you might consider tackling these problems, through a quick refactor of your issues:
function pluck (key) {
return function pluckFrom(obj) { return obj[key]; };
}
angular.module("app", ["ui.bootstrap"]);
angular.moule("app").service("omdbService", ["$http", function ($http) {
function getSearch (search) {
var searching = $http.get("http://www.omdbapi.com/?s=" + search + "&r=json")
.then(pluck("data"));
return searching;
}
function getMovie (title) {
var searching = $http.get("http://www.omdbapi.com/?t=" + title + "&y=&plot=full&r=json")
.then(pluck("data"));
return searching;
}
return {
getSearch: getSearch,
getMovie: getMovie,
getPlaceholderPoster: function () { return "http://placehold.it/350x450/FF6F59/FFFFFF&text=Image+not+Available!!"; }
};
}]);
angular.moule("app").controller("ListCtrl", ["$scope", "$q", "omdbService", function ($scope, $q, omdb) {
function loadMovie (movie) {
return omdb.getMovie(movie.Title)["catch"](function () { return undefined; });
}
function movieExists (movie) { return !!movie; }
function updatePoster (movie) {
movie.Poster = movie.Poster || omdb.getPlaceholderPoster();
return movie;
}
function setResults (movies) {
$scope.movie = movies; // $scope.movies, instead?
$scope.results = true;
$scope.error = false;
$scope.loading = false;
}
function handleError () {
$scope.results = false;
$scope.error = true;
}
$scope.submit = function () {
$scope.loading = true;
$scope.error = false;
omdb.getSearch($scope.search)
.then(pluck("Search"))
.then(function (movies) { return $q.all(movies.map(loadMovie)); })
.then(function (movies) { return movies.filter(movieExists).map(updatePoster); })
.then(setResults, handleError);
};
}]);
There are 8000 valid ways of handling this, and everyone will see it a little differently.
This is also not really how I'd tackle it in production, but not too far off...
Moving all endpoint-calls out to a service which is responsible for those means that any controller in your system (with that module as a dependency) can access them.
Doing small things per function and letting Array.prototype methods do the iterating (IE8 can be shimmed if needed) means that each function is super-specific.
By wrapping the controller/service functions in arrays, and naming their dependencies, they're now minification friendly.
The body of submit() is less than 10 lines, and deals with all kinds of crazy async stuff, but I know that I've handled errors like one of the movies returning a 404 (my code should still fire, with the remaining movies, the code of others might not -- most code would either never trigger success or would fail all the way through the program, if the server threw an error for a movie).
Now, I'm not checking that the server is sending the right kind of data for a "movie", but that's different.
Here is my controller:
$scope.mainModel = getReviews({model:mainModelArr[1]});
$scope.compareModel = getReviews({model:compareModelArr[1]});
function getReviews(data) {
$http.post(url, data)
.success(function(res) {
formatReviews(res)
})
.error(function(err) {
console.log("Something went wrong: "+err);
});
}
function formatReviews(data) {
var review = data[0];
review.sumReviews = (review.sumReviews/review.ratingAvg).toFixed(0);
review.sumRecommend = (review.sumRecommend/review.sumReviews*100).toFixed(1);
review.ratingAvg = (review.ratingAvg).toFixed(1);
console.log(review); // logs message fine
return review;
}
These functions work fine, logs review var., but somehow it didn't assign review variable neither to $scope.mainModel nor to $scope.compareModel.
NOTE: I know that it wasn't assigned, because it's never showed up in HTML:
<p>{{mainModel}}</p>
What did I do wrong and how can I fix that?
Ajax requests works Async, angularjs use promises to handle this requests
$scope.mainModel = undefined;
$scope.compareModel = undefined;
getReviews({model:mainModelArr[1]}).success(function(res){
$scope.mainModel = formatReviews(res);
});
getReviews({model:compareModelArr[1]}).success(function(res){
$scope.compareModel = formatReviews(res);
});
If you return post request, you can handle it anywhere, where you call getReviews method
function getReviews(data) {
return $http.post(url, data)
.error(function(err) {
console.log("Something went wrong:", err);
});
}
function formatReviews(data) {
var review = data[0];
review.sumReviews = (review.sumReviews / review.ratingAvg).toFixed(0);
review.sumRecommend = (review.sumRecommend / review.sumReviews * 100).toFixed(1);
review.ratingAvg = (review.ratingAvg).toFixed(1);
console.log(review); // logs message fine
return review;
}
Since asynchronous code won't allow you simply return the value, you should work with promises using their then methods:
getReviews({model: mainModelArr[1]}).then(function(data) {
$scope.mainModel = data;
});
getReviews({model: compareModelArr[1]}).then(function(data) {
$scope.compareModel = data;
});
function getReviews(data) {
return $http.post(url, data)
.success(formatReviews)
.error(function(err) {
console.log("Something went wrong: "+err);
});
}