I'm new in AngularJS and have some problems with Resource object that is returned by $resource. If I call data['something'], I get what I want. But the problem is that I don't know key and Resource has no .keys() function. How can I solve this? Resource object has only one key, if this helps.
Factory for request:
.factory('StorageRequest', ['$resource',
function ($resource) {
return $resource('/api/storage/:id/query/:queriString', {id: '#id'}, {});
}
]);
Code:
query = "ABC_12345";
StorageRequest.get({"id": $rootScope.selectedData,
"queriString": query}, function (data){
key = ??
$rootScope.values[key] = data[key];
});
Value of data:
Resource {ABC_12345: Array[3], $get: function, $save: function, $query: function, $remove: function…}
Response from the server:
{
- ABC_12345: [
1,
2,
3
]
}
You're actually working too hard :).
The get method will return a reference to an empty object that will be populated once the asynchronous http call to the resource completes.
You're modelling doesn't make a lot of sense to me though. Each value should be keyed to it's unique ID - presumably that's what your selectedData is. With that approach, you already know what the 'key' value should be.
It would look something like this:
query = "ABC_12345";
$rootScope.values[$rootScope.selectedData] = StorageRequest.get({"id": $rootScope.selectedData,
"queriString": query}, function (){
});
Related
I'm using angular factory to share data between controller each controller is for one page. Here is my js file
app.factory('myService', function() {
var savedData = {};
function set(data) {
savedData = data;
}
function get() {
return savedData;
}
return {
set: set,
get: get
}
});
app.controller("logincont", ['$scope','$http','md5','$window','myService',function($scope,$http,md5,$window,myService){
$scope.cari = function () {
$http.get('http://localhost:8089/MonitoringAPI/webresources/login?a='+$scope.userid+'&d='+$scope.password).then(function(response){
$scope.reslogin = response.data;
$scope.reslogin2 = response.data.username;
myService.set($scope.reslogin2);
console.log($scope.reslogin2);
console.log(myService.set($scope.reslogin2));
});
};
}]);
app.controller("moncont", ['$scope','$http','$filter','myService',function($scope,$http,$filter,myService){
$scope.user = myService.get();
console.log($scope.user);
}]);
Here is the result when I call console.log
console.log($scope.reslogin2) = ristian
console.log(myService.set($scope.reslogin2)) = undefined
console.log($scope.user)={}
The result that I expected, ristian is filled each scope.
There are a lot of issues here, some have already been addressed in the comments. Another of the problems is that your set() function overrides the savedData object reference. So when you call myService.get() you get the reference to the empty object, then when the http request resolved and set is called, whatever you've assigned originally, still references the empty object.
So in the above example, this is what happens chronologically (assuming you call $scope.cari() at some point in time):
$scope.user = myService.get(); assigns a reference to the empty object in savedData to $scope.user
At some point you make a http request, that calls myService.set($scope.reslogin2);
This call to set overrides the reference in savedData.
$scope.user still references the old empty object.
To fix this particular issue, you need to either rethink your entire flow, or replace your set method with somethink like this
function set(data) {
angular.extend(savedData, data);
}
which mutates the savedData object, instead of overrides it. This can cause other issues, with empty properties not being overriden, but that entirely depends on what properties are in data.
Backbone 1.1.2
Underscore 1.7.0
jQuery 1.11.1
I have a single collection that holds messages.
My messages can be be of different types (and the endpoints in the api are different for each type, but I have an endpoint that allows me to do one request and get all the messages)
When Collection.fetch()
I need to be able to define which model to use when populating the collection based on existing properties.
I have tried as suggested here: A Backbone.js Collection of multiple Model subclasses
as well as the backbone documentation backbonejs.org
My code looks like this
model: function (attr, options) {
if(attr.hasOwnProperty('prop')){
return new PropModel(attr,options);
}
else if(attr.hasOwnProperty('another_prop')){
new AnotherPropModel(attr,options);
}
},
the attr value is just one big array of objects, so without traversing somehow this solution makes no sense to me and its obvious why it doesn't work.
Am I handling this correctly is there another way to do this?
---UPDATE----
I have also tried doing this in the Parse Function of the collection and my collection is just empty
parse: function (resp, options) {
_.each(resp, _.bind(function (r) {
console.log(this);
if(r.hasOwnProperty('prop')){
this.add(new PropModel(r));
}else{
this.add(new AnotherPropModel(r));
}
},this));
}
So the solution was a mix of using model function and return.
Here goes the explanation:
First off we have the parse function
which is just an entry point for us to alter a response that we receive from our server
parse: function (resp, options) {
return resp;
}
In my case, the server was returning an Object of Object as
{{1:data},{2:data}}
Firstly this is strange and obviously needs to be resolved.
The IMPORTANT POINT IS:
When backbone assess the response return from parse, it needs to decide where to break off for each model as in what defines a new model.
Backbone sees objects as a single model and as in my case, I had one big object, I was getting one big model... this is why the attrs argument in model function was one big mush of data.
So I simply altered my response in the parse function and Voila!! everything in model function worked as expected:
Here is the code:
model: function (attr, options) {
if(attr.hasOwnProperty('prop')){
return new PropModel(attr,options);
}
else if (attr.hasOwnProperty('anotherProp')){
return new AnotherPropModel(attr,options);
}
},
parse: function (resp, options) {
var response = [];
_.each(resp, _.bind(function (r) {
response.push(r);
},this));
return response;
}
Im sure there is a better way to resolve the object to array, but for now this works and I'm smiling again!!
This article lead me to the solution:
A collection of muppets
You could do something like the following - reacting to the different type (if possible) and then provide a different URL. Then once the JSON models are in the template, then you can render the HTML the way you like:
Example Json
"[{"id":1,"Type":"Person"},{"id":2,"Type":"Business"}]"
Example Model
var Person = Backbone.Model.extend({
keyTypes: {
Person: 'Person',
Business: 'Business'
},
url: function() {
// we are assuming only two types. Change logic if there are three or more types.
return this.get('Type') === this.keyTypes.Person ? '/api/people' : '/api/businesss';
}
});
Collection
var Collection = Backbone.Collection.extend({
model: Person
});
View
var View = Backbone.View.extend({
initialize: function() {
this.collection = new Collection()
.on('fetch', this.render, this);
},
bootstrap: function() {
this.collection.fetch();
}
render: function() {
this.$el.html(_.template({
models: this.collection.toJSON()
}));
}
})
** !! Update !! **
If you want to still use parse, it will could to look the following.
parse: function (data, options) {
var models = [];
_.each(data, function (entity) {
// this is IE8 Safe....
var model = Object.prototype.hasOwnProperty.call(entity,'prop') ? PropModel : AnotherPropModel;
models.push(new model(entity));
});
return models;
}
I'm looking for a way to sort an array inside an Angular service, and still retain the correct bindings in the controller.
If I skip the sorting, the bindings work great, but the array isn't ordered as I need it to be.
Whenever I perform the sort using Lodash's _.sortBy or angular's $filter('orderBy') service, one of two things happens:
The array in the service is sorted correctly, but the binding to the controller is severed due to it no longer referencing the same array anymore.
If I attempt to fix this by using Lodash's _.cloneDeep or angular's angular.copy, the browser freezes due to circular references (?).
Service.js
angular.module('exampleapp')
.factory('ClientFeedService', function($filter, $firebase, FIREBASE_URL, FeedItemService) {
return function(clientId) {
var ClientFeedService = this;
var ref = new Firebase(FIREBASE_URL + 'feeds/clients/' + clientId);
var initialDataLoaded = false;
ClientFeedService.feedArray = [];
ClientFeedService.sortItems = function() {
// Sorting logic here
};
/**
* Bind to the initial payload from Firebase
*/
ref.once('value', function() {
// Sort items after initial payload
ClientFeedService.sortItems();
initialDataLoaded = true;
});
/**
* Bind to new items being added to Firebase
*/
ref.on('child_added', function(feedItemSnap) {
console.log('child_added');
ClientFeedService.feedArray.unshift(FeedItemService.find(feedItemSnap.name(), feedItemSnap.val()));
// Sort after new item if initial payload loaded
if (initialDataLoaded) {
ClientFeedService.sortItems();
}
});
ClientFeedService.getFeedItems = function() {
return ClientFeedService.feedArray;
};
return ClientFeedService;
};
});
Controller.js
app.controller('ClientsFeedCtrl', function($scope, $stateParams, ClientFeedService) {
var clientId = $stateParams.clientId;
$scope.clientFeed = new ClientFeedService(clientId).getFeedItems();
});
There are a couple of ways that you can solve this. First, let's look at what is happening.
You are assigning the initial array to $scope.cliendFeed. After this, as data is added, a new Array is being generated and stored in the Service, but you still have a reference to the original Array. So ultimately, what you want to do is find a way to keep $scope.clientFeed in sync with your service.
The simplest solution is probably to use a getter method instead of storing a reference to the array in your scope.
In order to do this, you would have to add something like this:
var service = new ClientFeedService(clientId);
$scope.getClientFeed = function () {
return service.getFeedItems();
};
And make sure your ng-repeat called this function:
<li ng-repeat="item in getClientFeed()">...</li>
Hope that helps!
You can push the new data returned from API to the same array in the controller and then apply the $filter
Here is example
function getData(){
$scope.array.push(returnData);
sortArrayList($scope.orderByField, $scope.reverseSort);
}
function sortArrayList(orderByField, reverseSort){
$scope.array = $filter('orderBy')($scope.array, orderByField, reverseSort);
}
This is probably yet another javascript scope question with a knockout spin.
Going through the examples in a book that I purchased in which the author presents an example of a single page application but chooses not to completely explain the javascript as it is not the focus of the book.
My question is how does the function in the success action in the ajax call understand the definition for the nested object used as an argument.
outerobj.myarray.push.apply(outerobj.myarray, data.map(function (nestedobj) { nestedobj.prop1 }))
The main object
var outerobj = {
view: ko.observable("View1")
nestedobj : {
prop1 : ko.observable(""),
prop2 : "",
prop3 : ko.observable("")
},
myarray : ko.observableArray([])
}
In a later Ajax/Jquery option there is a call to push.apply with a call like this
var getProperties = function ()
{
$.ajax("/path", {
type: "GET",
success: function (data) {
outerobj.myarray.removeAll();
outerobj.myarray.push.apply(outerobj.myarray, data.map(function(nestedobj) { return nestobj.prop1; }))
outerobj.view("Result");
}
});
}
Array.prototype.push will push values into the 'top' of the array. It can receive a variable number of arguments, such that:
[1].push(2); //[1,2]
[1].push(2, 3); //[1,2,3]
.apply executes a function from the given scope, applying a given array of arguments. It spreads out the array as arguments to the function. For example:
var arr =[];
[].push.apply(arr, [1,2]); // this is equivalent to arr.push(1,2);
Finally, .map returns an array... So basically this is a nice way of pushing an array of items into the array.
I have a resource that returns an array from a query, like so:
.factory('Books', function($resource){
var Books = $resource('/authors/:authorId/books');
return Books;
})
Is it possible to add prototype methods to the array returned from this query? (Note, not to array.prototype).
For example, I'd like to add methods such as hasBookWithTitle(title) to the collection.
The suggestion from ricick is a good one, but if you want to actually have a method on the array that returns, you will have a harder time doing that. Basically what you need to do is create a bit of a wrapper around $resource and its instances. The problem you run into is this line of code from angular-resource.js:
var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
This is where the return value from $resource is set up. What happens is "value" is populated and returned while the ajax request is being executed. When the ajax request is completed, the value is returned into "value" above, but by reference (using the angular.copy() method). Each element of the array (for a method like query()) will be an instance of the resource you are operating on.
So a way you could extend this functionality would be something like this (non-tested code, so will probably not work without some adjustments):
var myModule = angular.module('myModule', ['ngResource']);
myModule.factory('Book', function($resource) {
var service = $resource('/authors/:authorId/books'),
origQuery = service.prototype.$query;
service.prototype.$query = function (a1, a2, a3) {
var returnData = origQuery.call(this, a1, a2, a3);
returnData.myCustomMethod = function () {
// Create your custom method here...
return returnData;
}
}
return service;
});
Again, you will have to mess with it a bit, but that's the basic idea.
This is probably a good case for creating a custom service extending resource, and adding utility methods to it, rather than adding methods to the returned values from the default resource service.
var myModule = angular.module('myModule', []);
myModule.factory('Book', function() {
var service = $resource('/authors/:authorId/books');
service.hasBookWithTitle = function(books, title){
//blah blah return true false etc.
}
return service;
});
then
books = Book.list(function(){
//check in the on complete method
var hasBook = Book.hasBookWithTitle(books, 'someTitle');
})
Looking at the code in angular-resource.js (at least for the 1.0.x series) it doesn't appear that you can add in a callback for any sort of default behavior (and this seems like the correct design to me).
If you're just using the value in a single controller, you can pass in a callback whenever you invoke query on the resource:
var books = Book.query(function(data) {
data.hasBookWithTitle = function (title) { ... };
]);
Alternatively, you can create a service which decorates the Books resource, forwards all of the calls to get/query/save/etc., and decorates the array with your method. Example plunk here: http://plnkr.co/edit/NJkPcsuraxesyhxlJ8lg
app.factory("Books",
function ($resource) {
var self = this;
var resource = $resource("sample.json");
return {
get: function(id) { return resource.get(id); },
// implement whatever else you need, save, delete etc.
query: function() {
return resource.query(
function(data) { // success callback
data.hasBookWithTitle = function(title) {
for (var i = 0; i < data.length; i++) {
if (title === data[i].title) {
return true;
}
}
return false;
};
},
function(data, response) { /* optional error callback */}
);
}
};
}
);
Thirdly, and I think this is better but it depends on your requirements, you can just take the functional approach and put the hasBookWithTitle function on your controller, or if the logic needs to be shared, in a utilities service.