Accessing first item in $resource.query() - Angularjs - angularjs

I'm having some problems with the $resource.query() method. I'm trying to access the first element in it, but its not working for me. The selection controllers returns the collection without a problem.
controller.js
angular.module('bookshelf').controller('bsMainCtrl', function($scope, bsBooks){
var data = bsBooks.query();
$scope.singleBook = data[0];
});
angular.module('bookshelf').controller('bsSelectionCtrl', function($scope, bsBooks){
var booksData = bsBooks.query();
$scope.books = booksData;
});
main.jade
.singleBook
.selection
include selection
.content
include book-list
book-list.jade
div(ng-controller='bsMainCtrl')
p Title: {{ singleBook.title }}
p Author: {{ singleBook.author }}
p ISBN: {{ singleBook.ISBN }}
p Created at: {{ singleBook.createdAt | date }}
a(ng-href='/edit/{{ singleBook._id }}') Edit
selection.jade
div(ng-controller="bsSelectionCtrl")
div(ng-repeat='book in books')
button(ng-click='selectBook()') {{ book.title }}
service.js
angular.module('bookshelf').factory('bsBooks', function($resource) {
var BookshelfResource = $resource('/api/books/:_id', {_id: "#id" }, {
update: {method: 'PUT', isArray: false}
});
return BookshelfResource;
});
route.js
exports.getBooks = function(req, res){
Book.find({}).exec(function(err, collection){
res.send(collection);
});
};
Is it Angular $scope related? I have tried using the Angular inspector, but could only conclude that singleBook is null.

$resource.query() returns a promise, which contains no data initially, so you can't access it by numbered index at the point where you're attempting to.
Try instead:
bsBooks.query().$promise.then(function(data){
$scope.singleBook = data[0];
});
Demo

Use callback or promise for wait data from server
var data = bsBook.query(function(){
$scope.singleBook = data[0];
})
see here

Related

AngularJS : data binding not working as expected using a service

