$q.reject and $state.resolve not working as expected - angularjs

I have this function:
// Gets our sport
get: function (slug, kit) {
// If we have no slug, exit the function
if (!slug)
return $q.reject('No slug supplied.');
// Try to get our sport
return moltin.categories.get(slug).then(function (response) {
// If we have a kit
if (kit) {
// Assign our sport to our kit
kit.team.sport = response.slug;
}
// Return our response
return response;
});
}
As you can see I am using $q.reject() if my required parameter is not set.
The problem is, in a $state resolve method, if I invoke the function like this:
resolve: {
sport: ['$stateParams', 'kit', 'SimpleDesignerSharedSportService', function ($stateParmas, kit, sharedSport) {
// Get our slug
var slug = $stateParmas.sport || kit.team.sport;
// Get our sport
return sharedSport.get(slug, kit);
}]
}
my view will not be shown (it is just blank). But if I change my resolve to this:
resolve: {
sport: ['$stateParams', 'kit', 'SimpleDesignerSharedSportService', function ($stateParmas, kit, sharedSport) {
// Get our slug
var slug = $stateParmas.sport || kit.team.sport;
// Get our sport
return sharedSport.get(slug, kit).then(function (response) {
return response;
}, function (error) {
return null;
});
}],
pageTitle: ['PageHead', function (service) {
service.setTitle('Kudos Sports - Create your kit');
}]
}
it works. I don't want to have to specify a success and fail method. Is there anyway I can get around it?

Resolves expect a promise so that it can resolve it before loading the page. There are a few issues in your code that I will help you identify and solve, however I strongly suggest you take this short course on Udacity and brush up on your understanding of promises.
Firstly, we need to refactor your get method to return a promise once your data is resolved.
// Gets our sport
get: function (slug, kit) {
var deferred = $q.defer();
// If we have no slug, exit the function
if (!slug)
deferred.reject('No slug supplied.');
// Try to get our sport
moltin.categories.get(slug).then(function (response) {
// If we have a kit
if (kit) {
// Assign our sport to our kit
kit.team.sport = response.slug;
}
// Return our response
deferred.resolve(response);
});
return deferred.promise;
}
As you can see, you can't just return the data in the promise. You have to create another promise and return that once there is data.
The next step is to consume this function within a resolve. You seems to have used $stateParmas instead of $stateParams so we correct that as well. Also, it is bad practice to name your services differently in different places. Keeping consistent with naming conventions makes debugging much easier.
resolve: {
sport: ['$stateParams', 'kit', 'SimpleDesignerSharedSportService', function ($stateParams, kit, SimpleDesignerSharedSportService) {
// Get our slug
var slug = $stateParams.sport || kit.team.sport;
// Get our sport
return SimpleDesignerSharedSportService.get(slug, kit);
}]
}
Now the get method returns a promise that only resolves once there is a response from fetching the categories and you once again have control of the data flow.

Related

Mongoose and Angular Populate Issue

