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.
Related
the problem is that the data can't be used in the controller because it is always undefined , I think this is because the return is a promise not resoloved before running the controller, please help.. here is my code.
.factory('channelsFactory', ['$resource', 'baseURL', function($resource, baseURL ) {
return $resource(baseURL+"channels/:id",null,{'update':{method:'PUT' }});
}])
this is the controller in my controllers.js
.controller('videoController',[ '$scope','$http', 'baseURL','channelsFactory','channel',function($scope, $http, baseURL,channelsFactory, channel){
$scope.baseURL= baseURL;
$scope.channel= channel;
console.log(channel)// I can see the promised data just fine
$scope.videos = [];
$scope.youtubeParams = {
key: 'AIzaSyAc6Su5lq1-OIXHu3VMfssPM5RzY8F2tPk',
type: 'video',
maxResults: '20',
part: 'id,snippet',
q:'',
order: 'date',
channelId:channel.channelid,//undfined
$http.get('https://www.googleapis.com/youtube/v3/search', {params:$scope.youtubeParams}).success(function(response){
angular.forEach(response.items, function(child){
$scope.videos.push(child);
console.log(child);
});
});
and here is the view in the app.js
.state('app.channelvideos', {
url: '/channelsList/:id',
views: {
'mainContent': {
templateUrl: 'templates/channelvideos.html',
controller: 'videoController',
resolve: {
channel: ['channelsFactory','$stateParams', function(channelsFactory,$stateParams ){
return channelsFactory.get({id:parseInt($stateParams.id,10)})
}]
}
}
}
})
where do you use the $scope.youtubeParams?,if this is used in the trigger event,you can put this code into the function of trigger event.Try to show the $scope.channel in your html like {{channel}} to see if you have get the correct object
thanks good finally I found an article talking about my problem specifically and the solution was to force angular to resolve the resource in the ui-router , and the solution was just in one word, so this piece of code will be edited as following
return channelsFactory.get({id:parseInt($stateParams.id,10)}).$promise;
I'm using $resource for my RESTful api's and love the parameterized URL template for example 'api/clients/:clientId'
This works great for CRUD operations. Some of my api's however are just reports or read-only end points without the need for the full RESTful treatment. I felt it was overkill to use $resource for those and instead used a custom data service with $http.
The only drawback is I lose the parameterized URL templates. I would love to define a url like'api/clients/:clientId/orders/:orderId' and just pass { clientId: 1, orderId: 1 }. I realize I can build the url dynamically but was hoping $http supported the parameterized template and I just haven't found it yet.
All the best
UPDATE 7/5
The word I was missing in my searches is 'Interpolate'. More information comes up when I search for 'url interpolation in angular $http'. The short answer looks to be 'No' $http doesn't support url interpolation. There are a few fairly easy ways to accomplish this however.
1. Use $interpolate:
Documentation for $interpolate here
var exp = $interpolate('/api/clients/{{clientId}}/jobs/{{jobId}}', false, null, true);
var url = exp({ clientId: 1, jobId: 1 });
2. Write your own url interpolation function
Ben Nadel has a great post on this exact topic here.
3. Steal the functionality right out of angular-resource
Check out setUrlParams on Route.prototype in angular-resource.js. It is fairly straightforward.
Sample data service using $interpolate
(function () {
'use strict';
var serviceId = 'dataservice.jobsReports';
angular.module('app').factory(serviceId, ['$http', '$interpolate', function ($http, $interpolate) {
var _urlBase = 'http://localhost:59380/api';
var _endPoints = {
getJobsByClient: {
url: 'Clients/{{clientId}}/Jobs',
useUrlInterpolation: true,
interpolateFunc: null
}
};
// Create the interpolate functions when service is instantiated
angular.forEach(_endPoints, function (value, key) {
if (value.useUrlInterpolation) {
value.interpolateFunc = $interpolate(_urlBase + '/' + value.url, false, null, true);
}
});
return {
getJobsByClient: function (clientId) {
var url = _endPoints.getJobsByClient.interpolateFunc({ clientId: clientId });
return $http.get(url);
}
};
}]);
})();
To prevent this being "unanswered" when it has been answered ...
1. Use $interpolate:
Documentation for $interpolate here
var exp = $interpolate('/api/clients/{{clientId}}/jobs/{{jobId}}', false, null, true);
var url = exp({ clientId: 1, jobId: 1 });
2. Write your own url interpolation function
Ben Nadel has a great post on this exact topic here.
3. Steal the functionality right out of angular-resource
Check out setUrlParams on Route.prototype in angular-resource.js. It is fairly straightforward.
Sample data service using $interpolate
(function () {
'use strict';
var serviceId = 'dataservice.jobsReports';
angular.module('app').factory(serviceId, ['$http', '$interpolate', function ($http, $interpolate) {
var _urlBase = 'http://localhost:59380/api';
var _endPoints = {
getJobsByClient: {
url: 'Clients/{{clientId}}/Jobs',
useUrlInterpolation: true,
interpolateFunc: null
}
};
// Create the interpolate functions when service is instantiated
angular.forEach(_endPoints, function (value, key) {
if (value.useUrlInterpolation) {
value.interpolateFunc = $interpolate(_urlBase + '/' + value.url, false, null, true);
}
});
return {
getJobsByClient: function (clientId) {
var url = _endPoints.getJobsByClient.interpolateFunc({ clientId: clientId });
return $http.get(url);
}
};
}]);
})();
For URL templateing, there is a clearly defined recommandation: RFC 6570
You can find one implementation on Github : bramstein/url-template
It is quite simple. Here is an AngularJS service making use of a library implementing RFC 6570 standard:
var app=angular.module('demo',[]);
app.service('UserStore',function () {
var baseUrl=urltemplate.parse('/rest/v1/users{/_id}');
return {
load:function(id){
return $http.get(baseUrl.expand({_id:id}));
},
save:function (profile) {
return baseUrl.expand(profile);
//return $http.post(baseUrl.expand(profile),profile);
},
list:function (id) {
}
}
});
app.controller('demoCtrl',function(UserStore){
this.postUrlOfNewUser=UserStore.save({name:"jhon"});
this.postUrlOfExistingUser=UserStore.save({_id:42,name:"Arthur",accessory:"towel"});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdn.rawgit.com/bramstein/url-template/master/lib/url-template.js"></script>
<div ng-app="demo">
<div ng-controller="demoCtrl as ctrl">
<div>New user POST URL: {{ctrl.postUrlOfNewUser}}</div>
<div>Existing user POST URL: {{ctrl.postUrlOfExistingUser}}</div>
</div>
</div>
<script>
</script>
As you can see, the standard even handle optional PATH component. It make it a breeze !
And you can also use "." or ";" -- for matrix notation and even expand query strings!
I'm new with MEAN and now I have a problem which I can't solve. On server side for providing a REST API with express I'm using library node-restful. So there I have this schema Sport.js:
var mongoose = require('mongoose');
// create a schema
var SportSchema = new mongoose.Schema({
name: {
type: String,
required: true
}
});
// export the model schema
module.exports = SportSchema;
and controller SportController.js
var restful = require('node-restful');
module.exports = function (app, route) {
// setup the controller for REST
var rest = restful.model(
'sport',
app.models.sport
).methods(['get', 'put', 'post', 'delete']);
// register the endpoint with the application
rest.register(app, route);
// return middleware
return function (req, res, next) {
next();
};
};
On the client side I'm using library restangular as AngularJS service to handle Rest API Restful Resources. Here is controller main.js which use it:
angular.module('clientApp')
.controller('MainCtrl', function ($scope, Sport) {
$scope.sports = Sport.getList().$object;
console.log($scope);
});
In Firebug I see that object $scope so that works fine and also I can use object $scope.sports in my template. But what I want here is using mongoosejs' commands, for example
angular.module('clientApp')
.controller('MainCtrl', function ($scope, Sport) {
// find each sport with a name matching 'Run'
Sport.findOne({ 'name': 'Run' }, function (err, sport) {
if (err) return handleError(err);
console.log(sport.name); // <-- does not work
});
$scope.sports = Sport.getList().$object;
//console.log($scope);
});
Is it possible to do it? I'm really new in MEAN so I apologize if I'm doing something totally wrong.
So you have a functional MongoDB and you now just want to list a sport that contains 'Run'?
Put a ng-model in the html element that is supposed to do something with the output. Say you want to list all the sports containing run in a list:
<any-html ng-model="findonly.name" value="Run">
Or perhaps as a modifiable box:
<input ng-model="searchSport.name" placeholder="Search for sports by name">
Which you can then use as an Angular filter
<table> <th>...</th>
<tr ng-repeat="Sport in sports" | filter:searchSport.name:strict"> <td> {{Sport.name}}
</td></tr></table>
or in case of the first example use
filter:findonly.name:strict"
I've created a Service that returns a $resource
module.registerFactory('Website', function ($resource, $cacheFactory) {
var cache = $cacheFactory('websites');
var pagedCache = $cacheFactory('websites_paged');
return $resource('/api/websites/:id', {id: '#id'}, {
query: {method: 'GET', isArray: false, cache: pagedCache},
get: {method: 'GET', cache: cache}
});
});
In an edit state I receive all details by calling
$scope.website = Website.get({'id': $stateParams.id});
The $scope.website promise contains my data as expected. (Below a shortened JSON result from server)
{"id":25,"name":"blabla","url":"http://here.com","description":"blabla",
"tags":[
{"id":6,"name":"..."},
{"id":7,"name":"..."}
{"id":10,"name":"..."}
],
"objectives":[
{"id":3206,"code":"WIS AD3.c","name":"[ommitted objective 3206]","parent_id":3203},
{"id":3209,"code":"WIS AD4.b","name":"[ommitted objective 3209]","parent_id":3207}
]}
My problem is with the objectives property.
In my EditCtrl I open a modal and send the objectives as selected items to the modal. That works perfect.
$scope.selectObjectives = function () {
var modalInstance = $modal.open({
templateUrl: 'app/modules/objectives/templates/select-objectives.html',
controller: 'SelectObjectivesModalCtrl',
size: 'lg',
resolve: {
selectedItems: function () {
return $scope.website.objectives;
}
}
});
modalInstance.result.then(function (selectedItems) {
$scope.website.objectives = selectedItems;
console.log($scope.website);
});
}
When closing the modal the newly selectedItems are injected back into $scope.website.objectives. (cfr. modalInstance.result.then() ... )
The console logs perfectly all properties - including the objectives.
Now comes the weird part:
As soon as I try to access $scope.website in another function (ie update)
The objectives property is removed from $scope.website.
This is my update method:
$scope.updateWebsite = function () {
console.log($scope.website);
$scope.website.$save(function () {
$cacheFactory.get('websites').remove('/api/websites/' + $scope.website.id);
$cacheFactory.get('websites_paged').removeAll();
$state.go('app.websites');
});
};
The console logs all properties in $scope.website - except for the objectives. This is completely removed.
I hope I made myself clear enough.
Thanks for taking some time to help me pointing to the right direction.
My bad.
My response didn't return the entire object as should be in a RESTful POST.
App design question. I have a project which has a very large number of highly customized inputs. Each input is implemented as a directive (and Angular has made this an absolute joy to develop).
The inputs save their data upon blur, so there's no form to submit. That's been working great.
Each input has an attribute called "saveable" which drives another directive which is shared by all these input types. the Saveable directive uses a $resource to post data back to the API.
My question is, should this logic be in a directive at all? I initially put it there because I thought I would need the saving logic in multiple controllers, but it turns out they're really happening in the same one. Also, I read somewhere (lost the reference) that the directive is a bad place to put API logic.
Additionally, I need to introduce unit testing for this saving logic soon, and testing controllers seems much more straightforward than testing directives.
Thanks in advance; Angular's documentation may be… iffy… but the folks in the community are mega-rad.
[edit] a non-functional, simplified look at what I'm doing:
<input ng-model="question.value" some-input-type-directive saveable ng-blur="saveModel(question)">
.directive('saveable', ['savingService', function(savingService) {
return {
restrict: 'A',
link: function(scope) {
scope.saveModel = function(question) {
savingService.somethingOrOther.save(
{id: question.id, answer: question.value},
function(response, getResponseHeaders) {
// a bunch of post-processing
}
);
}
}
}
}])
No, I don't think the directive should be calling $http. I would create a service (using the factory in Angular) OR (preferably) a model. When it is in a model, I prefer to use the $resource service to define my model "classes". Then, I abstract the $http/REST code into a nice, active model.
The typical answer for this is that you should use a service for this purpose. Here's some general information about this: http://docs.angularjs.org/guide/dev_guide.services.understanding_services
Here is a plunk with code modeled after your own starting example:
Example code:
var app = angular.module('savingServiceDemo', []);
app.service('savingService', function() {
return {
somethingOrOther: {
save: function(obj, callback) {
console.log('Saved:');
console.dir(obj);
callback(obj, {});
}
}
};
});
app.directive('saveable', ['savingService', function(savingService) {
return {
restrict: 'A',
link: function(scope) {
scope.saveModel = function(question) {
savingService.somethingOrOther.save(
{
id: question.id,
answer: question.value
},
function(response, getResponseHeaders) {
// a bunch of post-processing
}
);
}
}
};
}]);
app.controller('questionController', ['$scope', function($scope) {
$scope.question = {
question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
id: 1,
value: ''
};
}]);
The relevant HTML markup:
<body ng-controller="questionController">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="saveModel(question)" />
</body>
An alternative using only factory and the existing ngResource service:
However, you could also utilize factory and ngResource in a way that would let you reuse some of the common "saving logic", while still giving you the ability to provide variation for distinct types of objects / data that you wish to save or query. And, this way still results in just a single instantiation of the saver for your specific object type.
Example using MongoLab collections
I've done something like this to make it easier to use MongoLab collections.
Here's a plunk.
The gist of the idea is this snippet:
var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
var apiKey = "YOUR API KEY";
var collections = [
"user",
"question",
"like"
];
for(var i = 0; i < collections.length; i++) {
var collectionName = collections[i];
app.factory(collectionName, ['$resource', function($resource) {
var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
var svc = new resourceConstructor();
// modify behavior if you want to override defaults
return svc;
}]);
}
Notes:
dbUrl and apiKey would be, of course, specific to your own MongoLab info
The array in this case is a group of distinct collections that you want individual ngResource-derived instances of
There is a createResource function defined (which you can see in the plunk and in the code below) that actually handles creating a constructor with an ngResource prototype.
If you wanted, you could modify the svc instance to vary its behavior by collection type
When you blur the input field, this will invoke the dummy consoleLog function and just write some debug info to the console for illustration purposes.
This also prints the number of times the createResource function itself was called, as a way to demonstrate that, even though there are actually two controllers, questionController and questionController2 asking for the same injections, the factories get called only 3 times in total.
Note: updateSafe is a function I like to use with MongoLab that allows you to apply a partial update, basically a PATCH. Otherwise, if you only send a few properties, the entire document will get overwritten with ONLY those properties! No good!
Full code:
HTML:
<body>
<div ng-controller="questionController">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
</div>
<div ng-controller="questionController2">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
</div>
</body>
JavaScript:
(function() {
var app = angular.module('savingServiceDemo', ['ngResource']);
var numberOfTimesCreateResourceGetsInvokedShouldStopAt3 = 0;
function createResource(resourceService, resourcePath, resourceName, apiKey) {
numberOfTimesCreateResourceGetsInvokedShouldStopAt3++;
var resource = resourceService(resourcePath + '/' + resourceName + '/:id',
{
apiKey: apiKey
},
{
update:
{
method: 'PUT'
}
}
);
resource.prototype.consoleLog = function (val, cb) {
console.log("The numberOfTimesCreateResourceGetsInvokedShouldStopAt3 counter is at: " + numberOfTimesCreateResourceGetsInvokedShouldStopAt3);
console.log('Logging:');
console.log(val);
console.log('this =');
console.log(this);
if (cb) {
cb();
}
};
resource.prototype.update = function (cb) {
return resource.update({
id: this._id.$oid
},
angular.extend({}, this, {
_id: undefined
}), cb);
};
resource.prototype.updateSafe = function (patch, cb) {
resource.get({id:this._id.$oid}, function(obj) {
for(var prop in patch) {
obj[prop] = patch[prop];
}
obj.update(cb);
});
};
resource.prototype.destroy = function (cb) {
return resource.remove({
id: this._id.$oid
}, cb);
};
return resource;
}
var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
var apiKey = "YOUR API KEY";
var collections = [
"user",
"question",
"like"
];
for(var i = 0; i < collections.length; i++) {
var collectionName = collections[i];
app.factory(collectionName, ['$resource', function($resource) {
var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
var svc = new resourceConstructor();
// modify behavior if you want to override defaults
return svc;
}]);
}
app.controller('questionController', ['$scope', 'user', 'question', 'like',
function($scope, user, question, like) {
$scope.question = {
question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
id: 1,
value: ''
};
$scope.save = function(obj) {
question.consoleLog(obj, function() {
console.log('And, I got called back');
});
};
}]);
app.controller('questionController2', ['$scope', 'user', 'question', 'like',
function($scope, user, question, like) {
$scope.question = {
question: 'What is the coolest JS framework of them all?',
id: 1,
value: ''
};
$scope.save = function(obj) {
question.consoleLog(obj, function() {
console.log('You better have said AngularJS');
});
};
}]);
})();
In general, things related to the UI belong in a directive, things related to the binding of input and output (either from the user or from the server) belong in a controller, and things related to the business/application logic belong in a service (of some variety). I've found this separation leads to very clean code for my part.