Recommended way of getting data from the server - angularjs

What is the recommended way to connect to server data sources in AngularJS without using $resource.
The $resource has many limitations such as:
Not using proper futures
Not being flexible enough

There are cases when $resource may not be appropriate when talking to backend. This shows how to set up $resource like behavior without using resource.
angular.module('myApp').factory('Book', function($http) {
// Book is a class which we can use for retrieving and
// updating data on the server
var Book = function(data) {
angular.extend(this, data);
}
// a static method to retrieve Book by ID
Book.get = function(id) {
return $http.get('/Book/' + id).then(function(response) {
return new Book(response.data);
});
};
// an instance method to create a new Book
Book.prototype.create = function() {
var book = this;
return $http.post('/Book/', book).then(function(response) {
book.id = response.data.id;
return book;
});
}
return Book;
});
Then inside your controller you can:
var AppController = function(Book) {
// to create a Book
var book = new Book();
book.name = 'AngularJS in nutshell';
book.create();
// to retrieve a book
var bookPromise = Book.get(123);
bookPromise.then(function(b) {
book = b;
});
};

I recommend that you use $resource.
It may support (url override) in next version of Angularjs.
Then you will be able to code like this:
// need to register as a serviceName
$resource('/user/:userId', {userId:'#id'}, {
'customActionName': {
url:'/user/someURI'
method:'GET',
params: {
param1: '....',
param2: '....',
}
},
....
});
And return callbacks can be handled in ctrl scope like this.
// ctrl scope
serviceName.customActionName ({
paramName:'param',
...
},
function (resp) {
//handle return callback
},
function (error) {
//handler error callback
});
Probably you can handle code on higher abstraction level.

Related

How to return widgetDefinitions with data service in malhar-angular-dashboard?