I am new to mongoose and Angular and I am having an issue with mongoose's populate method. I have the following two mongoose schemas
var JobSchema = new mongoose.Schema({
jobName: String,
jobType: String,
status: String,
examples: [{type: mongoose.Schema.Types.ObjectId, ref: 'Example'}]
});
mongoose.model('Job', JobSchema);
and
var ExampleSchema = new mongoose.Schema({
content: String,
job: {type: mongoose.Schema.Types.ObjectId, ref: 'Job'}
});
mongoose.model('Example', ExampleSchema);
So basically the Job schema contains Example's. I also have the following Express route for getting the examples from a particular Job. I used this tutorial to figure out how to do this.
var Job = mongoose.model('Job');
var Example = mongoose.model('Example');
router.get('/jobs/:job', function (req, res) {
req.job.populate('examples', function (err, job) {
if (err) {return next(err);}
res.json(job);
});
});
Also, I am using the following to automatically retrieve the job from mongo and attach it to req.
router.param('job', function (req, res, next, id) {
var query = Job.findById(id);
query.exec(function (err, job) {
if (err) {
return next(err);
}
if (!job) {
return next(new Error('can\'t find job'));
}
req.job = job;
return next();
});
});
I also have the following Angular factory that uses this route
app.factory('jobs', ['$http', function ($http) {
var o = {
jobs: []
};
o.get = function (id) {
return $http.get('/jobs/' + id).then(function (res) {
return res.data;
});
};
return o;
}]);
I also created the following state which is supposed to immediately populate the examples for a given Job id using the above factory.
.state('jobs', {
url: '/jobs/{id}',
templateUrl: '/jobs.html',
controller: 'NerCtrl',
resolve: {
post: ['$stateParams', 'jobs', function ($stateParams, jobs) {
return jobs.get($stateParams.id);
}]
}
});
The problem comes when I actually try to show the examples using a controller.
app.controller('NerCtrl', [
'$scope',
'job',
function ($scope, job) {
$scope.examples = job.examples;
}]);
The view that tries to use $scope.examples just displays {{examples}} rather than the actual content of the scope variable. In fact, nothing in the controller seems to work with the `job` injection (not even simple 'alerts').
It looks the problem comes from the `job` injection in the controller. This is supposed to refer to the job that is retrieved in the resolve given the id but it doesn't look like this is working.
In addition, I have curled an example record's url (eg. curl http://localhost:3000/jobs/56920a1329cda48f16fc0815) and it does return the desired Job record, so it does look like the route part is working correctly. I suspect the problem is somewhere in the 'resolve' or the way in which I am injecting the result of the resolve into the controller.
Ok this was a silly mistake. The post inside the Job state should have been job. i.e.
.state('jobs', {
url: '/jobs/{id}',
templateUrl: '/jobs.html',
controller: 'NerCtrl',
resolve: {
job: ['$stateParams', 'jobs', function ($stateParams, jobs) {
return jobs.get($stateParams.id);
}]
}
});
In my inexperience, I did not know what post was referring to, but I suppose it refers to the job that is returned from jobs.get($stateParams.id) which is then the name that gets injected in the controller. So obviously the name in resolve must be consistent with what is injected in the controller.

How to return widgetDefinitions with data service in malhar-angular-dashboard?