I'm having trouble with services in AngularJS.
Being a newbie it's probably something crucial I'm missing here.
The title {{p01g.visiteTitel}} isn't magically refreshing but keeps displaying "sometitle".
The ng-repeat is working as expected.
(dataFactory is a service that connects to a remote server using $resource)
My service :
myApp.service('p00Service', ['dataFactory', function(dataFactory) {
var service = this;
service.visites = [];
service.visiteAantal = 0;
service.visiteTitel = "sometitle";
service.findVisites = function (datum) {
dataFactory.get({verb: "search", q: datum}, function (data) {
angular.copy(data.visites, service.visites);
service.visiteAantal = service.visites.length;
if (service.visiteAantal === 0) {
service.visiteTitel = "geen visites op " + datum
} else if (service.visiteAantal === 1) {
service.visiteTitel = "1 visite op " + datum
} else {
service.visiteTitel = service.visiteAantal + " visites op " + datum
}
});
};
}]);
My controller :
myApp.controller('p01gCtrl', ['p00Service', function (p00Service) {
var vm = this;
var datum = moment(); //I'm using moment.js -> moment() is date of today
p00Service.findVisites(datum);
vm.visites = p00Service.visites;
vm.visiteTitel = p00Service.visiteTitel;
}]);
My HTML :
<div class="p01g" ng-controller="p01gCtrl as p01g">
<div class="well_grey" style="min-height:40px;max-height:40px;max-width:330px">
<p style="font-size:20px;text-align:center;cursor:pointer;">
{{p01g.visiteTitel}}
</p>
</div>
<div class="well" style="min-height:190px;max-height:190px;max-width:330px">
<table style="width:100%;line-height:40px">
<tbody ng-repeat="visite in p01g.visites">
<tr>
<td style="width:20%;line-height:40px;padding-left:7px"><span style="font-size:16px">{{visite.t133datum | date:"dd/MM/yy"}}</span></td>
<td style="width:60%;line-height:40px;text-align:center"><span style="font-size:16px">{{visite.t133achternaam}}</span></td>
<td style="width:20%;line-height:40px;padding-left:30px"><span style="font-size:16px">{{visite.t133classificatie}}</span></td>
</tr>
</tbody>
</table>
</div>
</div>
the dataFactory looks like this :
myApp.factory("dataFactory", ['$resource', function ($resource) {
return $resource("/vf/rest/visites/:verb", {}, {
get: {method: "GET", isArray: false, cache: false}
});
}]);
I copied your code exactly and created a plunkr and added my own dataFactory to return sample data and there are no errors and the data is getting bound fine, so I believe the issue is in your implementation of dataFactory, or the way you are calling it (make sure it's expecting a callback function, since that is what you are passing it).
Edit: Here is a new plunkr with updated code that I believe reproduces your issue. So the reason why the title is not getting updated is because
vm.visiteTitel = p00Service.visiteTitel;
is setting vm.visiteTitel to the value of the string in p00Service.visiteTitel, but it is NOT getting a reference to p00Service.visiteTitel, so if you update visiteTitel in your p00Service after this assignment (which is happening in this case because the $resource callback is async), then it has no effect on vm.visiteTitel.
One way to make this work would be to pass a callback to p00Service to update your controller values like so:
p00Service.findVisites(datum, function(visites, visiteTitel) {
vm.visites = visites;
vm.visiteTitel = visiteTitel;
});
and then update your service to call this callback after the data is loaded:
service.visites = [];
service.visiteAantal = 0;
service.visiteTitel = "sometitle";
service.findVisites = function (datum, callback) {
dataFactory.get({}, function (data) {
...
if(callback) {
callback(service.visites, service.visiteTitel);
}
});
};
This code can be cleaned up a bit, but here is a workable plunkr demonstrating vm.visites and vm.visiteTitel getting updated correctly.

Delete a id using Angular

Users can search for a movie title in my inputfield and it will show them a list of movies. They can then hover over a title and a Add Movie button pops up. They can click it so it gets added to their frontpage. But now I'm trying to figure out how they can remove that movie from the database. But I can't find a clear example on how to do this.
This is how I show the movies,
%div{"ng-repeat" => "movie in movies"}
{{ movie.id }}
{{ movie.title }}
%a{"ng-click" => "deleteMovie($index)"}delete
I think I have to create a delete action in my controller called deleteMovie which works with a service to remove the id from the database.
This is how I see the service,
.factory('removeMovie', ['$http', function($http) {
return {
deleteMovie: function() {
return $http.delete('/movies.json/$id');
}
};
}])
The deleteMovie would be called in the controller. But I have the feeling I'm approaching this the wrong way.
Please follow the below code.
HTML View
%div{"ng-repeat" => "movie in movies"}
{{ movie.id }}
{{ movie.title }}
%a{"ng-click" => "deleteMovie(movie)"}delete
Controller
.controller('MainController', ['$scope','removeMovie',
function($scope, removeMovie) {
$scope.deleteMovie = function(movie){
removeMovie.deleteMovie(movie.id).then(function(sucessResponse){
//success callback
},function(errorResponse){
//Error callback
})
}
}
]);
Factory
.factory('removeMovie', ['$http', function($http) {
return {
deleteMovie: function(movieId) {
var _movieId = parseInt(movieId);
return $http.delete('/movies.json/'+_movieId);
}
};
}]);
if you are using the static json, then you do not need to make any rest request. You can simple splice the movie from movies array.if you are deleting movie from database then you need to make a rest request like this $http.delete('/movies/'+_movieId);
if the service only calls one http call, you might ass well write that already i your controller:
view:
%div{"ng-repeat" => "movie in movies"}
{{ movie.id }}
{{ movie.title }}
%a{"ng-click" => "deleteMovie(movie.id)"}delete
and inthe controller
.controller('myCtrl', ['$scope', '$http', function($scope, $http) {
$scope.deleteMovie = function(id) {
$http.delete('/movies.json/' + id);
}
}])
Pass in the whole movie object so that you can use it for the post but also to simply index it in the array for removal locally as well.
Adjust factory for id
.factory('removeMovie', ['$http', function($http) {
return {
deleteMovie: function(id) {
return $http.delete('/movies/' +id);
}
};
}]);
HTML
"ng-click" => "deleteMovie(movie)"}delete
Now make request from controller and remove from local array on completeion
$scope.deleteMovie = function(movie) {
removeMovie.deleteMovie(movie.id).then(function(resp){
// validate your response here before next step
// get index of movie in array
var idx = $scope.movies.indexOf(movie);
//remove from array
$scope.movies.splice( idx, 1);
});
}
Note that it seems strange to have a factory just for removeMovie.
Normally you would have all your CRUD operations in the same factory

Is it possible to use parameterized URL templates with angular $http service

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!

$http.get method not working on ng-submit

