AngularJS resourse structure for API - javascript

I have structure API:
api: {
users: {
details: {},
actions: {}
},
settings: {
users: {}
}
}
for example:
GET /api/users
return list of users
GET /api/users/1
return user with id 1
GET /api/users/1/details
return user deteils
GET /api/users/1/details/photo
return user fetail with alias photo
I wrote
.factory('userService', function($resource){
return $resource('/api/users/:id/:items/:itemsId', {}, {
query: { method: 'GET', isArray: false }
});
});
now I can do userService.query() and get list of users
but if I can`t do as this:
var users = userService.query();
users[1].name = 'newName';
users[1].save();
users[1] dont save edited info because users[1] dont have resourse methods, resourse methots isset only users.
And I can`t do as this:
var users = userService.query();
users[1].get('deteils');
How I can add resourse methods for all my structure?

First of all you have to prefix instance methods with a $ sign, like users[1].$save().
Then why are you setting isArray to false but treat the result like an array?
You should also bind the id paramter to the id property of your user. I assume it is called id.
So you would do something like this:
.factory('userService', function($resource){
return $resource('/api/users/:id/:items/:itemsId', { id: '#id' });
});
Note that you have to specify the parameters items and itemsId in your call, otherwise they will be left out.
e.g.:
var users = userService.query();
users[1].name = 'newName';
// assume users[1].id===1
users[1].$save();
// will do a POST /api/users/1
users[1].$get({ items: 'details' });
// will do a GET /api/users/1/details
users[1].$get({ items: 'details', itemsId: 'photo' });
// will do a GET /api/users/1/details/photo
PS: Maybe you should also check if the default methods (listed here) fits your REST API, otherwise you should define your own.

Related

AngularJS UI-Router / $stateParams - Get data based on object ID

I have an app that directs to a custom url based on a given employeeId parameter when a particular employee is clicked on in a list. When the employee is clicked on, you are taken to an employee details page with their id property as a parameter, and I can display this property in the dom.
What I'm trying to do is to display this employee object's other properties in this different state, which I've had a look around at trying to do, but can't find a solution that matches what I'm trying to do.
Example:
Clicking on employee number 21101994 on employees/employeesList state directs to employees/employeeDetails/?21101994 page and displays only their data in the js fields such as {{employee.firstName}}.
I can successfully show the id, but I want to be able to show ALL of the data for the object that matches the employee's id.
The url routing is working fine, and clicking on this employee on the list page directs correctly to a details page with their parameter, but I can't seem to successfully pass their data into the new state/controller.
-
HTML link:
<li class="collection-item col-xs-12" data-ng-repeat="employee in employees | orderBy: sortByAllDepartments | filter:searchAllDepartments">
<a ui-sref="employees/employeeDetails({employeeId: employee.id})" class="employeeLink"></a>
-
What I've tried with the states:
.state('employees/employeesList', {
url: '/employees/employeesList',
templateUrl: 'pages/employees/employeesList.html',
controller: 'employeesListController'
})
.state('employees/employeeDetails', {
url: '/employees/employeeDetails/:employeeId',
templateUrl: 'pages/employees/employeeDetails.html',
resolve: {
employeeId: function($stateParams) {
return $stateParams.employeeId;
}
},
controller: function(employeeId) {
console.log(employeeId)
}
})
-
Employees service:
app.service('employeesService', function() {
var employees = [
{
id: '21101994',
firstName: 'Employee',
lastName: 'One'
}
];
var addEmployee = function(newObj) {
employees.push(newObj);
};
var getEmployees = function() {
return employees;
}
return {
addEmployee: addEmployee,
getEmployees: getEmployees
}
})
-
Employees List Controller:
app.controller('employeesListController', function($scope, $stateParams, employeesService) {
$scope.active = 'active';
$scope.sortByAllDepartments = '+lastName';
$scope.employees = employeesService.getEmployees();
})
The states will be receiving params like
url: '/employees/employeeDetails/{:employeeId}',
Or
url: '',
params: {
employeeId: null
},
resolve: {...}
I prefer the second one to receive due to clarity.
To pass all the data of employee, localstorage of employee object will be better in case you need this object frequently or in other parts in application.
localStorage.setItem('employeeData', JSON.stringify(empData));
To access this you can do
let empData = JSON.parse(localstorage.employeeData); //Object
In case you need to need this stored data, let's say you are navigating away from this employeeDetails state, you can delete this
localstorage.removeitem('employeeData');
If you require passing multiple state params, just add a comma separated string
ui-sref="employeeDetails({id: emp_id, name: emp_name, mobile: emp_mobile})"
and then receive this in state as below
params: {
employeeId: null,
employeeName: null,
employeeMobile: null
},
I hope this last makes clear as in why should you avoid passing too many params in stateParams

