Adding a function to $resource prototype - javascript

I'm trying to add a view function to an Angular $resource. I add it to the $resource via prototype but for some reason the 'this' reference in the prototype function is incorrect so all of the properties are undefined. Strangely though in console.log the this seems to have all the properties it would need to return correctly.
http://plnkr.co/edit/YsTlAztjEKjn3piQAem2?p=preview
app.factory("Now", function($resource) {
var Now = $resource("http://date.jsontest.com/");
Now.prototype.$dateTime = function() {
console.log("2", this); // this has date and time properties, good
return this.date + " " + this.time;
};
return Now;
});
app.controller("TestController", function(Now) {
var now = new Now();
now.$get();
console.log("1", now); // prototype has the $dateTime function!
console.log("3", now.$dateTime()); // but it returns undefined, bad
});

Actually, your error is that your calling $dateTime before the resource returns with the data.
See this plunk
The only reason the answer above me appears to work is it is being interpolated, and when the resource finally returns, the datetime function is called AGAIN. But if you were to leave the code the same, it would still fail

You're attempting to use the properties of this while the model is still being fetched from the server which is resulting in those properties being undefined when you ask for them. You need to use the promise methods that are available on the object returned from $get in order to guarantee that the request has finished.
This
now.$get();
Should become this
now.$get().then(function() {
console.log("1", now);
console.log("3", now.$dateTime());
});

