I have an AngularJS factory method that is setting properties like this (specifically in the getPageInformation method):
function factoryMethod($http, $q) {
return function(id) {
return {
id: id,
getPageInformation: function() {
var deferred = $q.defer();
$http({
method: "post",
url: 'getInfo.php',
data: {
id: this.id
},
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}).then(function(successResponse) {
console.log(successResponse);
deferred.resolve('Fetched');
for (var attr in successResponse.data) {
this[attr] = successResponse.data[attr];
}
}, function(errorResponse) {
console.log(errorResponse);
deferred.reject('Unable to fetch');
});
return deferred.promise;
}
}
}
}
The problem is not with AngularJS, as you can see I am returning and object (from the curly braces syntax), but I need to be able to dynamically set the object properties. I did a loop through the appropriate info I got from the response, but it isn't setting it:
for (var attr in successResponse.data) {
this[attr] = successResponse.data[attr];
}
Instead, I think the problem is that (when I console.loged this it brought me the entire browser window object), this is failing to refer to the current instance. Is there any I can achieve what I'm doing or is this how it should be working and there is another problem?
The meaning of the this variable in Javascript is a pain. You can try searching online for the gory details. For this question I believe it suffices to say that this in callbacks is most probably not what you intuitively expect. You need to create a closure with the real this, commonly done as:
return {
...
getPageInformation: function() {
var self = this; // The closed variable here
...
$http({...})
.then(function(successResponse) {
...
for (var attr in successResponse.data) {
self[attr] = successResponse.data[attr]; // Use like this
}
...
Related
I have this javascript object:
return {
AccDocs: {
query: function() {
...
},
deleteAndQuery: function() {
...
AccDocs.query(); //Error: AccDocs is not defined
}
}
}
But, it returns an error that says AccDocs is not defined.
How can I achieve something like this?
Variables and properties on objects are different things. You cannot access the property of an object without specifying which object you mean.
You can probably access it using the this keyword:
this.query();
Keeping in mind that the value of this will vary depending on how the function is called (when a.b.c.d.AccDocs.deleteAndQuery() is called, this inside deleteAndQuery will be AccDocs as it is the first object to the left of the last ., but if you were to first copy query to another variable and then call query(), pass it to setTimeout, or if you were to use call or apply then the value of this would change).
For more robustness (but less flexibility, since being able to change the context can be useful) you can store your object in a variable which you can access by name.
var AccDocs = {
query: function() {
...
},
deleteAndQuery: function() {
...
AccDocs.query();
}
};
return { AccDocs: AccDocs };
By using the this keyword:
return {
AccDocs: {
query: function() {
...
},
deleteAndQuery: function() {
...
this.query(); //Here
}
}
}
I am trying to pass data from directive to controller via service, my service looks like this:
angular
.module('App')
.factory('WizardDataService', WizardDataService);
WizardDataService.$inject = [];
function WizardDataService() {
var wizardFormData = {};
var setWizardData = function (newFormData) {
console.log("wizardFormData: " + JSON.stringify(wizardFormData));
wizardFormData = newFormData;
};
var getWizardData = function () {
return wizardFormData;
};
var resetWizardData = function () {
//To be called when the data stored needs to be discarded
wizardFormData = {};
};
return {
setWizardData: setWizardData,
getWizardData: getWizardData,
resetWizardData: resetWizardData
};
}
But when I try to get data from controller it is not resolved (I think it waits digest loop to finish), So I have to use $timeout function in my controller to wait until it is finished, like this:
$timeout(function(){
//any code in here will automatically have an apply run afterwards
vm.getStoredData = WizardDataService.getWizardData();
$scope.$watchCollection(function () {
console.log("getStoredData callback: " + JSON.stringify(vm.getStoredData));
return vm.getStoredData;
}, function () {
});
}, 300);
Despite of the fact that it works, what I am interested in is, if there is a better way to do this, also if this is bug free and the main question, why we use 300 delay and not 100 (for example) for $timeout and if it always will work (maybe for someone it took more time than 300 to get data from the service).
You can return a promise from your service get method. Then in your controller, you can provide a success method to assign the results. Your service would look like this:
function getWizardData() {
var deferred = $q.defer();
$http.get("/myserver/getWizardData")
.then(function (results) {
deferred.resolve(results.data);
}),
function () {
deferred.reject();
}
return deferred.promise;
}
And in your ng-controller you call your service:
wizardService.getWizardData()
.then(function (results) {
$scope.myData = results;
},
function () { });
No timeouts necessary. If your server is RESTFULL, then use $resource and bind directly.
Use angular.copy to replace the data without changing the object reference.
function WizardDataService() {
var wizardFormData = {};
var setWizardData = function (newFormData) {
console.log("wizardFormData: " + JSON.stringify(wizardFormData));
angular.copy(newFormData, wizardFormData);
};
From the Docs:
angular.copy
Creates a deep copy of source, which should be an object or an array.
If a destination is provided, all of its elements (for arrays) or properties (for objects) are deleted and then all elements/properties from the source are copied to it.
Usage
angular.copy(source, [destination]);
-- AngularJS angular.copy API Reference
This way the object reference remains the same and any clients that have that reference will get updated. There is no need to fetch a new object reference on every update.
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.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
javascript object, access variable property name?
I'm trying to call a method of an object by its name when certain event happens - here's my code:
var imageObject = {
sendImage : function(obj) {
"use strict";
var thisId = obj.attr('id');
var thisSubmit = obj.attr('data-submit');
var thisCallback = obj.attr('data-callback');
var thisUrl = $('#' + obj.attr('data-url')).text();
new AjaxUpload(thisId, {
action: thisUrl,
name: 'thisfile',
onSubmit: imageObject.thisSubmit,
onComplete: imageObject.thisCallback
});
}
}
I would also like to pass the parameters to the submit and callback method.
At the moment nothing happens and I'm sure I'm not calling it right - could anyone explain how to achieve this?
I've also used the following for submit and complete :
onSubmit: function(file, extension) {
imageObject.thisSubmit(file, extension);
},
onComplete: function(file, response) {
imageObject.thisCallback(file, response);
}
But what I get is the error saying :
imageObject.thisSubmit is not a function
The attributes only store the name (string) of the method within the object - so it would be for instance 'uploadImage'.
TJ's answer is nearly there, except that (per the new code in your question) you need to either copy the arguments from onSubmit to thisSubmit, or use .apply() to copy them for you.
The code below uses the latter method, which avoids having to duplicate the function signature over and over:
new AjaxUpload(thisId, {
action: thisUrl,
name: 'thisfile',
onSubmit: function() {
imageObject[thisSubmit].apply(imageObject, arguments);
},
onComplete: function() {
imageObject[thisCallback].apply(imageObject, arguments);
}
});
If I've understood your question correctly, you need to use the square bracket syntax to access the properties:
new AjaxUpload(thisId, {
action: thisUrl,
name: 'thisfile',
onSubmit: function () { //Anonymous function so you can pass arguments
imageObject[thisSubmit]("myArg"); //Square brackets here
},
onComplete: imageObject[thisCallback] //Square brackets here (no arguments)
});
I'm assuming the thisSubmit and thisCallback local variables are meant to be the names of the functions that exist on imageObject.
Do both call those methods using names from those strings, and pass in arguments, you use a combination of bracketed syntax and closures:
var imageObject = {
sendImage : function(obj) {
"use strict";
var thisId = obj.attr('id');
var thisSubmit = obj.attr('data-submit');
var thisCallback = obj.attr('data-callback');
var thisUrl = $('#' + obj.attr('data-url')).text();
new AjaxUpload(thisId, {
action: thisUrl,
name: 'thisfile',
onSubmit: function() {
imageObject[thisSubmit](arg, anotherArg, etc);
},
onComplete: function() {
imageObject[thisCallback](arg, anotherArg, etc);
}
});
}
// Presumably there are more methods here, or added later...
}
you have to use call which call a function in js
onSubmit: function(file, extension) {
imageObject.thisSubmit.call(undefined, file,extension);
},
see What is the difference between call and apply?
I have a JavaScript class named 'Item'. 'Item' is defined as shown here:
function Item() { this.create(); }
Item.prototype = {
create: function () {
this.data = {
id: getNewID(),
}
},
save: function() {
$.ajax({
url: getBackendUrl(),
type: "POST",
data: JSON.stringify(this.data),
contentType: "application/json",
success: save_Succeeded,
error: save_Failed
});
},
function save_Succeeded(result) {
// Signal an event here that other JavaScript code can subscribe to.
}
function save_Failed(e1, e2, e3) {
// Signal an event here that other JavaScript code can subscript to.
}
}
Please note, I'm coming from a C# background. So I'm not even sure if what I want to accomplish is possible. But essentially, I want to create an object, subscribe to some event handlers, and attempt to save my object. For instance, I envision doing something like the following throughout my code.
var i = new Item();
i.item_save_succeeded = function() {
// Do stuff when the item has successfully saved
};
i.item_save_failed = function() {
// Do stuff when the item has failed to save
};
i.save(); // start the save process
Is this event-based approach even possible in JavaScript? If so, how? What am I missing? I keep getting a variety of errors that are vague. Because of that, I'm not sure if I'm getting closer or farther away.
If you are using jQuery, you can add an event handler to a custom event type.
The following snippet is taken from the jQuery docs
$('#foo').bind('custom', function(event, param1, param2) {
alert(param1 + "\n" + param2);
});
$('#foo').trigger('custom', ['Custom', 'Event']);
But since jQuery 1.7 deprecates bind, you should use on now. See the jQuery docs for on.
Not 100% sure and I look forward to seeing the answer from a JS pro, but here is what I would do.
Expose some properties within you Item object - namely the functions you wish to be subscribed to.
Upon instancing an item you could then provide callback functions for the events that you wish to be notified of. In your code you could then do something like this:
save: function() {
var self = this;
$.ajax({
url: getBackendUrl(),
type: "POST",
data: JSON.stringify(this.data),
contentType: "application/json",
success: function() { if(typeof(self.success) == "function") self.success(); }
error: function() { if(typeof(self.fail) == "function") self.fail(); }
});
},
In effect, pass the callback functions to the object and let it call them directly when needed. I'm sure someone will now suggest a better way of doing it. :-)