I have installed the malhar-angular-dashboard module. I do not want to hard code my widgetDefinitions, so I created a service witch will return the array with the widget objects. I am using a rest service to return data such as the widget names, titles, etc. The problem is I get this error and do not know how to resolve :
TypeError: widgetDefs.map is not a function
SERVICE DATA
.factory('widgetRestService',['$http','UrlService','$log','$q',
function($http,UrlService,$log,$q){
var serviceInstance = {};
serviceInstance.getInfo = function(){
var request = $http({method: 'GET', url: '/rest/widgets/getListInfoDashboards'})
.then(function(success){
serviceInstance.widgets = success.data;
$log.debug('serviceInstance.widgets SUCCESS',serviceInstance.widgets);
},function(error){
$log.debug('Error ', error);
$log.debug('serviceInstance.widgets ERROR',serviceInstance.widgets);
});
return request;
};
serviceInstance.getAllWidgets = function () {
if (serviceInstance.widgets) {
return serviceInstance.widgets;
} else {
return [];
}
};
return serviceInstance;
}]);
widgetDefinitions service
.factory('widgetDefinitions',['widgetRestService','$log','$q','$http',function(widgetRestService,$log,$q,$http) {
var widgetDefinitions = [];
return widgetRestService.getInfo().then(function (data) {
var widgets = widgetRestService.getAllWidgets();
$log.debug('widgetsDefs ', widgets);
for (var i = 0; i < widgets.length; i++) {
widgetDefinitions.push(widgets[i]);
}
$log.debug('widgetDefinitions ', widgetDefinitions);
return widgetDefinitions;
});
});
Console
TypeError: widgetDefs.map is not a function
widgetDefs: [Object,Object,Object]
widgetDefinitions: [Object,Object,Object]
Note
If I hard-code my widgetDefinitions-service returned array like this it works, if I returned with my rest service doesn`t work(widgetDefs.map is not a function):
[
{
name:'widgetList',
title:'title1'
},
{
name:'widgetPie',
title:'title2'
},
{
name:'widgetTable',
title:'title3'
}
]
Original issue in github has been commented on and closed.
Here's the comment from github:
#pitong Make sure the dashboard options contains a property named widgetDefinitions and it's an array, even if it's empty. Also make sure the the browser version you're using supports the map function for arrays. This map function for array became the ECMA-262 standard in the 5th edition so your browser version might not have it supported.

Firebase - Filter by a column

I'm using Firebase for my Angular.js application.
I'm looking for the equivalent of SQL's WHERE statement for Firebase.
I have an array of TV series stored in Firebase, and I want to fetch only these that has the name that the user entered (in the example searchQuery).
Does Firebase support it? Does it have something like this?
var seriesRef = new Firebase('http://{app}.firebaseio.com/series');
var seriesObject = $firebaseObject(seriesRef.query({ name: searchQuery }));
I have some suggestions that may help here:
Check out the Firebase Query documentation. Specifically,
.orderByChild()
.equalTo()
You can use queries in conjunction with .$ref() to get the desired record.
Example
Check out this working CodePen demo.
I replicated your data in one of my public Firebase instances.
The query that you're looking for is seriesCollectionRef.orderByChild('name').equalTo(seriesName)
If you enter 'Avatar: The Last Airbender' in the input and click "Find", you'll get the matching series object.
In my example, I extended the $firebaseArray service to include a method for finding a specific series by name.
See the documentation for extending AngularFire services.
You can accomplish the same thing without extending the service, see last code snippet.
Factories
app.factory('SeriesFactory', function(SeriesArrayFactory, fbUrl) {
return function() {
const ref = new Firebase(`${fbUrl}/series`);
return new SeriesArrayFactory(ref);
}
});
app.factory('SeriesArrayFactory', function($firebaseArray, $q) {
return $firebaseArray.$extend({
findSeries: function(seriesName) {
const deferred = $q.defer();
// query by 'name'
this.$ref()
.orderByChild('name')
.equalTo(seriesName)
.once('value', function(dataSnapshot) {
if (dataSnapshot.exists()) {
const value = dataSnapshot.val();
deferred.resolve(value);
} else {
deferred.reject('Not found');
}
})
return deferred.promise;
}
});
});
Controller
app.controller('HomeController',function($scope, SeriesFactory, fbUrl) {
$scope.seriesName = '';
$scope.findSeries = function() {
const seriesCollection = new SeriesFactory();
seriesCollection
.findSeries($scope.seriesName)
.then(function(data) {
$scope.series = data;
})
.catch(function(error) {
console.error(error);
});
};
});
Without Extended Service
Here is what a controller function would look like if you weren't using the factories:
$scope.findSeriesWithoutFactory = function() {
const seriesRef = new Firebase(`${fbUrl}/series`);
const seriesCollection = $firebaseArray(seriesRef);
seriesCollection.$ref()
.orderByChild('name')
.equalTo($scope.seriesName)
.once('value', function(dataSnapshot) {
if (dataSnapshot.exists()){
$scope.series = dataSnapshot.val();
} else {
console.error('Not found.');
}
});
};
Rules
Note: It's important to note that you should add ".indexOn": "name" to your Firebase rules so that the query runs efficiently. See the Indexing Your Data portion of the Firebase Security & Rules Guide for more information. Below is an example:
"yourfirebaseapp": {
".read": "...",
".write": "...",
"series": {
".indexOn": "name"
}
}

Angular.js, $resource, how to manage multiple asynchronous requests

I am implementing a "copy on write" CRUD system meaning i never overwrite a database entry but mark as inactive and write a new record. When editing an existing record this means i write to the old record deactivating then create a new record. My controller code is below:
$scope.save = function() {
if(!$scope.newDevice){
var editDevice = $scope.device;
$scope.delete(editDevice);
$scope.device = {name: editDevice.name, type: editDevice.type, hash: editDevice.hash};
}
var newDevice = new DeviceService($scope.device);
newDevice = newDevice.$save(function(newDevice, putResponseHeaders) {
DeviceService.query({active : true}, function(devices){
$scope.devices = devices;
});
});
};
When i call to get the list of active devices with DeviceService.query({active : true} I still get the old record as active since it executes and returns before the delete method has been processed and returned.
I think i should be using promise maybe. How do i write this code to be better and work?
thanks
Yes, you want to use promises. You have two options:
Use the success/failure callbacks that all $resource methods supply. Note you're using this when you call $save. You could do the same when you call $delete on the resource, so that your remaining code only executes when the $delete() succeeds. These callbacks are automatically invoked when the $resource's built-in promise is resolved or rejected.
Make your $scope.delete() function return a promise. It sounds like this might be better, because you do not always want to make the delete request.
The code for #2 might look like this:
// this function use the '$q' service, which you need to inject
// in your controller
$scope.delete = function(item) {
var deferred = $q.defer();
item.$delete({},
function(response) {
// the delete succeeded, resolve the promise
deferred.resolve(response);
},
function(error) {
// failed, reject the promise
deferred.reject(error);
}
);
return deferred.promise;
}
$scope.save = function() {
if(!$scope.newDevice){
var editDevice = $scope.device;
$scope.delete(editDevice).then(function(response) {
$scope.device = {name: editDevice.name, type: editDevice.type, hash: editDevice.hash};
// now trigger the code to save the new device (or whatever)
$scope.doTheActualSave();
},
function(error) { });
} else {
// there was nothing to delete, just trigger the code to save
$scope.doTheActualSave();
}
};
$scope.doTheActualSave = function() {
var newDevice = new DeviceService($scope.device);
newDevice = newDevice.$save(function(newDevice, putResponseHeaders) {
DeviceService.query({active : true}, function(devices){
$scope.devices = devices;
});
});
}

AngularJS - What would this look like had it been created TDD style?

I'm in the process of transferring all of our code onto Karma and Jasmine and am having a hard time figuring out where I start.
What would this code look like had I started building it from a TDD standpoint? What does a simple test look like?
Note: This code works 100%, but I don't have any tests setup.
(function() {
"use strict";
angular.module('system_centers', [
'system'
])
.factory('System', ['Api', function(Api) {
this.loadSystem = function(contactId, cardId) {
return Api.get('lmc/contact/system/' + contactId, {
card_id: cardId
});
};
this.completeSystem = function(recordId) {
return Api.put('system/complete/' + recordId);
};
this.createSystem = function(contactId, cardId) {
if (+contactId === 0 || +cardId === 0) {
return false;
}
return Api.post('contact/system/' + contactId, {
card_id: cardId,
type: 'systems',
origin: 'lmc'
});
};
return this;
}])
.controller('System_centersCtrl', ['$scope', 'System', function($scope, System) {
$scope.main.cardType = 'systems';
$scope.main.type = 'system_centers';
$scope.completeSystem = function(recordId) {
System.completeSystem(recordId).success(function(){
toastr.success("System completed!");
$scope.createSystem();
$scope.loadSystems();
});
};
$scope.createSystem = function() {
System.createSystem($scope.main.contactId, $scope.main.cardId).success(function() {
$scope.loadSystem($scope.main.contactId, $scope.main.cardId);
$scope.loadContacts();
});
};
$scope.loadSystem = function() {
System.loadSystem($scope.main.contactId, $scope.main.cardId).success(function(data) {
if (data.error) {
$scope.createSystem();
} else {
$scope.main.record = data.record;
}
});
};
$scope.loadSystems();
}]);
})();
Testing is easy, you just need to assert that your factory is working correctly. This doesn't mean that you want actually get/put/post stuff, that belongs to the Api test. Here we just want to know that calling certain functions of our factory will call some Api functions with the correct parameters.
I imagine that Api belongs to the system module. I load it and mock it:
beforeEach(module('system', function($provide) {
api = {
get: function(url, params) {},
put: function(url, params) {},
post: function(url, params) {}
};
spyOn(api, 'get');
spyOn(api, 'put');
spyOn(api, 'post');
$provide.value('Api', api);
}));
module will load your system module and then we just need to create a simple object with the interface of our Api service. No need to implement anything on them.
Then we just need to spy the methods (to be able to assert that they have been called).
Next, we load the system_centers module and we inject our services:
beforeEach(module('system_centers'));
beforeEach(inject(function(System) {
system = System;
}));
inject is used to inject dependencies in our tests. We just need to inject our System factory.
What rest are the test, I created a bunch of them:
it('should load the system', function() {
system.loadSystem(1, 0);
expect(api.get).toHaveBeenCalledWith('lmc/contact/system/1', {card_id : 0});
});
it('should be able to complete the system', function() {
system.completeSystem(20);
expect(api.put).toHaveBeenCalledWith('system/complete/20');
});
it('should create the system', function() {
system.createSystem(1, 3);
expect(api.post).toHaveBeenCalledWith('contact/system/1', { card_id: 3, type: 'systems', origin: 'lmc'});
});
it('should not create the system if contact_id is 0', function() {
system.createSystem(0, 20);
expect(api.post).not.toHaveBeenCalled();
});
it('should not create the system if card_id is 0', function() {
system.createSystem(1, 0);
expect(api.post).not.toHaveBeenCalled();
});
They are much the same. We call some factory method and we expect that our Api has been called with some parameters. Or even that calling createSystem with contact or card id with 0 won't call the Api.
Well, this is a good head start. You can continue with more tests or with other parts of your application.
Here is the plunker: http://plnkr.co/edit/5vfg0Y1G0vo2nnz0xByN?p=preview

Proper place for data-saving logic in AngularJS

App design question. I have a project which has a very large number of highly customized inputs. Each input is implemented as a directive (and Angular has made this an absolute joy to develop).
The inputs save their data upon blur, so there's no form to submit. That's been working great.
Each input has an attribute called "saveable" which drives another directive which is shared by all these input types. the Saveable directive uses a $resource to post data back to the API.
My question is, should this logic be in a directive at all? I initially put it there because I thought I would need the saving logic in multiple controllers, but it turns out they're really happening in the same one. Also, I read somewhere (lost the reference) that the directive is a bad place to put API logic.
Additionally, I need to introduce unit testing for this saving logic soon, and testing controllers seems much more straightforward than testing directives.
Thanks in advance; Angular's documentation may be… iffy… but the folks in the community are mega-rad.
[edit] a non-functional, simplified look at what I'm doing:
<input ng-model="question.value" some-input-type-directive saveable ng-blur="saveModel(question)">
.directive('saveable', ['savingService', function(savingService) {
return {
restrict: 'A',
link: function(scope) {
scope.saveModel = function(question) {
savingService.somethingOrOther.save(
{id: question.id, answer: question.value},
function(response, getResponseHeaders) {
// a bunch of post-processing
}
);
}
}
}
}])
No, I don't think the directive should be calling $http. I would create a service (using the factory in Angular) OR (preferably) a model. When it is in a model, I prefer to use the $resource service to define my model "classes". Then, I abstract the $http/REST code into a nice, active model.
The typical answer for this is that you should use a service for this purpose. Here's some general information about this: http://docs.angularjs.org/guide/dev_guide.services.understanding_services
Here is a plunk with code modeled after your own starting example:
Example code:
var app = angular.module('savingServiceDemo', []);
app.service('savingService', function() {
return {
somethingOrOther: {
save: function(obj, callback) {
console.log('Saved:');
console.dir(obj);
callback(obj, {});
}
}
};
});
app.directive('saveable', ['savingService', function(savingService) {
return {
restrict: 'A',
link: function(scope) {
scope.saveModel = function(question) {
savingService.somethingOrOther.save(
{
id: question.id,
answer: question.value
},
function(response, getResponseHeaders) {
// a bunch of post-processing
}
);
}
}
};
}]);
app.controller('questionController', ['$scope', function($scope) {
$scope.question = {
question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
id: 1,
value: ''
};
}]);
The relevant HTML markup:
<body ng-controller="questionController">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="saveModel(question)" />
</body>
An alternative using only factory and the existing ngResource service:
However, you could also utilize factory and ngResource in a way that would let you reuse some of the common "saving logic", while still giving you the ability to provide variation for distinct types of objects / data that you wish to save or query. And, this way still results in just a single instantiation of the saver for your specific object type.
Example using MongoLab collections
I've done something like this to make it easier to use MongoLab collections.
Here's a plunk.
The gist of the idea is this snippet:
var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
var apiKey = "YOUR API KEY";
var collections = [
"user",
"question",
"like"
];
for(var i = 0; i < collections.length; i++) {
var collectionName = collections[i];
app.factory(collectionName, ['$resource', function($resource) {
var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
var svc = new resourceConstructor();
// modify behavior if you want to override defaults
return svc;
}]);
}
Notes:
dbUrl and apiKey would be, of course, specific to your own MongoLab info
The array in this case is a group of distinct collections that you want individual ngResource-derived instances of
There is a createResource function defined (which you can see in the plunk and in the code below) that actually handles creating a constructor with an ngResource prototype.
If you wanted, you could modify the svc instance to vary its behavior by collection type
When you blur the input field, this will invoke the dummy consoleLog function and just write some debug info to the console for illustration purposes.
This also prints the number of times the createResource function itself was called, as a way to demonstrate that, even though there are actually two controllers, questionController and questionController2 asking for the same injections, the factories get called only 3 times in total.
Note: updateSafe is a function I like to use with MongoLab that allows you to apply a partial update, basically a PATCH. Otherwise, if you only send a few properties, the entire document will get overwritten with ONLY those properties! No good!
Full code:
HTML:
<body>
<div ng-controller="questionController">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
</div>
<div ng-controller="questionController2">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
</div>
</body>
JavaScript:
(function() {
var app = angular.module('savingServiceDemo', ['ngResource']);
var numberOfTimesCreateResourceGetsInvokedShouldStopAt3 = 0;
function createResource(resourceService, resourcePath, resourceName, apiKey) {
numberOfTimesCreateResourceGetsInvokedShouldStopAt3++;
var resource = resourceService(resourcePath + '/' + resourceName + '/:id',
{
apiKey: apiKey
},
{
update:
{
method: 'PUT'
}
}
);
resource.prototype.consoleLog = function (val, cb) {
console.log("The numberOfTimesCreateResourceGetsInvokedShouldStopAt3 counter is at: " + numberOfTimesCreateResourceGetsInvokedShouldStopAt3);
console.log('Logging:');
console.log(val);
console.log('this =');
console.log(this);
if (cb) {
cb();
}
};
resource.prototype.update = function (cb) {
return resource.update({
id: this._id.$oid
},
angular.extend({}, this, {
_id: undefined
}), cb);
};
resource.prototype.updateSafe = function (patch, cb) {
resource.get({id:this._id.$oid}, function(obj) {
for(var prop in patch) {
obj[prop] = patch[prop];
}
obj.update(cb);
});
};
resource.prototype.destroy = function (cb) {
return resource.remove({
id: this._id.$oid
}, cb);
};
return resource;
}
var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
var apiKey = "YOUR API KEY";
var collections = [
"user",
"question",
"like"
];
for(var i = 0; i < collections.length; i++) {
var collectionName = collections[i];
app.factory(collectionName, ['$resource', function($resource) {
var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
var svc = new resourceConstructor();
// modify behavior if you want to override defaults
return svc;
}]);
}
app.controller('questionController', ['$scope', 'user', 'question', 'like',
function($scope, user, question, like) {
$scope.question = {
question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
id: 1,
value: ''
};
$scope.save = function(obj) {
question.consoleLog(obj, function() {
console.log('And, I got called back');
});
};
}]);
app.controller('questionController2', ['$scope', 'user', 'question', 'like',
function($scope, user, question, like) {
$scope.question = {
question: 'What is the coolest JS framework of them all?',
id: 1,
value: ''
};
$scope.save = function(obj) {
question.consoleLog(obj, function() {
console.log('You better have said AngularJS');
});
};
}]);
})();
In general, things related to the UI belong in a directive, things related to the binding of input and output (either from the user or from the server) belong in a controller, and things related to the business/application logic belong in a service (of some variety). I've found this separation leads to very clean code for my part.

Resources