Meteor User table value axtracting

How do I pick the email address value from meteor Mongo user table?
I have written below query to pick the element:
users=Meteor.users.find({},{emails:1})
This the code I have written to fetch the email address, but I don't know how much it's affecting performance in the code:
users = Meteor.users.find({})
users.forEach(function(key,option){
key.emails.forEach(function (key,option){
console.log(key.address)
});
});
In meteor, you should call:
users = Meteor.users.find({}, { fields: { emails: 1 } })
Reference in docs
EDIT
Please remember users is a cursor object. Cursor objects can be handled directly in templates, and must be the return of publications. You can't iterate a cursor directly in a javascript loop.
Example: (remember authorization in production publications)
Meteor.publish('user-emails', function() {
return Meteor.users.find({}, { fields: { emails: 1 } });
});
If you want to directly access the user instances, for example to iterate them in a javascript code, you need to fetch the cursor (reference in docs).
Example:
var users = Meteor.users.find({}, { fields: { emails: 1 } }).fetch();
Now users is an array of users. Feel free to iterate them.
Example (I'm using underscore.js):
var users = Meteor.users.find({}, { fields: { emails: 1 } }).fetch();
_.each(users, function(user) {
console.log(user.emails);
});
Now, if you need a vector only with emails, one on each index, you can pluck the emails from a fetched array with underscore.js (reference of pluck)
var emails = _.pluck(Meteor.users.find({}, { fields: { emails: 1 } }).fetch(), 'emails');
Hope it works :)
if its not working, dont forget to return
return users

Links (relations) to REST resources in AngularJS

