AngularJS nested functions in services and dependency injection? - angularjs

Ok I have two modules which depend upon each other both modules have services, directives, ctrl's etc, now my question is how do i get values assigned in the nested function of the second module's service in the controller of the first service, I have added the dependencies to the first controller but i can't see to get at the nested functions variables to then manipulate them in the ctrl of the first module here's the code(considerably cut down):
angular.module("mainapp", [
"dateSheet",
"bookingApp"
]).controller("AppCtrl", [
"$scope",
"$attrs",
"Booking",
function (scope, source, attributes, AppDataLoader, booking, Booking) {
//HERE I NEED TO BE ABLE TO DO SOMETHING LIKE THIS
var getdaiyrate = function(){
var dumpDailyRates = scope.Booking.getalldates.getrates.dailyPrice
console.log(dumpDailyRates);
}
}
]);
angular.module("bookingApp", ["bookingApp.services",]);
angular.module("bookingApp.services").service("Booking", [
function(){
function getRate(source, dateSheet, dateSheetCtrl, expect, $$childTail, appData) {
var dateValue = $("Date", source).text() || "";
if (!dateValue) {
return null;
}
var dailyPrice = $("DailyPrice", source).text() || "";
var weeklyPrice = $("WeeklyPrice", source).text() || "";
var monthlyPrice = $("MonthlyPrice", source).text() || "";
var isAvailable = $("IsAvailable", source).text() === "1";
var minimumStay = Number($("MinimumStay", source).text());
if (isNaN(minimumStay)) {
minimumStay = DEFAULT_MINIMUM_STAY;
}
return {
date: new Date(dateValue),
dailyPrice: dailyPrice,
weeklyPrice: weeklyPrice,
monthlyPrice: monthlyPrice,
reserved: !isAvailable,
minimumStay: minimumStay
};
}
return {
getalldates: function(source, $scope){
return getRate(source, scope);
}
};
}
]);
The above doesn't work what am i doing wrong....
Could someone please send me in the direction of a decent tutorial that deals with a end to end app using various modules and dependencies??
Chris

You need to inject the service module into the module that you want to use it in. So the first line becomes
angular.module("mainapp", ["dateSheet","bookingApp","bookingApp.services"])
Also i don't see the creation of bookingApp.services so this may also be required
angular.module("bookingApp.services",[]);
and the invocation would be something like this
var dumpDailyRates = Booking.getalldates(sourceParameter, $scope);

Related

Rerender angular-datatables when switching language with angular-translate