Your error is in using .factory. It expects an object to be returned, but $resource is a function. Change it to .service and it will work
app.service("now", function($resource) {
complete code: http://plnkr.co/edit/n31MMNTlKV3i04HdXUms?p=catalogue
var app = angular.module("test", ["ngResource"]);
app.service("now", function($resource) {
var now = $resource("http://date.jsontest.com/");
now.prototype.dateTime = function() {
return this.date + " " + this.time;
};
return now;
});
app.controller("TestController", function($scope, now) {
$scope.now = now.get();
});

Related

Where is angular "data" coming from

--- Given this code in a file ---
angular.module('storyCtrl', ['storyService'])
.controller('StoryController', function(Story, socketio) {
var vm = this;
Story.getStory()
.success(function(data) {
vm.stories = data;
});
vm.createStory = function() {
vm.message = '';
var newMessage = vm.storyData.content;
var newStory = { str: newMessage , timeNow: new Date(), mess: "Hello" };
Story.createStory(newStory)
.success(function(data) {
vm.storyData = '';
vm.message = data.message;
});
};
socketio.on('story', function(data) {
vm.stories.push(data);
})
})
Where does "data" being initialize or where is it coming from as it is not even a global variable or from ['storyService'].
Thank you
The variable data represents what the function (getStory or createStory or the on function) is returning to you for use in the function. For example, the getStory function might be returning a json array. Within the success function, this data is assigned to the vm.stories variable.
Does that help?
The storyCtrl module references another module called storyService. Slightly confusing, the storyService module contains a service (or factory) called Story. The Story service provides a method called getStory. Internally getStory very likely makes a call using $http. You can tell because getStory does not use standard promises, but instead uses the success and error methods that $http provides.

Factory has two methods, one is $http method with $promise - how to reference the other method from within the first one's success function?

In one of my factories I need to set a variable when data is fetched (through $http) so I can access it in my controller (the intention is to display a spinner image until the data is loaded).
.factory('LoadData', function LoadData($http, $q){
return {
getMyData: function(){
return $http(
// implementation of the call
).then(
function(response){
var myData = response.data;
// this should be reference to the other method (getLoadStatus)
// where I want to change its value to "true"
// this doesn't work - "this" or "self" don't work either because we're in another function
LoadData.getLoadStatus.status = true;
}
);
},
// by calling the below method from my controller,
// I want to mark the completion of data fetching
getLoadStatus = function(){
var status = null;
return status;
}
}
}
I hope you got the idea - how could this be accomplished? Also, I'm open to any suggestions which are aimed towards a better approach (I want to pick up best practice whenever possible).
Status is essentially a private variable; use it as:
.factory('LoadData', function LoadData($http, $q){
var status = null; // ESSENTIALLY PRIVATE TO THE SERVICE
return {
getMyData: function(){
return $http(...).then(function(response){
...
status = true;
});
},
getLoadStatus = function(){
return status;
}
};
})
There are several ways.
Here's one which I prefer to use:
.factory('LoadData', function LoadData($http, $q){
var status = false;
var service = {
getMyData: getMyData,
status: status
};
return service;
function getMyData() {
return $http(
// implementation of the call
).then(
function(response){
var myData = response.data;
status = true;
}
);
}
}
This provides good encapsulation of your methods and gives you a clean interface to export. No need for the getter method if you don't want it.
Inspiration via John Papa's Angular style guide (found here).
You could simply store variable flag in closure:
.factory('LoadData', function LoadData($http, $q) {
var status = false;
return {
getMyData: function() {
status = false;
return $http(/* implementation of the call */).then(function(response) {
status = true;
return response.data;
});
},
getLoadStatus: function() {
return status;
}
}
});
Also if getMyData loads fresh data every time, it's important to reset status to false before each request.
I actually decide to use a promise in my controller after calling this service and when data is returned, I am simply making the status true. It seems that is best practice to have a promise returned when calling a service so I should be good.

Can't pass around returned object from server

I am picking up a project started by a peer, and having trouble with an API call.
This is my angular controller:
angular.module('oowli').factory('Note', ['$http', function($http) {
var Note = function(data) {
angular.extend(this, data);
};
Note.prototype.save = function() {
var note = this;
return $http.post('/notes', this).then(function(response) {
note = response.data;
}, function(error) {
return error;
});
};
return Note;
}]);
and the call is executed in this function:
var saveNote = function(Note, scope){
return function(span, phrase){
var noteObj = new Note({
user: scope.global.user._id,
content: span.innerText
});
noteObj.save().then(function(){
console.log(noteObj);
});
};
};
problem is, after saving the note, noteObj is the original, not the one that comes back from the server (with an _id field).
Debugging on the Note.prototype.save, response.data comes with the _id;
I need to know how to have access to the returned Note object, in the saveNote function.
You are assigning the new object to the local variable 'note' in the Note.prototype.save function, but then not doing anything with it.
The quickest solution is to copy the properties of the returned object to your note rather than assign to it, so instead of:
note = response.data;
This might work:
angular.copy(response.data, note)
In general though I don't like the approach of having the Note class responsible for saving itself. I would create a note service that saves the object.

Modifying Angular Factory Object from inside a function

This is the function that I am working with to call my factory
var myService = function($http) {
return {
bf: null,
initialize: function() {
this.promise = $http.get(this.server + "/requestkey").success(function(data) {
myService.bf = new Blowfish(data.key);
});
}
}
And I am creating this object using
TicTacTorrent.service('AService', ['$http', myService]);
However, when calling AService.initialize() it creates the promise object like it should, but it doesn't update the BF object. I'm confused as to how to update the bf object to be the new value. How would I reference myService.bf since this.bf would create a local instance for .success function?
Try this:
var myService = function($http) {
this.bf = null;
return {
bf: this.bf,
initialize: function() {
this.promise = $http.get(this.server + "/requestkey").success(function(data) {
myService.bf = new Blowfish(data.key);
});
}
}
Where do you want to initialize?
Have you seen the $provider example code?
Search for "provider(name, provider)" and check if it suits your need.
Otherwise I'm unsure what the code you'vew written will run like.
I usually write factories like this:
angular.module('app').factory('myService', ['$http', function($http) {
var publicObj = {};
publicObj.bf = ""; // Just to make sure its initialized correctly.
publicObj.initialize = function() {snip/snap... myService.bf = new Blowfish(data.key);};
return publicObj;
}]);
The difference might be that you previous code returned an inline anonymous object which might have a hard time referring to itself. But by that logic it should work by just making myService return a predeclared var and returning that.

AngularJS : Asynchronously initialize filter

I'm having trouble trying to initialize a filter with asynchronous data.
The filter is very simple, it needs to translate paths to name, but to do so it needs a correspondance array, which I need to fetch from the server.
I could do things in the filter definition, before returning the function, but the asynchronous aspect prevents that
angular.module('angularApp').
filter('pathToName', function(Service){
// Do some things here
return function(input){
return input+'!'
}
}
Using a promise may be viable but I don't have any clear understanding on how angular loads filters.
This post explains how to achieve such magic with services, but is it possible to do the same for filters?
And if anyone has a better idea on how to translate those paths, I'm all ears.
EDIT:
I tried with the promise approch, but something isn't right, and I fail to see what:
angular.module('angularApp').filter('pathToName', function($q, Service){
var deferred = $q.defer();
var promise = deferred.promise;
Service.getCorresp().then(function(success){
deferred.resolve(success.data);
}, function(error){
deferred.reject();
});
return function(input){
return promise.then(
function(corresp){
if(corresp.hasOwnProperty(input))
return corresp[input];
else
return input;
}
)
};
});
I'm not really familliar with promises, is it the right way to use them?
Here is an example:
app.filter("testf", function($timeout) {
var data = null, // DATA RECEIVED ASYNCHRONOUSLY AND CACHED HERE
serviceInvoked = false;
function realFilter(value) { // REAL FILTER LOGIC
return ...;
}
return function(value) { // FILTER WRAPPER TO COPE WITH ASYNCHRONICITY
if( data === null ) {
if( !serviceInvoked ) {
serviceInvoked = true;
// CALL THE SERVICE THAT FETCHES THE DATA HERE
callService.then(function(result) {
data = result;
});
}
return "-"; // PLACEHOLDER WHILE LOADING, COULD BE EMPTY
}
else return realFilter(value);
}
});
This fiddle is a demonstration using timeouts instead of services.
EDIT: As per the comment of sgimeno, extra care must be taken for not calling the service more than once. See the serviceInvoked changes in the code above and the fiddles. See also forked fiddle with Angular 1.2.1 and a button to change the value and trigger digest cycles: forked fiddle
EDIT 2: As per the comment of Miha Eržen, this solution does no logner work for Angular 1.3. The solution is almost trivial though, using the $stateful filter flag, documented here under "Stateful filters", and the necessary forked fiddle.
Do note that this solution would hurt performance, as the filter is called each digest cycle. The performance degradation could be negligible or not, depending on the specific case.
Let's start with understanding why the original code doesn't work. I've simplified the original question a bit to make it more clear:
angular.module('angularApp').filter('pathToName', function(Service) {
return function(input) {
return Service.getCorresp().then(function(response) {
return response;
});
});
}
Basically, the filter calls an async function that returns the promise, then returns its value. A filter in angular expects you to return a value that can be easily printed, e.g string or number. However, in this case, even though it seems like we're returning the response of getCorresp, we are actually returning a new promise - The return value of any then() or catch() function is a promise.
Angular is trying to convert a promise object to a string via casting, getting nothing sensible in return and displays an empty string.
So what we need to do is, return a temporary string value and change it asynchroniously, like so:
JSFiddle
HTML:
<div ng-app="app" ng-controller="TestCtrl">
<div>{{'WelcomeTo' | translate}}</div>
<div>{{'GoodBye' | translate}}</div>
</div>
Javascript:
app.filter("translate", function($timeout, translationService) {
var isWaiting = false;
var translations = null;
function myFilter(input) {
var translationValue = "Loading...";
if(translations)
{
translationValue = translations[input];
} else {
if(isWaiting === false) {
isWaiting = true;
translationService.getTranslation(input).then(function(translationData) {
console.log("GetTranslation done");
translations = translationData;
isWaiting = false;
});
}
}
return translationValue;
};
return myFilter;
});
Everytime Angular tries to execute the filter, it would check if the translations were fetched already and if they weren't, it would return the "Loading..." value. We also use the isWaiting value to prevent calling the service more than once.
The example above works fine for Angular 1.2, however, among the changes in Angular 1.3, there is a performance improvement that changes the behavior of filters. Previously the filter function was called every digest cycle. Since 1.3, however, it only calls the filter if the value was changed, in our last sample, it would never call the filter again - 'WelcomeTo' would never change.
Luckily the fix is very simple, you'd just need to add to the filter the following:
JSFiddle
myFilter.$stateful = true;
Finally, while dealing with this issue, I had another problem - I needed to use a filter to get async values that could change - Specifically, I needed to fetch translations for a single language, but once the user changed the language, I needed to fetch a new language set. Doing that, proved a bit more tricky, though the concept is the same. This is that code:
JSFiddle
var app = angular.module("app",[]);
debugger;
app.controller("TestCtrl", function($scope, translationService) {
$scope.changeLanguage = function() {
translationService.currentLanguage = "ru";
}
});
app.service("translationService", function($timeout) {
var self = this;
var translations = {"en": {"WelcomeTo": "Welcome!!", "GoodBye": "BYE"},
"ru": {"WelcomeTo": "POZHALUSTA!!", "GoodBye": "DOSVIDANYA"} };
this.currentLanguage = "en";
this.getTranslation = function(placeholder) {
return $timeout(function() {
return translations[self.currentLanguage][placeholder];
}, 2000);
}
})
app.filter("translate", function($timeout, translationService) {
// Sample object: {"en": {"WelcomeTo": {translation: "Welcome!!", processing: false } } }
var translated = {};
var isWaiting = false;
myFilter.$stateful = true;
function myFilter(input) {
if(!translated[translationService.currentLanguage]) {
translated[translationService.currentLanguage] = {}
}
var currentLanguageData = translated[translationService.currentLanguage];
if(!currentLanguageData[input]) {
currentLanguageData[input] = { translation: "", processing: false };
}
var translationData = currentLanguageData[input];
if(!translationData.translation && translationData.processing === false)
{
translationData.processing = true;
translationService.getTranslation(input).then(function(translation) {
console.log("GetTranslation done");
translationData.translation = translation;
translationData.processing = false;
});
}
var translation = translationData.translation;
console.log("Translation for language: '" + translationService.currentLanguage + "'. translation = " + translation);
return translation;
};
return myFilter;
});

Categories