Properly wrap new domain object instance with $resource - angularjs

As a follow up to $resource to return array of [OO] objects how to properly wrap new domain object instances from $resource
app.factory('NoteResource', ['$resource', function($resource) {
var res = $resource('http://okigan.apiary.io/notes/:id', null, ...);
res.newInstance = function() {
return angular.extend(new Note(), res);
};
}
NoteResource.newInstance() shall return object with standard $save/$update methods.
Best to modify the plunker and watch traffic then "New note" button shall work correctly.

UPDATED
Here is a way you can continue incorporate your Note constructor into your $resource service...
Add a new instance method to the object returned from the factory:
res.getNew = function(){
var newNote = new Note();
newNote.id = undefined; // new note doesn't have id set
newNote.checked = undefined; // don't need this data passed to server?
angular.extend(newNote, res);
return newNote;
}
This method modifies an instantiated Note object (to remove ID and other data that doesn't need to be passed to the server when creating a new note) before merging it with the $resource service and returning that object to the caller.
Call that method from your controller assigning its return value to a local variable. You can also modify its properties from the controller:
var note = NoteResource.getNew();
note.title = 'Eat a lot of junk food';
Call the save method on the note object, passing itself as the sole parameter:
note.save(note).$promise.then(function() {
$scope.notes = NoteResource.query();
$scope.newNotes = [];
});
Watch in dev tools and notice that this does result in a JSON payload being sent with the POST containing your note properties (which your previous solution did not).
Hopefully this solution ticks off your box for enforcing a model without having to using a factory in an unintended manner.
Plunker Demo

Posting my solution for reference:
Latest demo: http://plnkr.co/edit/AVLQItPIfoLwsgDzoBdK?p=preview
Key item to the solution is to wrap returned json objects into javascript objects
with correctly set prototype.
var app = angular.module('plunker', ['ngResource']);
//
function Note() {
this.id = '<new id>';
this.title = '<new title>';
this.checked = false;
this.spellCheck = function() {
// imagine spellchecking logic here
this.checked = true;
};
}
app.factory('NoteResource', function($resource) {
var wrap = function(_) {
// forward declaration -- function redefined below
};
function extend(item) {
return angular.extend(new Note(), item);
}
var url = 'http://okigan.apiary.io/notes/:id';
var res = $resource(url, null, {
create: {
method: 'POST',
transformResponse: function(data, headersGetter) {
var item = angular.fromJson(data);
var headers = headersGetter();
// also handle HTTP 201 response
var extra = {
location: headers.location
};
var model = wrap(item);
angular.extend(model, extra);
return model;
}
},
query: {
method: 'GET',
isArray: true,
transformResponse: function(data, headersGetter) {
var items = angular.fromJson(data);
var models = [];
angular.forEach(items, function(item) {
models.push(wrap(item));
});
return models;
}
},
get: {
method: 'GET',
params: {
id: '#id'
},
transformResponse: function(data, headersGetter) {
var item = angular.fromJson(data);
var model = wrap(item);
return model;
}
}
});
res.url = url;
wrap = function(data) {
var T = Note;
// read up on http://javascript.crockford.com/prototypal.html
T.prototype = res.prototype;
var instance = new T();
angular.extend(instance, data);
return instance;
};
res.newModel = function() {
return wrap({});
};
return res;
});
app.controller('MainCtrl', function($scope, NoteResource) {
$scope.name = 'World';
$scope.notes = NoteResource.query();
$scope.newNotes = [];
$scope.spellCheckAllNotes = function() {
angular.forEach($scope.notes, function(note) {
note.spellCheck();
});
};
$scope.newNote = function() {
var note = NoteResource.newModel();
note.title = 'Buy cheese and bread for breakfast.';
$scope.newNotes.push(note);
note.$save().then(function() {
$scope.notes = NoteResource.query();
$scope.newNotes = [];
});
};
});

Related

Angular service/factory - class property not updating inside the cordova file transfer

I have Angular service that does an Cordova File Transfer upload within a mobile app - it uploads a file to media server and returns an object. I am trying to pass this object to the media_response property but i'm having no luck - what I am doing wrong?
Please note - I have tried using a service and factory and still seem to get no joy - anything within the $cordovaFileTransfer upload block isn't passed to the class properties for some bizarre reason.
I am expecting to see media_response with the same output as the data variable which is an object returned from the media server but it says no response
Any idea why I'm not getting the expected response?
// Expected response
UploadService.media_response in the controller should be an object to match the console.log(data)
// Actual response
UploadService.media_response is the string 'no response'
// Upload Service
abcdServices.service('UploadService', function($http, $localStorage, $location, $q, $rootScope, $cordovaFileTransfer) {
var UploadService = function() {
this.upload_in_progress = false;
this.progress = 0;
this.media_response = '';
};
UploadService.prototype.cordovaFileUpload = function (filename, url, targetPath) {
this.upload_in_progress = true;
this.media_response = 'no response';
var options = {
id: new Date() . getTime() + filename,
fileKey: "file",
fileName: filename,
chunkedMode: false,
mimeType: "multipart/form-data",
params : {'fileName': filename}
};
$cordovaFileTransfer.upload(url, targetPath, options).then(
function(result) {
// Success!
var data = JSON.parse(result.response);
console.log('file uploaded - response:', data); // ALWAYS A OBJECT
this.media_response = 'fake response2';
UploadService.media_response = 'fake response3';
if (angular.isDefined(data.id)) {
angular.forEach(data, function(value, key) {
// image[key] = value;
});
// $scope.post.linkThumbnail = false;
// $scope.post.video = '';
} else if (angular.isDefined(data.message)) {
// $scope.uploadError = data.message;
} else {
}
}, function(err) {
}, function (progress) {
// constant progress updates
this.progress = parseInt(100 * progress.loaded / progress.total) + '%';
}
);
};
return new UploadService();});
// controller
UploadService.cordovaFileUpload(filename, url, targetPath, options);
console.log(UploadService); // to view in js console
Your this. variables are scoped to the nearest function block, not the service/factory.
Create your variables in the root of your service/factory with either var or let.
abcdServices.service('UploadService', function($http, $localStorage, $location, $q, $rootScope, $cordovaFileTransfer) {
let upload_in_progress = false;
let progress = 0;
let media_response = '';
var UploadService = function() {
upload_in_progress = false;
progress = 0;
media_response = '';
};
UploadService.prototype.cordovaFileUpload = function (filename, url, targetPath) {
upload_in_progress = true;
media_response = 'no response';
// Rest of your code
};
return new UploadService();});
If you really need/want to use this, use the new function arrow notation, which does not create a new this-scope in the function block.
abcdServices.service('UploadService', function(...) {
this.upload_in_progress = 'test';
this.progress = 0;
this.media_response = '';
someFunction = (param1, param2, ..) => {
console.log(this.upload_in_progress) // 'test'
}
});

How to load data from a factory service before using in application

This is an ASP.NET MVC app with AngularJS.
When the application loads, we have to call some action method which returns a dictionary of resources, string key string value.
This array/dictionary of resources, needs to be available throughout the application.
How can we wait until these resources are loaded before accessing them within the application?
var app = angular.module("app", []);
app.controller("TestCtrl", ['cacheService', function (cacheService) {
var self = this;
self.test = function () {
var value = cacheService.getResourceValue('Err_lbl_UserExist');
}
}]);
app.factory('cacheService', ['$http', function ($http) {
var obj = {};
obj.resourceDictionary = [];
obj.loadResourceDictionary = function () {
var httpConfig = {
url: "/Cache/GetResourceDictionary",
method: "GET",
headers: {
"X-Requested-With": 'XMLHttpRequest',
"__RequestVerificationToken": $("[name=__RequestVerificationToken]").val()
}
}
$http(httpConfig)
.success(function (data) {
obj.resourceDictionary = data;
});
}
obj.getResourceValue = function (resourceKeyName) {
if (obj.resourceDictionary.length <= 0) {
obj.loadResourceDictionary();
}
return obj.resourceDictionary[resourceKeyName];
}
return obj;
}]);
EDIT w/ Accepted Answer
var app = angular.module("app", []);
app.controller("TestCtrl", ['cacheService', function (cacheService) {
var self = this;
self.test = function () {
var value = cacheService.getResourceValue('Err_lbl_UserExist');
}
}]);
app.factory('cacheService', ['$rootScope', '$http', function ($rootScope, $http, $q) {
var obj = { resourcesLoaded: false };
obj.loadResourceDictionary = function () {
obj.resourcesLoaded = false;
var httpConfig = {
url: "Cache/GetResourceDictionary",
method: "GET",
headers: {
"X-Requested-With": 'XMLHttpRequest',
"__RequestVerificationToken": $("[name=__RequestVerificationToken]").val()
}
}
$http(httpConfig).success(function (data) {
obj.resourceDictionary = data;
obj.resourcesLoaded = true;
$rootScope.$broadcast("ResourcesLoaded", null);
});
}
obj.getResourceValue = function (resourceKeyName) {
if (!obj.resourcesLoaded) {
obj.loadResourceDictionary();
$rootScope.$on("ResourcesLoaded", function () {
return obj.resourceDictionary[resourceKeyName];
});
} else {
return obj.resourceDictionary[resourceKeyName];
}
}
return obj;
}]);
you could use broadcast and on for that.
So once your keys are loaded you fire an event using broadcast
https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$broadcast
you listen for that message wherever you need to using on :
https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$on
you can store the data in a service, this will make it a singleton and you can reuse it, all you have to do is inject the service in whatever controller you need.

How to I refresh a JSON object, so it is not too long

org.springframework.http.converter.HTTPMEssageNotReadableException: Could not read JSON: Unexpected end-of-input in VALUE_STRING>
I believe my JSON object may be too long. It has 60 different id's. I want to delete them all, and start a fresh. I used angualrjs $http.put to get them there. I am using angularjs with a ModelSvc. I do not see the id listed in the data.
While bankInfoes/(id) returns the data, but localhost:9001/bankInfoes does not return any data.
This is the angularjs that I use to control the view.
timetrackingServices.factory('BankInfoSvc', ['$resource', '$rootScope', 'RootUrlSvc', 'ModelSvc', '$http',
function ($resource, $rootScope, RootUrlSvc, ModelSvc, $http) {
var initializeModel = function (bankInfo) {
var bankInfoResource = $resource(RootUrlSvc.rootUrls.bankInfoes + ':bankInfoId', {}, {
query: {method: 'GET', isArray: false}
});
bankInfoResource.query(function (data) {
var bankInfoes = data;
ModelSvc.model.bankInfoes = bankInfoes;
});
};
return {
initializeModel: initializeModel
};
}
]);
timetrackingServices.factory('ModelSvc', [
function () {
var model = {};
model.company = {};
model.bankinfoes = {};
model.printBossResponses = {};
model.config = {};
var isCompanyInitialized = function () {
return !angular.equals({}, model.company)
};
var isBankInfoInitialized = function () {
return !angular.equals({}, model.bankInfo)
};
var isPrintBossResponsesInitialized = function () {
return !angular.equals({}, model.printBossResponse)
};
return {
model: model,
isCompanyInitialized: isCompanyInitialized,
isBankInfoInitialized : isBankInfoInitialized,
isPrintBossResponsesInitialized : isPrintBossResponsesInitialized
}
}]);
#RequestMapping(value="/billPaymentOuts", method=RequestMethod.GET)
public List<BillPaymentOut> displayBillPaymentOutPage() {
...
return lList;
}

a service to provide Restangular objects

I'm trying to make a service that will load persons from the server on demand. The first version looked like this:
services.factory('PersonServiceOld', function(Restangular, ErrorService) {
var persons = [];
var requesting = [];
var get = function(id) {
if (requesting[id]) {
return persons[id];
}
requesting[id] = true;
persons[id] = {'id' : id, 'photoName' : '0.png'};
Restangular.one('persons', id).get().then(function(success) {
persons[id].firstName = success.firstName;
persons[id].lastName = success.lastName;
persons[id].photoName = success.photoName;
}, function(failure) {
requesting[id] = false;
ErrorService.serverError(failure);
});
return persons[id];
};
var reset = function() {
persons = [];
requesting = [];
};
return {
getPerson : get,
clearCache : reset,
};
});
That way I get a reference to an object right away and it will be filled with data slightly after. It worked well... until I noticed that in another use case, I also want to request the address of a person like
var person = PersonService.get(id);
person.one(address).get().then(.......
but the objects returned from my PersonService aren't Restangular objects. So I tried something else:
services.factory('PersonService', function(Restangular, ErrorService) {
var persons = [];
var get = function(id) {
if (!persons[id]) {
persons[id] = Restangular.one('persons', id);
persons[id].get().then(function(success) {
}, function(failure) {
ErrorService.serverError(failure);
persons[id] = null;
});
}
return persons[id]; // also tried: persons[id].$object
};
return {
getPerson : get
};
});
I hope somebody understands what I'm trying to do here and can give me a good pointer on how to achieve this.
Check this Plunkr for a complete example.
As Restangular returns promises, and your get function may be asynchronous or synchronous (in case you use your own cache), you need to create a promise for returning always the same type of object.
You can do it as described in the Angular documentation for $q service.
So your get function may look like :
var get = function (id) {
var deferred = $q.defer();
if (store[id]) {
deferred.resolve(store[id]);
} else {
Restangular.one('person', id).get().then(function (res) {
store[res.id] = res;
deferred.resolve(res);
}, function (err) {
deferred.reject(err);
});
}
return deferred.promise;
}
Then in your controller, for retrieving your data :
PersonService.get(475).then(function (person) {
// stuff
}, function (err) {
// err handling
});

how to return a factory object after all ajax calls are done

I have a factory which returns an object with several properties. But each property value is computed by ajax call and in some cases I do promise chaining in order to set a property value. Before I return the object how do i make sure all ajax calls are done such that property values are assigned
My factory looks something like this
app.factory('Resource', ['$http', '$q', function ($http, $q) {
var Resource = {
masterDB: null,
replicaDB: null,
replicaCluster: null,
masterForests: null,
forestHosts:{}
};
Resource.setMasterDB = function (dbname) {
console.log('inside setMasterDB', dbname);
this.masterDB = dbname;
};
Resource.getResources = function (dbname) {
var url = '/manage/v2/databases/'+ dbname + '?format=json';
$http.get(url).then(function (response) {
Resource.masterForests = getAttachedForests(response.data);
console.warn('Master Forests = ', Resource.masterForests);
return response;
}).then(function (response) {
Resource.replicaCluster = getReplicaClusters(response.data);
console.warn('Replica Cluster = ',Resource.replicaCluster);
}).then(function () {
console.log('final then', Resource.masterForests);
var reqs = function () {
var arr = [];
angular.forEach(Resource.masterForests, function(forestName){
arr.push($http.get('/manage/v2/forests/'+ forestName + '?format=json'));
});
return arr;
}.call();
console.log('reqs = ', reqs);
$q.all(reqs).then(function (results) {
console.warn(results);
angular.forEach(results, function(result){
console.log('HOST', getForestHost(result.data));
});
return results;
});
});
};
console.warn('RESOURCES: ', JSON.stringify(Resource));
return Resource;
}]);
We had scenario where the datas have to be updated from two different ajax responses, we have followed the below approach.
For Example:
function functionname()
{
var First_Response = $http.$get("/test1/test2/...");
var Second_Response = $http.$get("test3/test4/...");
return $q.all([First_Response,Second_Response]); // This function will return only when all the ajax responses are obtained.
}
In the below url:
https://docs.angularjs.org/api/ng/service/$q
It is mentioned that $q.all will return result only when all the requests mentioned in the array gets the ajax response.
We tried this approach and it worked for us. Hopefully it will give some pointers
Here's the working code
getResources: function (dbname) {
var url = '/manage/v2/databases/'+ dbname + '?format=json';
return $http.get(url).then(function (response) {
Resource.masterForests = getAttachedForests(response.data);
Resource.appservers = getAttachedAppServers(response.data);
Resource.replicaClusters = getReplicaClusters(response.data);
console.warn('Master Forests = ', Resource.masterForests);
return Resource.replicaClusters;
}).then(function(replicaClusters) {
var clusterArr = [];
angular.forEach(replicaClusters, function(cluster) {
clusterArr.push($http.get('/manage/v2/clusters/'+ cluster + '?format=json'));
});
return $q.all(clusterArr).then(function(results) {
angular.forEach(results, function(result) {
var cluster = result.data['foreign-cluster-default'].name;
var dbs = getReplicaDBs(result.data);
Resource.replicaDBs[cluster] = dbs;
});
});
}).then(function() {
var forestarr = [];
angular.forEach(Resource.masterForests, function(forestName){
forestarr.push($http.get('/manage/v2/forests/'+ forestName + '?format=json'));
});
return $q.all(forestarr).then(function (results) {
angular.forEach(results, function(result){
var host = getForestHost(result.data);
var forest = result.data['forest-default'].name;
Resource.forestHosts.push(host);
// group forest by hosts
groupForestsByHost(Resource, host, forest);
});
});
});
}
In controller
Resource.getResources($scope.db).then(function() {
$scope.masterForests = Resource.masterForests;
$scope.replicaClusters = Resource.replicaClusters;
$scope.forestHosts = Resource.forestHosts;
$scope.forestsOnHosts = Resource.forestsOnHosts;
$scope.replicaDBs = Resource.replicaDBs;
$scope.appservers = Resource.appservers;
}

Resources