I have installed the malhar-angular-dashboard module. I do not want to hard code my widgetDefinitions, so I created a service witch will return the array with the widget objects. I am using a rest service to return data such as the widget names, titles, etc. The problem is I get this error and do not know how to resolve :
TypeError: widgetDefs.map is not a function
SERVICE DATA
.factory('widgetRestService',['$http','UrlService','$log','$q',
function($http,UrlService,$log,$q){
var serviceInstance = {};
serviceInstance.getInfo = function(){
var request = $http({method: 'GET', url: '/rest/widgets/getListInfoDashboards'})
.then(function(success){
serviceInstance.widgets = success.data;
$log.debug('serviceInstance.widgets SUCCESS',serviceInstance.widgets);
},function(error){
$log.debug('Error ', error);
$log.debug('serviceInstance.widgets ERROR',serviceInstance.widgets);
});
return request;
};
serviceInstance.getAllWidgets = function () {
if (serviceInstance.widgets) {
return serviceInstance.widgets;
} else {
return [];
}
};
return serviceInstance;
}]);
widgetDefinitions service
.factory('widgetDefinitions',['widgetRestService','$log','$q','$http',function(widgetRestService,$log,$q,$http) {
var widgetDefinitions = [];
return widgetRestService.getInfo().then(function (data) {
var widgets = widgetRestService.getAllWidgets();
$log.debug('widgetsDefs ', widgets);
for (var i = 0; i < widgets.length; i++) {
widgetDefinitions.push(widgets[i]);
}
$log.debug('widgetDefinitions ', widgetDefinitions);
return widgetDefinitions;
});
});
Console
TypeError: widgetDefs.map is not a function
widgetDefs: [Object,Object,Object]
widgetDefinitions: [Object,Object,Object]
Note
If I hard-code my widgetDefinitions-service returned array like this it works, if I returned with my rest service doesn`t work(widgetDefs.map is not a function):
[
{
name:'widgetList',
title:'title1'
},
{
name:'widgetPie',
title:'title2'
},
{
name:'widgetTable',
title:'title3'
}
]
Original issue in github has been commented on and closed.
Here's the comment from github:
#pitong Make sure the dashboard options contains a property named widgetDefinitions and it's an array, even if it's empty. Also make sure the the browser version you're using supports the map function for arrays. This map function for array became the ECMA-262 standard in the 5th edition so your browser version might not have it supported.

Create an angular filter that returns remote data?

I have a snippet of code that I use very often, that calls an API in order to return a user name based on an ID sent over to it.
GetUserNames.get({users: user}, function (data) {
$scope.userName = data[0];
})
What I would like to do is turn this into a filter where I can do something like
{{userID | returnUserName}}
so that I can reuse and keep my controllers and my views clean. I wrote something like this:
angular.module('app.filters').filter('returnUserName', function(GetUserNames) {
return function(input) {
GetUserNames.get({ users: input }, function (data) {
console.log(data[0])
return data[0]
});
}
});
and while console.log is returning the correct value, I dont see it on the page. I'm guessing this is a timing issue. Is there anything I can do to make this work, so that the value is then populated on the page?
I have a snippet of code that I use very often
Whenever you hear yourself saying that think services not filters. While you could in theory have your filter make remote calls, it is a gross misuse of filters and will probably be very inefficient (likely causing your page to be slow and possibly even DDoSing your server).
Your patterns should look more like
{{ returnUserName(userId) }}
Or perhaps even pre-computer the username somewhere and then just do
{{ userName }}
Just to answer your question , here's a snippet of code that should answer your question.
If you use Angular 1.3.X, you can use $stateful like :
angular.module('app.filters').filter('returnUserName', function($http) {
var data = null, serviceInvoked = false;
function realFilter(value) {
return data;
}
filterStub.$stateful = true;
function filterStub(value) {
if( data === null ) {
if( !serviceInvoked ) {
serviceInvoked = true;
$http.get('/url/test/').then(function (res) {
data = res;
});
}
return "";
}
else return realFilter(value);
}
return filterStub;
});
For more information : here
I hope it'll help you.
As #Florian Orpeliere mentioned, you could use a stateful filter - and additionally use some cache variable:
angular.module("app").filter('svcData', function($http) {
var cached = {};
var apiUrl = 'http://my.service.com';
function svcDataFilter(data_id, data_prop) {
if (data_id) {
if (data_id in cached) {
// avoid returning a promise!
return typeof cached[data_id].then !== 'function' ?
cached[data_id][data_prop] : undefined;
} else {
cached[data_id] = $http({
method: 'GET',
url: apiUrl + data_id
}).success(function (data) {
cached[data_id] = data;
});
}
}
}
svcDataFilterFilter.$stateful = true;
return svcDataFilterFilter;
})
and use it like so: {{data_id |svcData:'property'}}
Beware: The function svcDataFilter will be called on each digest cycle.
Also, you would need to figure out a way to reset the cache if it grows too big.
See: https://glebbahmutov.com/blog/async-angular-filter/
And the plunker (the link above won't display it, so here's a direct link): http://plnkr.co/edit/EK2TYI1NZevojOFDpaOG?p=preview

How to scope methods in AnjularJS

I have this controller that has several functions that call each other. On success, I want to return something to be displayed (located in the last function). For some reason, without errors, the return is not working but the console.log is. Can someone please tell me why the return does not work and give me a solution please. Thanks so much!
.controller("dayController", function(){
.controller("weatherController", function(){
this.currentWeatherToDisplay = function(){
if(navigator.geolocation){
navigator.geolocation.getCurrentPosition(gotLocation,initialize);
}
else{
alert("Device does not support geolocation");
initialize();
}
};
var currentLocation;
//get the location coords
function gotLocation(pos){
var crd = pos.coords;
currentLocation = loadWeather(crd.latitude+','+crd.longitude);
initialize();
}
function initialize(){
if(!currentLocation){
loadWeather("Washington, DC");
}
else{
loadWeather(currentLocation);
}
}
function loadWeather(location){
$.simpleWeather({
location: location,
woeid: '',
unit: 'f',
success: function(weather) {
var html = weather.temp+'°'+weather.units.temp;
console.log(html);
return html;
},
error: function(error) {
console.log(error);
return error;
}
});
}
});
Well, mmmm you are use some jQuery plugin to get the weather given a current location, and like almost every jQuery plugins this use callbacks call to works (success, and error) first one i recommend you to rewrite this method to something like this:
function loadWeather(location){
var defered = $q.defer();
$.simpleWeather({
location: location,
woeid: '',
unit: 'f',
success: function(weather) {
var html = weather.temp+'°'+weather.units.temp;
console.log(html);
defered.resolve(html);
},
error: function(error) {
console.log(error);
defered.reject(error);
}
});
return defered.promise;
}
Also you must inject the $q dependency to the controller, like this:
module.controller("weatherController", function($q){...}
or this
module.controller("weatherController", ['$q',function($q){...}
I recommend the last by minyfication improvement in angular, when you return a promise like the function loadWeather, you must understand some basic principles about the the $q (based kriskoval Q library), a promise is a expected value in the future, an have a method then to work with that data (its a very short concept), that means:
function gotLocation(pos){
var crd = pos.coords;
loadWeather(crd.latitude+','+crd.longitude)
.then(function(html){
//html contain the expected html from loadWeather defered.resolve(html)
currentLocation = html;
})
.catch(function(error){
//error contain the expected error by execute defered.reject(error)
// maybe gonna try to initialize here
initialize();
})
}
This must work, remember to change the initialize function to some like this:
function initialize(){
var promise;
if(!currentLocation){
promise = loadWeather("Washington, DC");
}
else{
promise = loadWeather(currentLocation);
}
promise.then(function(html){
// some logic with succesful call
}, function(error) {
// some logic with error call
})
}

Recommended way of getting data from the server

What is the recommended way to connect to server data sources in AngularJS without using $resource.
The $resource has many limitations such as:
Not using proper futures
Not being flexible enough
There are cases when $resource may not be appropriate when talking to backend. This shows how to set up $resource like behavior without using resource.
angular.module('myApp').factory('Book', function($http) {
// Book is a class which we can use for retrieving and
// updating data on the server
var Book = function(data) {
angular.extend(this, data);
}
// a static method to retrieve Book by ID
Book.get = function(id) {
return $http.get('/Book/' + id).then(function(response) {
return new Book(response.data);
});
};
// an instance method to create a new Book
Book.prototype.create = function() {
var book = this;
return $http.post('/Book/', book).then(function(response) {
book.id = response.data.id;
return book;
});
}
return Book;
});
Then inside your controller you can:
var AppController = function(Book) {
// to create a Book
var book = new Book();
book.name = 'AngularJS in nutshell';
book.create();
// to retrieve a book
var bookPromise = Book.get(123);
bookPromise.then(function(b) {
book = b;
});
};
I recommend that you use $resource.
It may support (url override) in next version of Angularjs.
Then you will be able to code like this:
// need to register as a serviceName
$resource('/user/:userId', {userId:'#id'}, {
'customActionName': {
url:'/user/someURI'
method:'GET',
params: {
param1: '....',
param2: '....',
}
},
....
});
And return callbacks can be handled in ctrl scope like this.
// ctrl scope
serviceName.customActionName ({
paramName:'param',
...
},
function (resp) {
//handle return callback
},
function (error) {
//handler error callback
});
Probably you can handle code on higher abstraction level.

Resources