I use angular-translate with angular-datatables and implemented a language switch between german and english (Explained here Switching between languages. Switching language works well but not with angular-datatables. When i switch angular-datatables keeps the old translations for the table header.
angular-datatables Controller:
It's loading the datatables data via json with a promise and then draws the table. It also refreshes the table every 5 minutes. I implemented a public function "rerenderTable" which i call when switching the app language.
.controller('DashboardCtrl', ['$scope', '$rootScope', 'DTOptionsBuilder', 'DTColumnBuilder', 'DTInstances', '$resource',
'$interval', '$translate',
function($scope, $rootScope, DTOptionsBuilder, DTColumnBuilder, DTInstances, $resource, $interval, $translate)
{
$scope.initTargetPackaging = function initTargetPackaging()
{
$scope.sourceUrl = 'json/v1.0.0/dashboard/datatables/myjson1.json';
};
$scope.initTargetConversion = function initTargetConversion()
{
$scope.sourceUrl = 'json/v1.0.0/dashboard/datatables/myjson2.json';
};
// Get the TargetPackaging JSON Data with promise AJAX call
var vm = this;
vm.dtOptions = DTOptionsBuilder.fromFnPromise( function()
{
return $resource($scope.sourceUrl).query().$promise;
})
.withOption('bInfo', false)
.withOption('paging', false)
.withOption('filter', false)
.withOption('rowCallback', rowCallback);
// Create the table columns
vm.dtColumns = [
DTColumnBuilder.newColumn('customer')
.withTitle($translate('DIRECTIVES.DASHBOARD.DATATALBE_TARGET_PACKAGING_COLUMN_CUSTOMER')),
DTColumnBuilder.newColumn('today')
.withTitle($translate('DIRECTIVES.DASHBOARD.DATATALBE_TARGET_PACKAGING_COLUMN_TODAY')),
DTColumnBuilder.newColumn('week')
.withTitle($translate('DIRECTIVES.DASHBOARD.DATATALBE_TARGET_PACKAGING_COLUMN_7DAYS')),
DTColumnBuilder.newColumn('month')
.withTitle($translate('DIRECTIVES.DASHBOARD.DATATALBE_TARGET_PACKAGING_COLUMN_30DAYS'))
];
vm.newPromise = newPromise;
vm.reloadData = reloadData;
vm.dtInstance = {};
function newPromise()
{
return $resource($scope.sourceUrl).query().$promise;
}
/**
* Reload the data
*/
function reloadData()
{
var resetPaging = false;
vm.dtInstance.reloadData(resetPaging);
}
// Trigger reloading - 5 mins
$interval(reloadData, 300000);
function rowCallback(nRow, aData)
{
// Add status CSS class if state is true
if (aData['state'] != undefined
&& aData['state'] === true)
{
$(nRow).addClass('ad-status-inactive');
}
}
$rootScope.rerenderTable = function()
{
vm.dtInstance.rerender();
};
}]);
Function to switch language:
$scope.changeLang = function(key)
{
$translate.use(key).then( function(key)
{
console.log("Sprache zu " + key + " gewechselt.");
$rootScope.rerenderTable();
},
function(key)
{
// Trigger log error message (failure of switching language)
});
};
Triggered here in html:
<div id="language-switch" class="col-xs-2" ng-controller="LanguageCtrl">
<div class="inner">
{{ 'MAIN_NAVIGATION.CHOOSE_LANGUAGE' | translate }}
<span ng-click="changeLang('en')" class="lang-sm" lang="en"></span>
<span ng-click="changeLang('de')" class="lang-sm" lang="de"></span>
</div>
</div>
Summary: Switching languages works well. But not in the case of angular-datatables. It does not switch the language. But translating the strings is fine.
How do i get angular-datatables to rerender the table by using the currently chosen language?
1- Listen to the language change to render the table afterwards.
$rootScope.$on('$translateChangeEnd', function (event, lang) {
$scope.dtInstance.rerender();
});
2-Inside constructor function of your table
var headerCallback = function( thead, data, start, end, display ) {
$compile(angular.element(thead).contents())($scope);
}
3-
$scope.dtOptions(your name) = DTOptionsBuilder
.newOptions()
.withOption('headerCallback', headerCallback)
..........your code
$scope.dtColumns = [
DTColumnBuilder.newColumn('code').withTitle(`${'<span translate>'}${'TAG'}${'</span>'}`).renderWith(your_code).withClass('center-text'),
.........
Works for me ;)
A little bit late but here is an answer, which is not the best imho:
$rootScope.$on('$translateChangeSuccess', function (event, lang) {
vm.dtOptions.withLanguageSource('http://cdn.datatables.net/plug-ins/1.10.11/i18n/'+(lang.language == 'de' ? 'German' : 'English')+'.json');
$rootScope.rerenderTable();
});
It's a shame, they doesn't provide language files named like the ISO-codes. So you have to convert them into the english "long" language names.

AngularJS Generic List Controller

So I didn't get an answer from my last question so I decided to handle this myself.
I created a generic controller like this:
.controller('GenericListController', function () {
// Define this
var self = this;
// Define our list
self.list = [];
// Create our page sizes array
self.pageSizes = [10, 20, 50, 100];
// For filtering and sorting the table
self.pageSize = self.pageSizes[0];
self.predicate = 'name';
self.reverse = false;
self.filter = '';
// For deleting
self.delete = function (e, model) {
// Delete the item
service.delete(model.id);
};
});
very simple as you can see.
Now I was using this by injecting it into my controller like this:
.controller('DashboardController', ['GenericListController', 'CenterService', 'companyId', 'centers', function (Controller, service, companyId, centers) {
// Assign this to a variable
var self = Controller;
}])
In theory everything that is assigned to the GenericListController is now available to the DashboardController. The problem is the line in the generic controller that looks like this:
service.delete(model.id);
Somehow I need to reference my service in the generic controller. I thought that maybe I could create a provider and inject the service reference into the constructor but I am not sure if it being a singleton is an issue, so I need some help.
Is a service / factory / provider a good way to build the GenericListController?
Does a service / factory being a singleton affect anything? If so, can they be created so they are not singletons?
Is there another way to achieve what I am after?
Update 1
So it appears some people are confused....
So if I created a factory that looks like this:
.factory('ListControllerService', function () {
// Default constructor expecting a service
return function (service) {
// Define this
var self = this;
// Define our list
self.list = [];
// Create our page sizes array
self.pageSizes = [10, 20, 50, 100];
// For filtering and sorting the table
self.pageSize = self.pageSizes[0];
self.predicate = 'name';
self.reverse = false;
self.filter = '';
// For deleting
self.delete = function (e, model) {
// Delete the item
service.delete(model.id);
};
};
})
then I create 2 separate controllers that looks like this:
.controller('DashboardController', ['ControllerService', 'CenterService', 'companyId', 'centers', function (Controller, service, companyId, centers) {
// Assign this to a variable
var self = new Controller(service);
self.list = centers;
}])
.controller('CompanyController', ['ControllerService', 'CompanyService', 'ArrayService', 'companies', function (Controller, service, arrayService, centers) {
// Assign this to a variable
var self = new Controller(service);
self.list = companies;
}])
Hopefully you can see that the service I am injecting into the ListControllerService is different for each controller. The only caveat I have with my example is that each "service" must have a delete method (not so difficult because they are all api services).
I hope that explains things better.
The solution I am using on my current project is to write a function that registers a factory.
function CreateMyGenericFactory(factoryName, serviceName)
app.factory(factoryName, [serviceName, function (service){
var GenericListFactory = {
list: [],
pageSizes: [10, 20, 50, 100],
predicate: 'name',
reverse: false,
filter: ''
delete: function(e, model){
service.delete(model.id);
}
}
GenericListFactory.pageSize = GenericListFactory.pageSizes[0];
return GenericListFactory;
}]);
}
Then execute the function in your JS to register a new factory dynamically.
CreateMyGenericFactory('ListFactory', 'ListService');
And use it in your controller.
app.controller('GenericListController', ['ListFactory', function (ListFactory) {
...
console.log(ListFactory.pageSizes.length); // -> 4
ListFactory.delete(e, model);
}];
I also registered the service inside my CreateMyGenericFactory function to further reduce duplication, you may consider doing that as well if each factory has its own service.
The final solution looked like this.
function CreateMyGenericFactory(name)
var factoryName = name + 'Factory'; // e.g. listFactory
var serviceName = name + 'Service'; // e.g. listService
app.factory(factoryName, [serviceName, function (service){
var factory = {
list: [],
pageSizes: [10, 20, 50, 100],
predicate: 'name',
reverse: false,
filter: ''
delete: function(e, model){
service.delete(model.id);
}
}
factory.pageSize = factory.pageSizes[0];
return factory;
}]);
app.service(serviceName, ['$resource', function (resource){
return $resource(Modus[backend] + '/api/'+slug+'s/:id', {
id: '#_id'
});
}]);
}
That way I could register all the Factories and Services I needed without duplicating any code.
CreateMyGenericFactory('user');
// this registered userFactory and userService
Well, it looks like I was nearly there.
I have managed to solve this but I am not sure if it is the best way to do it, but it certainly works.
So my service is very similar to before:
.factory('ListControllerService', function () {
// Default constructor expecting a service
return function (ctrl, service) {
// Define this
var self = ctrl;
// Define our list
self.list = [];
// Create our page sizes array
self.pageSizes = [10, 20, 50, 100];
// For filtering and sorting the table
self.pageSize = self.pageSizes[0];
self.predicate = 'name';
self.reverse = false;
self.filter = '';
// For deleting
self.delete = function (e, model) {
// Delete the item
service.delete(model.id);
};
return self;
};
})
The only thing that changes is that instead of:
self = this;
I now do:
self = ctrl;
ctrl is the controller that is inheriting from this service.
I also return self so that I can bind it to self in the inherited controller.
now my controller looks like this:
.controller('DashboardController', ['ListControllerService', 'CenterService', 'companyId', 'centers', function (Controller, service, companyId, centers) {
// Assign this to a variable
var self = new Controller(this, service);
console.log(this);
console.log(self);
// Assign our centers
self.list = centers;
}])
both console.log output the same which is great.

