I'm building out a little app and the first thing I need to do is make a call to Parse's REST API using AngularJS's ngResource.
I've successfully constructed two Parse classes (let's call them "Parent" and "Child" for now) and set up a many-to-many relation between them such that a Parent can relate to zero-n Child objects and a Child may relate to zero-n Parent objects.
So far, the very basic stuff works fine. This method, for example, successfully retrieves all the Parent objects:
.factory('Parent', function($resource, appConfig) {
return $resource('https://api.parse.com/1/classes/Parent/:id', {}, {
list : {
method : 'GET',
headers: appConfig.parseHttpsHeaders
}
});
})
Awesome. It even has a "Child" attribute describing the relation (as you would expect) but does not return the actual Child objects inside each Parent.
Now I need to expand on this method to retrieve the related Child objects at the same time - I really don't want to have to make another call per Parent to get the related Child objects.
So we try this:
.factory('Parent', function($resource, appConfig) {
return $resource('https://api.parse.com/1/classes/Parent/:id', {}, {
list : {
method : 'GET',
headers: appConfig.parseHttpsHeaders,
params : {
'include' : 'Child'
}
}
});
})
...and the server returns...the exact same response as before. The Child records are not included.
Any ideas where I'm going wrong?
Edit: Note that while I've set up the Parent and Child relation already, I'm open to suggestions if this is not the best way of doing things if I need to query the data in this way. There will be a limited set of Parent objects (let's say under 100) and a much smaller set of possible Child objects (under 30).
The problem is the distinction between pointers and relations. Pointers can be eagerly fetched using include, but relations cannot. If the cardinality of the parent->child relationship is low, use an array of pointers instead of a relation and you'll get the results you expect/desire.
With help from danh, I've worked out how to get this set up in the way I wanted. Here's a little code snippet:
var cId = 'CHILD_ID';
var pId = 'PARENT_ID';
var Child = Parse.Object.extend("child");
var cQ = new Parse.Query(Child);
var Parent = Parse.Object.extend("parent");
var pQ = new Parse.Query(Parent);
pQ.get(pId, {
success: function (mResponse) {
console.log(pResponse);
cQ.get(cId, {
success: function (cResponse) {
console.log(cResponse);
pResponse.add('children', cResponse);
pResponse.save();
},
error: function (object, error) {
// The object was not retrieved successfully.
// error is a Parse.Error with an error code and message.
}
});
},
error: function (object, error) {
// The object was not retrieved successfully.
// error is a Parse.Error with an error code and message.
}
});
First of all, create your two classes in the Parse.com data browser. Ensure that the Parent class has an Array column defined (in this case it was called 'children').
Add your parent object id and child object id to the relevant variables in the script and run it (obviously including your API keys at the top of the script). Hey presto! You will have an array of pointers for each relationship you run the script for.
Not only that, I can confirm that my AngularJS REST Api call returns the parent and child details in one call.
Related
I currently have three components of the same type. For example, I have a component called 'Lister' and I have three Lister components that calls different apis (e.g. listing teachers, students and courses). However, if a component that loads courses want to access data from the component that renders teachers then in this case, what should I do? I've seen few ways but they are all related to sharing information between components with other types (via $scope and $rootScope).
<lister data-ng-cloak api="/teacher" type="teacher"></lister>
<lister data-ng-cloak api="/student" type="student"></lister>
<lister data-ng-cloak api="/course" type="course"></lister>
This is the current way to use three components. They share common controller and service.
APIs may be different in your case but they have a common controller and service. So, declare three arrays to store teachers, students and courses in the service.
app.service('myService', function{
vm = this;
vm.teachers = [];
vm.students = [];
vm.courses = [];
});
have an api call to get the data.
vm.getApiData = $http.get(url).then(); // url may be to get above three from api
have a function in service to get the lists in to the controller
vm.getData: function(val){
return vm[val]; // value may be teachers, students and courses
}
Call the above function from the controller.
$scope.teachers = myService.getData('teachers');
$scope.students= myService.getData('students');
$scope.courses= myService.getData('courses');
Now, data is ready. Just access it based on your requirement. Just an idea
Create a parent object in controller
var parent={};
Then for all the listers assign value from this parent object so that all your data can be binded to one common object.
eg : parent.course=value;
Then they all be sharing data from one common object
In you controller create one object named as lister having properties for each different component :
$scope.lister = {
"teacherView": "teacher",
"studentView": "student",
"courseView": "course",
}
and when you call the setData method, receive the data to be stored and the key, lets say
componentService.setData(data, lister.teacherView)
and update your getData so you can choose which data you want to get like this :
componentService.getData(lister.teacherView)
You can create a service like this:
angular.module('app').service('componentService', function() {
var d = {};
function setData(data, key){
d[key] = data;
}
function getData(key){
return d[key];
}
})
.state('state1'
{
name :
url:
data:{
data1=[];
data2=[];
}
});
.state('state2'
{
name :
url:
data:{
data1=[];
data2=[];
}
});
Let us say I have n number of states.When user goes from one state to another
he is storing data into $state.current.data.
My question is , "is there any shortcut or any technique to reset the data for all the states again", let us say for the scenario when user logs out from the application , I want to reset the data .
I don't want to use $window.location.reload()
Any suggestions/help is appreciated!!
Thanks
You can use $state.get() to get all the configured states and then reinitialize the data object to a desired value.
Important: child states inherit the data property from their parent states - source. In order not to break the inheritance we'll use angular.copy.
This example reinitializes the data object with {}
angular.forEach($state.get(), function (state) {
angular.copy({}, state.data);
});
Note that there isn't any way to know the original value of the data object without explicitly specifying or storing it.
According to the Angularfire docs, when working with an object returned through $asObject(), you can set priority for said object by defining a $priority property on the object and then using $save().
My code works great, but $priority isn't doing anything. Here's some code with complete explanations in the comments:
app.factory('MyService', function($rootScope, $firebase) {
// The complete Firebase url
var ref = *obfuscated*;
// Returning the dataset as an object containing objects
var data = $firebase(ref).$asObject;
// This object is what's returned by MyService
var Data = {
// Method to create a new object within the data set,
// keyed by myId. Attempting to set priority for the
// record via $priority. returnData.uid is a valid string.
create: function(returnData, myId) {
data[myId] = {
myId: myId,
$priority: returnData.uid
};
// No need to explain the stuff between here and the
// $rootScope listener below, just added for context
data.$save().then(function() {
setMyId(myId);
});
},
findByMyId: function(myId) {
if (myId) {
return data[myId];
}
}
};
function setMyId(myId) {
$rootScope.myId = User.findByMyId(myId);
}
// This event listener works fine, fires
// at user login and returns data
$rootScope.$on('$firebaseSimpleLogin:login', function(e, returnData) {
// returnData.uid has the correct value - this
// function should return the object(aka record) with
// a priority matching returnData.uid
var query = $firebase(ref.startAt(returnData.uid).endAt(returnData.uid)).$asObject();
// console shows an object with the normal $firebase
// properties, but no records. If I define query without
// limiting the set ($firebase(ref).$asObject()), it returns
// the full set just fine. The priority on all records is still
// null.
console.log(query);
query.$loaded(function() {
setData(query.myId);
});
});
return Data;
});
Yes, I'm following Thinkster.io's tutorial and I'm in Chapter 7. No, this is not a duplicate of the other questions about that chapter, I already found my way around the pre-Angularfire 0.8 code present in their examples, just can't set $priority, and I've spent about 5 hours so far trying to find a solution through my own efforts and on the web.
Any takers?
When viewed in the light of how JavaScript works with objects (i.e. unordered), how JSON handles objects (i.e. unordered), and in light of the expectation that AngularFire's $asObject() method is intended for storing key/value pairs, and singular records that are not used as a collection, this starts to make some sense.
Internally, the synchronize'd object's $save method calls Firebase's setWithPriority. In set or setWithPriority calls, the child nodes are replaced. Any meta data like priorities on those children are replaced.
In AngularFire, $asArray is intended to handle ordered collections, and provides the ability to set $priority on child nodes (only one level deep, of course, as it treats its children as singular records that are not themselves collections).
Since, in your case, you want to work with fixed keys rather than push ids, you'll probably want to override the $add method using $extendFactory and do something like the following:
angular.module(MY_APP).factory('FixedKeysFactory', function($FirebaseArray, $firebaseUtils) {
return $FirebaseArray.$extendFactory({
$add: function(data) {
this._assertNotDestroyed('$add');
if( angular.isObject(data) && typeof data.$id === 'string' ) {
return this.$inst().$set(data.$id, $firebaseUtils.toJSON(data));
}
else {
return this.$inst().$push($firebaseUtils.toJSON(data));
}
}
});
});
You could then pass this into your $firebase instance in place of the default factory:
var list = $firebase(ref, {arrayFactory: FixedKeysFactory}).$asArray();
A simpler but less awesomatic™ solution would be to manually add your objects to the array, manually giving them a $id, then call $save:
var list = $firebase(ref).$asArray();
var i = list.length;
list.push({ foo: 'bar', $id: 'kato' });
list.$save(i);
Some notes on the future: It will soon be possible to use any field as sort criteria and there will be no need to set priorities (yay!). It will probably be possible to set your own $id before calling $add on a synchronized array in AngularFire as soon as I clear that with the other devs (like the 0.8.3 release).
I have a study, which has multiple cases and multiple executionMessages. Also each case has multiple executionSteps. I am able to access the study, the case and even each case's executionSteps. I cannot figure out why I cannot access the complete executionMessages. By that I meant each executionMessage has a type, message which are accessible but any objects inside executionMessage is not accessing. Here it the code
StudyService.studies.get({id: $routeParams.studyIdentifier}).$promise.then(function(study) {
$scope.study = study;
StudyService.executionMessagesForStudy.get({id: study.id}).$promise.then(function(executionMessages){
$scope.study.executionMessages = executionMessages;
});
for(var i=0;i<study.cases.length;i++){
var callback = callbackCreator(i);
StudyService.executionstepsForCase.get({id: $routeParams.studyIdentifier,caseId:study.cases[i].id})
.$promise.then(callback);
}
});
function callbackCreator(i) {
return function(executionSteps) {
$scope.study.cases[i].executionSteps = executionSteps;
}
}
It looks like you are using $resource for your service types (a guess at the .get().$promise pattern you are using), From the docs:
...invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data. This is a useful trick since usually the resource is assigned to a model which is then rendered by the view.
see: https://docs.angularjs.org/api/ngResource/service/$resource
So your code can be simplified like this:
// get() returns an object that will "fill-in" with the attributes when promise is resolved
$scope.study = StudyService.studies.get({id: $routeParams.studyIdentifier})
$scope.study.$promise.then(function(study) {
// again, executionMessages gets a placeholder that will be filled in on the resolved promise
$scope.study.executionMessages = StudyService.executionMessagesForStudy.get({id: study.id});
angular.forEach(study.cases, function(case, idx) {
// again, executionSteps is a placeholder that will be filled in on the resolved promise
var executionSteps = StudyService.executionstepsForCase.get({id: $routeParams.studyIdentifier, caseId: case.id})
// not sure if ordering is important, the service calls can return in any order
// so using push can produce list with elements in any order.
$scope.study.cases.push(executionSteps);
});
});
You still need to use the study.$promise to get the nested data after the first service call resolves.
I have been trying out backbone.js and have been stymied when I create a new model object then call model.save(). I am expecting the backbone.js default behavior to update the model object with the id from the database but it is not. Is this not supposed to happen? I have verified that I am getting a post with the attributes in json format. My server saves the json to a table and then returns the json with a new id field to backbone.js. Is this correct? Should my server return the entire new object or just the id or what?
//contents of the POST from backbone.js
{ "text":"this is a test" }
//reply from my server
{ id:"15", text:"this is a test" }
My sample code is below
var SQLRow = Backbone.Model.extend({
table:"",
urlRoot:'db',
url:function () {
return "/" + this.urlRoot + "?table=" + this.table +
"&id=" + this.attributes.id;
}
});
var Xtra = SQLRow.extend ({
table:'Xtra'
});
var row = new Xtra({
text: "this is a test"
});
alert(row.url());
row.save()
alert("row:" + row.get("id"));
Tough to tell from your post. Two ideas :
1) the response from the server isn't successful What does your save call return ?
2) Your "id" attribute is named something other than ID. To account for the different name add the following to your model :
idAttribute : "MyModelsID",
EDIT
You're likely facing a timing issue, where the alert fires before the ID has returned. Instead of your last two lines try this :
row.save( null,
{
success : function(model, response) { alert(model.get('id'); }
}
);
ALTERNATIVE
As #mu_is_too_short mentioned, another way is to listen for the change even on the model and respond to the event. (i was just trying to keep the answer as close to your code as possible). But something like the following pseudo code should get you started...
var myView = Backbone.View.extend({
....
initialize : function () {
this.collection.bind('change', this.SOME_LISTENING_FUNC );
}
});
OR, if you're in a collection/view-less world something like this creates a listenr ...
row.on('change', function() { /* do stuff */ }, this);
This answer is based on one comment of Cjolly in the answer above.
It is essential for making the Backbone.Model.save([attributes],[options]) successful in assiging the model with the newly generated model's id from the server, that the server returns the model's id in a JSON string like this { "id" : <the id> }. (note it is "id" and not id).
In essence backbone rightly expects a JSON string and in contrast to how objects may be defined in Javascript without quoted keys, JSON requires the object keys to be quoted (see JSON Spec - does the key have to be surrounded with quotes?)
Since according to Cjolly's comment this has been the essential problem, I want to hightlight this solution in an second answer. Partially because I was hit by the very same problem and only by reading througth the comments I was able to receive the insight.
I've faced the same issue and what I've found is that my validate function of the saved model actually invalidates the model returned from the back end. That's why my fields were not updated properly.
Maybe its a little outtimed, but today I had the same missing id.
It turns out, that the server just sends a Header 'Location' with a redirect containing the new id, but dosen't return the persisted object.
Adding the object to the response was the solution.
It seems, that not returning the object is standard behavier with Roo(Spring) generated Json-Controllers.