I want $http.get method to work when a form is submitted.
Here is my code. The object $scope.questions is being set when the method is called but the data doesn't show up in the div. Moreover, when the $http.get method is outside the signIn() function it works just fine.
$scope.signIn = function(data) {
$location.path('/profile');
var url = "database/fetch_data.php?query=";
var query = "Select * from question where userId=2";
url += query;
$http.get(url).success(function(questionData) {
$scope.questions = questionData;
console.log($scope.questions);
});
};
<div>
User Profile
<br/>Question Posted
<br/>
<input ng-model="query.title" id="value" type="text" placeholder="Search by Title..." ">
<div>
<ul>
<li ng-repeat="question in questions | filter: query ">
{{question.title}}
</li>
</ul>
</div>
<br/>
</div>
You need to move your $location.path('/profile') inside your http request. Remember that a http request is async call. You should redirect after getting the data not before.
$scope.signIn = function(data) {
var url = "database/fetch_data.php?query=";
var query = "Select * from question where userId=2";
url += query;
$http.get(url).success(function(questionData) {
$scope.questions = questionData;
console.log($scope.questions);
$location.path('/profile');
});
};
If you're redirecting to another route with a completely separate scope you will lose any scope you're setting in the success handling.
From what I'm reading you're clicking a button to do an action. After that action you're redirecting to another page with a separate controller and trying to persist the data.
Unfortunately, Angular hasn't figured out a great way to do this. The easiest way to persist data through controllers and scope is to create a service that will store it in one controller and grab it in another controller.
For instance:
$scope.signIn = function(data) {
var url = "database/fetch_data.php?query=";
var query = "Select * from question where userId=2";
url += query;
$http.get(url).success(function(questionData) {
$location.path('/profile');
storageService.store("question", questiondata)
});
};
Your new factory to persist data through:
angular.module('moduleName').factory('storageService', [
function () {
return {
store: function (key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
get: function(key) {
return JSON.parse(localStorage.getItem(key));
},
remove: function(key) {
localStorage.removeItem(key);
}
}
}
]);
Other controller to access data:
$scope.question = storageService.get("question");
// remove localstorage after you've grabbed it in the new controller
storageService.remove("question");
An alternative to doing the somewhat 'hacky' way of using localStorage to persist data through controllers is to use ui-router and have a resolve on the route you're redirecting to.
For instance:
$scope.signIn = function(data) {
$state.go('profile');
};
In your route file:
.state('profile', {
url: '/profile'
controller: profileControllerName,
templateUrl: 'profileHtmlTemplate.html',
resolve: {
'questions': [function() {
var url = "database/fetch_data.php?query=";
var query = "Select * from question where userId=2";
url += query;
$http.get(url).success(function(res) {
return res.data;
});
}]
}
}
In your profile controller:
Inject your 'questions' resolve into your controller and assign `$scope.question = questions;
This will make the HTTP call as soon as you click the route, return the data if successful, then render the page. It will NOT render the page if the resolve does not return success. This will ensure your data will be loaded before you load the page that depends on that data.
I would highly recommend using services to hold your HTTP calls for specific parts of your application. If you have a GET questions, POST question, PUT question. I would create a questionService and make all my HTTP methods there so you don't have to clutter your routes. You would only have to call:
.state('profile', {
url: '/profile'
controller: profileControllerName,
templateUrl: 'profileHtmlTemplate.html',
resolve: {
'questions': [function() {
return questionService.getQuestions(id).then(function(res) {
return res.data;
})
}]
}
}

Passing current tab url to server in angular resources fetch

I am trying to send the current tab url to the resource service { in param } .
but the global tablUrl is not having any value at
var url = "http://[localhost]/getProfile?domain="+tabUrl
but getting logged corrent at :
console.log(tabUrl);
this is my code :
var tabUrl;
angular.module('jsonService', ['ngResource'])
.factory('JsonService', function($resource) {
chrome.tabs.getSelected(null, function(tab) {
tabUrl = tab.url;
console.log(tabUrl);
});
var url = "http://[localhost]/getProfile?domain="+tabUrl
return $resource(url,{}, {
list : {
method : 'GET',
cache : true
}
});
});
template binding :
<body ng-controller="extensionCtrl">
this is controller :
app.controller('extensionCtrl', function($scope , JsonService) {
JsonService.get(function(data){
$scope.data = data;
});
});
First:
Please, don't use the deprecated chrome.tabs.getSelected. Use chrome.tabs.query instead.
Second:
chrome.tabs.getSelected/chrome.tabs.query are asynchronous. This means that execution continues while they do some work in the background and the specified callback is called when they are done.
So, in a case like this:
line 1: chrome.tabs.getSelected(null, funkyCallback);
line 2: var url = ...
line 3: return $resource(...);
...a possible (and very probable) order of execution is:
1. chrome.tabs.getSelected (starts retrieving the active tab in the background)
2. line 2 gets executed (at this time 'tabURL' is not set yet)
3. line 3 gets executed (returning something)
4. Once the the active tab is retrieved, 'funkyCallback' is called
(setting 'tabURL' after it is too late).
When using asynchronous APIs (such as most of the chrome.* APIs), you have to change the whole logic of your scripts to be in line with the asynchronous nature of the API calls.
E.g., you could achieve the same result like this:
HTML:
<html ng-app="jsonService">
...
<body ng-controller="extensionCtrl">
<p>{{jsonData}}</p>
...
JS:
var app = angular.module("jsonService", ["ngResource"]);
app.factory("JsonFactory", function($resource) {
var url = "http://localhost/getProfile?domain=:tabUrl";
var retObj = $resource(url, {}, {
list: {
method: "GET",
cache: true
}
});
return retObj;
});
app.controller("extensionCtrl", function($q, $rootScope, JsonFactory) {
chrome.tabs.query({ active: true }, function(tabs) {
JsonFactory.list({ tabUrl: tabs[0].url }, function(data) {
// On success...
$rootScope.jsonData = data;
}, function(data) {
// On error...
$rootScope.jsonData = "Error using JsonFactory.list(...) !";
});
});
});
See, also, this short demo that does something similarly asynchronous

Resources