AngularJS Service to Service with a delay

I am currently facing a problem with my project's design.
I am using angularjs framework and my task is to provide a translations for a webpage, but the translations need to be provided form the xml file o the BE side.
So since I#ve found out that angulars i18n is configurable on the FE side i had to use another strategy.
I've decided to make a service which fetches the data during a resolve period before everything else is loaded:
app.factory('dictionaryService', ['$http', '$rootScope', function ($http, $rootScope) {
return {
getDictionary: function (defaultLanguage) {
var chosenLanguage = null;
if (angular.isUndefined($rootScope.defaultLanguage) || $rootScope.defaultLanguage == null) {
chosenLanguage = defaultLanguage;
$rootScope.defaultLanguage = chosenLanguage;
} else {
chosenLanguage = $rootScope.defaultLanguage;
}
var translation = new Array();
translation[chosenLanguage] = new Array();
return $http.get('Translation/GetCurrentDictionary/', {
params: {
language: chosenLanguage
}
});
},
GetLanguagesSetup: function () {
return $http.get('Translation/GetLanguagesSetup/');
}
}
}]);
and then resolve it as follows:
$routeProvider.when("/diagnose", {
controller: "diagnoseCtrl",
templateUrl: "/app/views/diagnose.html",
resolve: {
startupData: function (dictionaryService, $q) {
var def = $q.defer();
var translation = new Array();
var startupData = new Array();
var defaultLanguage = "EN";
var dict = dictionaryService.getDictionary(defaultLanguage).then(function (JSONData) {
var keys = Object.keys(JSONData.data.data);
var chosenLanguage = JSONData.data.lang;
translation[chosenLanguage] = {};
for (i = 0; i < keys.length; i++) {
translation[keys[i]] = JSONData.data.data[keys[i]];
}
startupData['translations'] = translation;
def.resolve(startupData);
}).catch(function (e) {
console.log("Translation fetching exception, " + e);
return $q.reject(e);
});
return def.promise;
}
}
});
So as you can see I am storing my fetched translations in a startupData. Then in a controller which is using it I am assigning this data to the $rootScope. It seems already here as a not the best solution, but I could not come up with a different one
Then I have created a translation service which gets the direct translation text:
app.factory('translationService', ['$rootScope', '$http', function ($rootScope, $http) {
var translations = null;
return {
getText: function (key) {
if ($rootScope.cachedTranslations == undefined) {
return key;
}
var result = $rootScope.cachedTranslations[key];
if (result == null) {
return key;
} else {
return result;
}
}
}
}]);
The biggest problem with this solution is, that I am not using promises, but I do not want to make an http query to BE for each translation.
The other problem is with the html template provided by the designers:
<body ng-controller="mainController">
<loading-screen ng-show="!isDataLoaded"></loading-screen>
<div id="header" class="headerView" ng-controller="headerController" ng-show="isDataLoaded">
some header stuff
...
<button ng-bind="option1" ng-click="redirectTo('#subpage1')"></button>
<button ng-bind="option2" ng-click="redirectTo('#subpage2')"></button>
<button ng-bind="option3" ng-click="redirectTo('#subpage3')"></button>
<button ng-bind="language" ng-if="availableLanguages.length > 1" ng-repeat="language in availableLanguages" ng-click="setLanguage(language)"></button>
</div>
</div>
<
<div id="content" ng-view ng-show="isDataLoaded">
</div>
<footer id="footer" class="footer" ng-show="isDataLoaded">
<status-bar></status-bar>
</footer>
Resolve applies only for ng-views's controller, but header stuff needs to be translated as well, so I need to make a headerCtrl somehow wait before it tries to apply translations.
So I have made another unpopular decision to inform all controllers about the finished startup via a broadcast message and to wait until it is all done while showing the loading screen.
It looks fine and is pretty responsive (1sec per startup is acceptable at this point).
The problem is, that I see many design mistakes with this attempt and I just can not come up with the better design.
So my main question is:
How can I make it better? 1st service returns a whole array which is used by the 2nd service so I do not know how to combine it with promises?
I am afraid that with the development of the application I will find myself in a global variables and global events hell
Thanks in advance for your help!
It seems like you are looking for the angular-translate-loader-static-files extension for angular-translate. See the documentation here.
This together with proper configuration of $translateProvider will allow you to fetch json files with translations from the backend or even swap translations on demand - for example user changes language setting, controller reconfigures $translateProvider. Your job is done - everything will be fetched and updated automatically without a page reload.