I have a REST API, which returns User object, where its roles are specified via link to another object. So at localhost/project/api/users/27/ is JSON object:
{
"id": 42,
"name": "John",
"prijmeni": "Doe",
"login": "johndoe",
"nickname": null,
"grade": 1.3,
"roles": {
"href": "http://localhost/project/api/users/1716/roles/"
}
}
What I'm trying to do is to get roles in controller. My User service looks like this:
projectServices.factory('User', ['$resource', 'UserRoles',
function($resource, UserRoles, Role) {
var User = $resource('http://localhost/project/api/users/:id', {}, {
'query': {
method: 'GET',
isArray: true
}
});
return User;
}
]);
and I tried to add (to that resource code block):
User.prototype.roles= function(){
return UserRoles.get({id:42});
};
this one freezes browser when called in ngRepeat. So I tried
User.prototype.roles = UserRoles.get({id:42});
this works. Then I tried
User.prototype.roles = $resource(this.roles.href, {}, {
'get': {
method: 'GET',
isArray: true
}
});
says, that roles is undefined. I also tried to add transformResponse param to User service GET action, but that function was never called.
The second option works just perfectly fine - except, that I have to hardcode the user ID. Suitable solution would be with somehow getting the user ID for me (i tried this.id, but that didn't work).
Perfect solution would be creating resource from given href, but as I can't access roles in prototype, I don't know how.
Thanks for any advice.
This should do the trick
projectServices.factory('UserRoles', function(){
return $resource('http://localhost/project/api/users/:id', {id: #id},
{'query': {
method: 'GET',
isArray: true
})
}
Now you can call it with
UserRoles.get({id:42})
// this makes the request : http://localhost/project/api/users/42
The #id tells angular to use the id key from the parameter passed.

Backbone not making a put request with save() after save

I am experiencing a really interesting problem with backbone, I have a function like this in one of my views:
addpeople :function(){
var authArray = _.clone(this.model.get("authorizedUsers"));
var values = $(".add-input").val().split(",");
values.forEach(function(value) {
authArray.push(value);
});
this.model.set("authorizedUsers" , authArray);
this.model.save();
},
this function gets called when a button is clicked. This version of the function triggers a change event because I am cloning my array, but for some reason this.model.save()never gets called, aka the server never receives a PUT request. When I refresh the page I go back to the old state of the model..
However if I dont clone the array and change the function to, this:
addpeople :function(){
var authArray = this.model.get("authorizedUsers");
var values = $(".add-input").val().split(",");
values.forEach(function(value) {
authArray.push(value);
});
this.model.set("authorizedUsers" , authArray);
this.model.save();
},
This time the PUT request is sent successfully, but the page is not re-rendered because a change event is not triggered. When I refresh the page I can see the updated model..
I know that I can manually trigger a change event in the second example but I am more curious about why my this.model.save() is not called in the first example..
To help you understand the problem more my model looks something like:
var PostModel = Backbone.Model.extend({
urlRoot : '/tweet',
idAttribute: '_id',
defaults:{
name: '',
comments: [],
tags: [],
authorizedUsers: [],
postedBy : '',
dateCreated: ''
},
});
and my node.js update function looks like:
exports.updateTweet = function(req,res){
console.log("Getting called ! ")
var update = req.body;
var id = req.url.split("/")[2];
Post.update({ _id: id }, { $set: { authorizedUsers: req.body.authorizedUsers }}, function (err, post) {
if (err) return handleError(err);
});
res.end();
};
The reason why change didn't trigger for your second example is because it is the same object and Backbone ignore it. Hence no change event triggered.
As for why the first one failed; do you have validator for your model? May be something that validating for empty string perhaps? val() can return an empty string and split() on empty string will return [""]
Also, your defaults should be a function otherwise all your model would have the same instance of comments, tags and authorizedUsers
From Backbone doc.
Remember that in JavaScript, objects are passed by reference, so if you include an object as a default value, it will be shared among all instances. Instead, define defaults as a function.
Arrays are object too.
var PostModel = Backbone.Model.extend({
urlRoot : '/tweet',
idAttribute: '_id',
defaults: function(){
return {
name: '',
comments: [],
tags: [],
authorizedUsers: [],
postedBy : '',
dateCreated: ''
}
}
});
Lastly, array.forEach() is not available on IE8 and older.

How to alter the data returned by $resource in Angular.js?

I'm using an API that returns JSON data in this format:
{
paging: {
previous: null,
next: null
},
data: [
{ title: 'First Item' },
{ title: 'Second Item' },
...
]
}
I'm using Angular's $resource service to fetch this data.
My code - which is located in a controller - goes something like this:
var Entity = $resource('/api/entities');
var entities = $scope.entities = Entity.get();
And then, in the view, I can display the data like this:
<ul>
<li ng-repeat="entity in entities.data">{{entity.title}}</<li>
</ul>
It all works fine, but:
I'd rather expose only the contents of entities.data to the view, instead of the whole entities object. How can I intercept the data returned by the GET request to modify it before it populates $scope.entities?
Correlated question: since I am fetching an array of data, it would be cleaner to use Entity.query() instead of Entity.get(). But if I use Entity.query() in the code above, I get an error "TypeError: Object # has no method 'push'". This makes sense, since the API is returning an object instead of an array (hence, no 'push' method on the object). Again, if I could extract the .data attribute from the response, I'd have an array.
Following these indications by Dan Boyon, I managed to customize the default $resource service and to override the .get() or .query() methods, but I'm not sure where to go from there.
I don't think you need to modify the get or query defaults. Just use the success callback to do what you want. It should be more robust as well.
Entity.get(
{}, //params
function (data) { //success
$scope.entities = data.data;
},
function (data) { //failure
//error handling goes here
});
Html will be cleaner, too:
<ul>
<li ng-repeat="entity in entities">{{entity.title}}</<li>
</ul>
By the way, I usually declare services for my resources and then inject them into my controllers as I need them.
myServices.factory('Entity', ['$resource', function ($resource) {
return $resource('/api/entities', {}, {
});
}]);
You can use the Response Transformer (transformResponse) like this:
$resource('/api/entities', {}, {
query: {
method: 'GET',
responseType: 'json',
isArray: true,
transformResponse: function (response) {
return response.data;
}
}
});
This code modifies the "query" method behaviour, you can do the same for "get" ... !

Categories

Resources