Rerender angular-datatables when switching language with angular-translate - angularjs

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.

Related

How to Change Color of background in Angularjs Dynamically

I have a Poller that I have setup that has 2 files which are being queried. When new data has been found I am trying to set the color of my text background in the view but its just not happening.
If someone can solve this issue that would be great I am also welcome to suggestions to improving the structure of the code.
Service:
function Poller($http, $timeout) {
var projectcache = { response: [], calls: 0 };
var msgcache = { response: [], calls: 0 };
var newdata = false;
var msgdata = false;
var msgcolor = {};
var projectcolor = {};
var poller = function () {
$timeout(poller, 10000);
console.log("Begin Poller!");
$http.get('http://localhost/app/controllers/php/getProjects.php')
.then(function(r) {
if (r.data.projects.length > projectcache.response.length) {
newdata = true;
projectcolor = 'green';
} else {
newdata = false;
projectcolor = 'green';
};
angular.copy(r.data.projects, projectcache.response);
console.log("New Data Found: " + newdata);
});
$http.get('http://localhost/app/controllers/php/getMessages.php')
.then(function(m) {
if (m.data.messages.length > msgcache.response.length) {
msgdata = true;
msgcolor = 'green';
} else {
msgdata = false;
msgcolor = 'green';
};
angular.copy(m.data.messages, msgcache.response);
console.log("New Msg Found: " + msgdata);
});
};
poller();
return {
projects: projectcache.response,
messages: msgcache.response,
newdata: newdata,
msgdata: msgdata,
msgcolor: msgcolor,
projectcolor: projectcolor
};
};
View:
<li ng-class="{active: selectTab=='inbox'}" style="background-color:{{msgcolor}};" ng-click="selectTab='inbox'">Inbox</li>
<li ng-class="{active: selectTab=='projects'}" style="background-color:{{projectcolor}};" ng-click="selectTab='projects'">Projects</li>
Controller:
app.controller("taskbarController", ['$scope', 'authData', '$location', 'projectsModal', 'sendMessageModal', 'Poller',
function ($scope, authData, $location, projectsModal, sendMessageModal, Poller) {
$scope.msgcolor = Poller.msgcolor;
$scope.projectcolor = Poller.projectcolor;
}]);
My first thought is to use ng-class for this. I see you already have ng-class handling the display of your 'active' class.
If you'd like to try this approach out, I would:
1. Create css clases for each state/color you want to change to. (Can do this in external css file or between tags you create at the beginning of your page.
.successBackground {
background-color:green;
}
.errorBackground {
background-color:red;
}
Modify your ng-class attributes. Here I am assuming that success means that msgdata=true and error means that msgdata=false
Current html:
<li ng-class="{active: selectTab=='inbox'}" style="background-color:{{msgcolor}};" ng-click="selectTab='inbox'">Inbox</li>
<li ng-class="{active: selectTab=='projects'}" style="background-color:{{projectcolor}};" ng-click="selectTab='projects'">Projects</li>
Updated html:
<li ng-class="{active: selectTab=='inbox', successBackground:msgdata===true, errorBackground:msgdata===false}" ng-click="selectTab='inbox'">Inbox</li>
<li ng-class="{active: selectTab=='projects',successBackground:msgdata===true, errorBackground:msgdata===false}" ng-click="selectTab='projects'">Projects</li>
Now when your msgdata is updated, the successBackground and errorBackground are automatically updated based on the latest msgdata value.
Hope this helps!
#Elevant, the comment option didn't allow me to format my code snippets, so I am replying to your latest comment in this answer post.
I'm not sure if the watcher can listen to just the Poller object or if it'll need to listen to each attribute separately (msgColor, projectColor). In my code snippet here, I'll assume we cannot and we'll need to listen to each individually.
Current code:
$scope.msgcolor = Poller.msgcolor;
$scope.projectcolor = Poller.projectcolor;
Updated with watchers:
$scope.$watch('Poller.msgcolor', function(newValue,oldValue) {
$scope.msgcolor = Poller.msgcolor;
});
$scope.$watch('Poller.projectcolor', function(newValue,oldValue) {
$scope.projectcolor = Poller.projectcolor;
);
Though if you still wanted to look into the option to move $timeout, I would make the following changes (not sure if this matches what you had tried).
In the Poller service definition remove $timeout. Updated snippet:
function Poller($http)
Still in Poller, remove this line:
$timeout(poller, 10000);
In the Controller add $timeout - updated snippet:
app.controller("taskbarController", ['$scope', 'authData', '$location', 'projectsModal', 'sendMessageModal', 'Poller','$timeout'
function ($scope, authData, $location, projectsModal, sendMessageModal, Poller,$timeout)
Then in the controller, you would add:
$timeout(function(Poller) {
Poller.poller();
$scope.msgcolor = Poller.msgcolor;
$scope.projectcolor = Poller.projectcolor;
}, 10000);
I hope this helps, I haven't had a chance to test the code, so you may have to tinker around with it a bit. Let me know how it goes!

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.

Angular-DataTables custom filter

I am trying to add a custom filter to angular-DataTables with server side processing, which works perfectly with sorting and built in search of datatables.
I was following example Angular-DataTables, to build the server side processing and setup the DataTable, in searching around i have found some info but haven't been able to make it work.
What i am trying to get is to redraw the table with filtered data once the checkbox [Player] has been triggered.
Does anyone know a solution for this or has a working example for this?
have found this example Custom Table Filter, but it seems it doesn't work either.
HTML:
<div ng-app="showcase"><div ng-controller="ServerSideProcessingCtrl">
<label><input type="checkbox" id="customFilter" value="player"> Player</label>
<table datatable="" dt-options="dtOptions" dt-columns="dtColumns" class="row-border hover"></table>
JS part:
'use strict';
angular.module('showcase', ['datatables'])
//.controller('ServerSideProcessingCtrl', ServerSideProcessingCtrl);
.controller('ServerSideProcessingCtrl',["$scope", "DTOptionsBuilder", "DTColumnBuilder", function($scope, DTOptionsBuilder, DTColumnBuilder) {
//function ServerSideProcessingCtrl(DTOptionsBuilder, DTColumnBuilder) {
console.log($scope);
$scope.dtOptions = DTOptionsBuilder.newOptions()
.withOption('ajax', {
// Either you specify the AjaxDataProp here
// dataSrc: 'data',
url: 'getTableData.php',
type: 'POST'
})
// or here
.withDataProp('data')
.withOption('serverSide', true)
.withPaginationType('full_numbers');
$scope.dtColumns = [
DTColumnBuilder.newColumn('id').withTitle('ID'),
DTColumnBuilder.newColumn('name').withTitle('First name'),
DTColumnBuilder.newColumn('position').withTitle('Position'),
DTColumnBuilder.newColumn('type').withTitle('Type')
];
$scope.$on('event:dataTableLoaded', function(event, loadedDT) {
console.log(event);
console.log(loadedDT);
$('#customFilter').on('change', function() {
loadedDT.DataTable.draw();
} );
});
}]);
JSON on load:
{"draw":"1","recordsTotal":8,"recordsFiltered":8,"data":[{"id":"1","name":"Raul","position":"front","type":"player"},{"id":"2","name":"Crespo","position":"front","type":"player"},{"id":"3","name":"Nesta","position":"back","type":"player"},{"id":"4","name":"Costacurta","position":"back","type":"player"},{"id":"5","name":"Doc Brown","position":"staff","type":"medic"},{"id":"6","name":"Jose","position":"staff","type":"manager"},{"id":"7","name":"Ferguson","position":"staff","type":"manager"},{"id":"8","name":"Zinedine","position":"staff","type":"director"}]}
After searching and browsing, combined few examples and came up with this.
HTML :
<label><input type="checkbox" id="customFilter" value="player" ng-click="reload()" > Player</label>
JS:
'use strict';
angular.module('showcase', ['datatables'])
//.controller('ServerSideProcessingCtrl', ServerSideProcessingCtrl);
.controller('ServerSideProcessingCtrl',["$scope", "DTOptionsBuilder", "DTColumnBuilder","DTInstances", function ($scope, DTOptionsBuilder, DTColumnBuilder, DTInstances) {
//function ServerSideProcessingCtrl(DTOptionsBuilder, DTColumnBuilder) {
console.log($scope);
$scope.dtOptions = DTOptionsBuilder.newOptions()
.withOption('ajax', {
// Either you specify the AjaxDataProp here
// dataSrc: 'data',
url: 'getTableData.php',
type: 'POST',
// CUSTOM FILTERS
data: function (data) {
data.customFilter = $('#customFilter').is(':checked');
}
})
// or here
.withDataProp('data')
.withOption('serverSide', true)
.withPaginationType('full_numbers');
$scope.dtColumns = [
DTColumnBuilder.newColumn('id').withTitle('ID'),
DTColumnBuilder.newColumn('name').withTitle('First name'),
DTColumnBuilder.newColumn('position').withTitle('Position'),
DTColumnBuilder.newColumn('type').withTitle('Type')
];
DTInstances.getLast().then(function (dtInstance) {
$scope.dtInstance = dtInstance;
});
$scope.reload = function(event, loadedDT) {
$scope.dtInstance.reloadData();
};
}]);
and on the backend just go through the $_POST and check for custom filter, hopefully this will help someone
You can use withFnServerData with fromSource functions instead of
withOption:
This API allows you to override the default function to retrieve the data (which is $.getJSON according to DataTables documentation) to something more suitable for you application.
It's mainly used for Datatables v1.9.4. See DataTable documentation.
$scope.dtOptions = DTOptionsBuilder.fromSource('data.json')
.withFnServerData(serverData);
function serverData (sSource, aoData, fnCallback, oSettings) {
oSettings.jqXHR = $.ajax({
'dataType': 'json',
'type': 'POST',
'url': sSource,
'data': aoData,
'success': fnCallback
});
:)
Ok sorry its not a full blown example. This only works with angular and datatables, if you do a filter on the ng-repeat eg | aFilter:this The this transfers the scope. The filtering applied can now be quite complex. Within the ng-controller <div> you can have an html partial containing drop downs or input texts, all having an ng-model value.
When these change they kick off the filter routineaFilter an angular.filter('aFilter'.... js routine. The records are piped through the afilter routine allowing the ones wanted to be pushed onto an array and this is what is returned with the return. It doesn't work with breeze, yet. Be aware it is unlikely to be server side. To deal with server side maybe an SQL call in the service....another day.
eg in the ng-table id="test" :
<tr ng-repeat="edRec in aSetOfJSonRecords | aFilter:this | orderBy:'summat'">
{{edRec.enCode}} etc
</tr>
in the aFilter, the fltEnCode represents the ng-model values, the test variable allows freedom from nulls causing issues upon comparison, good idea to test for undefined first:
app.filter('aFilter', [function () {
return function (items, $scope) {
var countItems = 0;
var filtered = [];
var isOK = 0;
angular.forEach(items, function (item) {
isOK = 1;
// some conditions
if ($scope.fltEnCode !== "") {
if (item.enCode === null) { test = ""; } else { test = item.enCode; }
if (test.indexOf($scope.fltEnCode) < 0) isOK = 0;
}
// end of conditions
if (isOK > 0) {
filtered.push(item);
countItems++;
}
});
// alert(countItems);
return filtered;
};
}]);
Hope its of some use. I've avoided boolean variables as they have given grief before. Odd occasions have needed an ng-change in the html items pointing to an angular function resetting the data by calling the getTheItemsForTest() in the controller. This redraws the list. Having
$scope.dtOptions = {
stateSave: false, .......
in your controller, keeps the sorting columns correct.
$(document).ready(function() {
var table = $('#test').DataTable();
table.draw();
};
might also be useful if its recalcitrant. I need to know how to make it work for breeze??? Enjoy..
here is what I really missed after I searched alot
bower install datatables-light-columnfilter

How to get a length of a list from today's date only, using angularjs?

I am building an application using angularjs. I use mysql for database.
How do i get the length of the list which are updated on today's date only.?
for getting the length from the whole list, I used something like this
app.factory('tripsheetFactory', function ($http) {
return {
getTripsheets: function () {
return $http.get(urlt + '/all');
},
addTripsheet: function (tripsheet) {
return $http.post(urlt, tripsheet);
},
deleteTripsheet: function (tripsheet) {
return $http.delete(urlt + '?id=' + tripsheet.id);
},
updateTripsheet: function (tripsheet) {
return $http.put(urlt + '?id=' + tripsheet.id, tripsheet);
}
};
});
app.controller('IndexCtrl', function ($scope, $filter, tripsheetFactory) {
$scope.getTotalTripsheets = function () {
return $scope.tripsheets.length;
};
});
But i want to fetch the data only from today's date.
"tripsheets" is the list i want to fetch the data from
You need to access the $http data using either a success callback or since it is a promise using a then callback.
app.controller('IndexCtrl', function ($scope, $filter, tripsheetFactory) {
/* call the method in factory */
tripsheetFactory.getTripsheets().success( serverResponse){
/* set scope var in ajax success callback */
$scope.tripsheets= serverResponse;
});
});
Now within your html you can do things like:
<span>Total trips : {{ tripsheets.length}} </span>
<ul>
<li ng-repeat="trip in tripsheets">
Driver {{trip.driver}} drove {{trip.distance}} on {{trip.date | date:'yyyy-MM-dd'}}
</li>
</ul>
There are numerous ways to filter data. A simple controller filter for example:
var today=new date();
$scope.activeTrips= $scope.tripsheets.filter(function(trip){
return new Date( trip.date) >= today;
})

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