AngularJs - get list of all registered modules

Can I get a list of all registered modules at run time?
For example:
// Some code somewhere in some .js file
var module1 = angular.module('module1', []);
// Some code in some other .js file
var module2 = angular.module('module2', []);
// Main .js file
var arrayWithNamesOfAllRegisteredModules = .....
// (result would be: ['module1', 'module2'])
Angular does not provide a way to retrieve the list of registered modules (at least I was not able to find a way in source code). You can however decorate angular.module method to store names in array. Something like this:
(function(orig) {
angular.modules = [];
angular.module = function() {
if (arguments.length > 1) {
angular.modules.push(arguments[0]);
}
return orig.apply(null, arguments);
}
})(angular.module);
Now you can check angular.modules array.
Demo: http://plnkr.co/edit/bNUP39cbFqNLbXyRqMex?p=preview
You can simply do :
console.log(angular.module('ModuleYouWantToInspect').requires);
It should return of an array of strings (dependencies). You can do the same for the output.
Given an angular.element, the $injector.modules array contains the list of registered modules.
e.g.
angular.element(document.body).injector().modules
If you're debugging, I discovered you can get the list by:
Find or add code to invoke run() from any module with any body, say:
angular.module('myModule')
.run(function() {})
Put a breakpoint on the .run, and step into angular.run(). There's an object called "modules" in scope that has all the modules as properties, by name.
This may work with other module methods too, or be accessible from code; I haven't tried very hard to understand the larger picture.
Improving solution
(function(angular) {
var orig = angular.module;
angular.modules = [];
angular.modules.select = function(query) {
var cache = [], reg = new RegExp(query || '.*');
for(var i=0,l=this.length;i< l;i++){
var item = this[i];
if(reg.test(item)){
cache.push(item)
}
}
return cache;
}
angular.module = function() {
var args = Array.prototype.slice.call(arguments);
if (arguments.length > 1) {
angular.modules.push(arguments[0]);
}
return orig.apply(null, args);
}
})(angular);
Now you can select modules:
angular.modules.select('app.modules.*')
Creating modules tree:
var app = angular.module('app.module.users', ['ui.router'...]);
var app = angular.module('app.module.users.edit', ['app.modules.users']);
Your main module app (concat submodules)
angular.module('app', ['ui.bootstrap', 'app.services', 'app.config']
.concat(angular.modules.select('app.module.*')));
in addition to #dfsq answer you can get list of modules with it dependencies
var AngularModules = (function (angular) {
function AngularModules() {
extendAngularModule();
angular.element(document).ready(function () {
getModulesDependencies();
});
}
var extendAngularModule = function () {
var orig = angular.module;
angular.modules = [];
angular.module = function () {
var args = Array.prototype.slice.call(arguments);
var modules = [];
if (arguments.length > 1) {
modules.push(arguments[0]);
}
for (var i = 0; i < modules.length; i++) {
angular.modules.push({
'module': modules[i]
});
}
return orig.apply(null, args);
};
};
var getModulesDependencies = function () {
for (var i = 0; i < angular.modules.length; i++) {
var module = angular.module(angular.modules[i].module);
angular.modules[i].dependencies = module && module.hasOwnProperty('requires') ? module.requires : [];
}
};
return AngularModules;
})(angular);
Usage:
var modules = new AngularModules();
There is a similar question with better answers here https://stackoverflow.com/a/19412176/132610, a summary of what they proposed is:
var app = angular.module('app', []);
# app.service(/**your injections*/) etc
# to access to the list of services + injections
app._invokeQueue #has following form:
[
[
'$provide',
'service',
Arguments[
'serviceName',
[
'$dependency1',
'$dependency2',
function(){}
],
]
]
]
This involves poking at implementation details that may change over time, but you can try this.
Load the page fully.
Set a breakpoint inside angular.module().
Call angular.module() from the console.
When you hit the breakpoint execute print out the modules dictionary console.dir(modules) or if you want to copy it into another editor window.prompt('', JSON.stringify(modules))
This works because behind the scenes angular builds a dictionary of the loaded modules called modules. You also want to wait until it's finished loading all the modules so they're in the dictionary.

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