restangular animation on data load - angularjs

How would I use fadein, fadeout effect on data load with restangular. How would I get know about when to fade out and when to fadein
my controller method looks like
$scope.fetchResult = function() {
spinner_in();
return api.items.search($scope.filterCriteria).then(function(data) {
$scope.products = data;
$scope.totalPages = data.total;
$scope.productsCount = data.TotalItems;
}, function() {
$scope.products = [];
$scope.totalPages = 0;
$scope.productsCount = 0;
});
};

Here is my solution. Add a response interceptor and a request interceptor that sends out a rootscope broadcast. Then create a directive to listen for that response and request.:
angular.module('mean.system')
.factory('myRestangular',['Restangular','$rootScope', function(Restangular,$rootScope) {
return Restangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.setBaseUrl('http://localhost:3000/api');
RestangularConfigurer.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
var extractedData;
// .. to look for getList operations
if (operation === 'getList') {
// .. and handle the data and meta data
extractedData = data.data;
extractedData.meta = data.meta;
} else {
extractedData = data.data;
}
$rootScope.$broadcast('apiResponse');
return extractedData;
});
RestangularConfigurer.setRequestInterceptor(function (elem, operation) {
if (operation === 'remove') {
return null;
}
return (elem && angular.isObject(elem.data)) ? elem : {data: elem};
});
RestangularConfigurer.setRestangularFields({
id: '_id'
});
RestangularConfigurer.addRequestInterceptor(function(element, operation, what, url) {
$rootScope.$broadcast('apiRequest');
return element;
});
});
}]);
Here is the directive:
angular.module('mean.system')
.directive('smartLoadingIndicator', function($rootScope) {
return {
restrict: 'AE',
template: '<div ng-show="isAPICalling"><p><i class="fa fa-gear fa-4x fa-spin"></i> Loading</p></div>',
replace: true,
link: function(scope, elem, attrs) {
scope.isAPICalling = false;
$rootScope.$on('apiRequest', function() {
scope.isAPICalling = true;
});
$rootScope.$on('apiResponse', function() {
scope.isAPICalling = false;
});
}
};
})
;

Related

How to redisplay directive after Javascript function executes

I have an AngularJS Directive defined in a Javascript file that looks like this:
(function () {
'use strict';
angular
.module('ooApp.controllers')
.directive('fileUploader', fileUploader);
fileUploader.$inject = ['appInfo', 'fileManager'];
function fileUploader(appInfo, fileManager) {
var directive = {
link: link,
restrict: 'E',
templateUrl: 'views/directive/UploadFile.html',
scope: true
};
return directive;
function link(scope, element, attrs) {
scope.hasFiles = false;
scope.files = [];
scope.upload = fileManager.upload;
scope.appStatus = appInfo.status;
scope.fileManagerStatus = fileManager.status;
}
}
})();
and in the template URL of the directive there is a button that calls a Javascript function which looks like this:
function upload(files) {
var formData = new FormData();
angular.forEach(files, function (file) {
formData.append(file.name, file);
});
return fileManagerClient.save(formData)
.$promise
.then(function (result) {
if (result && result.files) {
result.files.forEach(function (file) {
if (!fileExists(file.name)) {
service.files.push(file);
}
});
}
appInfo.setInfo({ message: "files uploaded successfully" });
return result.$promise;
},
function (result) {
appInfo.setInfo({ message: "something went wrong: " +
result.data.message });
return $q.reject(result);
})
['finally'](
function () {
appInfo.setInfo({ busy: false });
service.status.uploading = false;
});
}
Once I select files for upload and click the upload button I need to reload the directive or somehow get it back to it's initial state so I can upload additional files. I'm relatively new to AngularJS and I'm not quite sure how to do this. Any help is much appreciated.
Thanks,
Pete
You just need to create a reset method. Also, you may want to call the parent controller function.
Using answer from this
ngFileSelect.directive.js
...
.directive("onFileChange",function(){
return {
restrict: 'A',
link: function($scope,el){
var onChangeHandler = scope.$eval(attrs.onFileChange);
el.bind('change', onChangeHandler);
}
}
...
fileUploader.directive.js
(function () {
'use strict';
angular
.module('ooApp.controllers')
.directive('fileUploader', fileUploader);
fileUploader.$inject = ['appInfo', 'fileManager'];
function fileUploader(appInfo, fileManager) {
return {
link: link,
restrict: 'E',
templateUrl: 'views/directive/UploadFile.html',
scope:{
onSubmitCallback: '&',
onFileChange: '&'
}
};
function link(scope, element, attrs) {
scope.reset = reset;
scope.fileChange = fileChange;
reset();
function reset() {
scope.hasFiles = false;
scope.files = [];
scope.upload = fileManager.upload;
scope.appStatus = appInfo.status;
scope.fileManagerStatus = fileManager.status;
if(typeof scope.onSubmitCallback === 'function') {
scope.onSubmitCallback();
}
}
function fileChange(file) {
if(typeof scope.onFileChange === 'function'){
scope.onFileChange(file);
}
}
}
}
})();
UploadFile.html
<form>
<div>
...
</div>
<input type="submit" ng-click="reset()" file-on-change="fileChange($files)" />Upload
</form>
parent.html
<file-uploader on-submit-callback="onUpload" on-file-change="onFileChange" ng-controller="UploadCtrl" />
upload.controller.js
...
$scope.onUpload = function() {
console.log('onUpload clicked %o', arguments);
};
$scope.onFileChange = function(e) {
var imageFile = (e.srcElement || e.target).files[0];
}
...

AngularJS $watch newValue is undefined

I am trying to make a alert service with a directive. It is in the directive part I have some trouble. My have a directive that looks like this:
angular.module('alertModule').directive('ffAlert', function() {
return {
templateUrl: 'components/alert/ff-alert-directive.html',
controller: ['$scope','alertService',function($scope,alertService) {
$scope.alerts = alertService;
}],
link: function (scope, elem, attrs, ctrl) {
scope.$watch(scope.alerts, function (newValue, oldValue) {
console.log("alerts is now:",scope.alerts,oldValue, newValue);
for(var i = oldValue.list.length; i < newValue.list.length; i++) {
scope.alerts.list[i].isVisible = true;
if (scope.alerts.list[i].timeout > 0) {
$timeout(function (){
scope.alerts.list[i].isVisible = false;
}, scope.alerts.list[i].timeout);
}
}
}, true);
}
}
});
The reason for the for-loop is to attach a timeout for the alerts that has this specified.
I will also include the directive-template:
<div class="row">
<div class="col-sm-1"></div>
<div class="col-sm-10">
<div alert ng-repeat="alert in alerts.list" type="{{alert.type}}" ng-show="alert.isVisible" close="alerts.close(alert.id)">{{alert.msg}}</div>
</div>
<div class="col-sm-1"></div>
</div>
When I run this, I get this error in the console:
TypeError: Cannot read property 'list' of undefined
at Object.fn (http://localhost:9000/components/alert/ff-alert-directive.js:10:29)
10:29 is the dot in "oldValue.list" in the for-loop. Any idea what I am doing wrong?
Edit: I am adding the alertService-code (it is a service I use to keep track of all the alerts in my app):
angular.module('alertModule').factory('alertService', function() {
var alerts = {};
var id = 1;
alerts.list = [];
alerts.add = function(alert) {
alert.id = id;
alerts.list.push(alert);
alert.id += 1;
console.log("alertService.add: ",alert);
return alert.id;
};
alerts.add({type: "info", msg:"Dette er til info...", timeout: 1000});
alerts.addServerError = function(error) {
var id = alerts.add({type: "warning", msg: "Errormessage from server: " + error.description});
// console.log("alertService: Server Error: ", error);
return id;
};
alerts.close = function(id) {
for(var index = 0; index<alerts.list.length; index += 1) {
console.log("alert:",index,alerts.list[index].id);
if (alerts.list[index].id == id) {
console.log("Heey");
alerts.list.splice(index, 1);
}
}
};
alerts.closeAll = function() {
alerts.list = [];
};
return alerts;
});
try like this , angular fires your watcher at the first time when your directive initialized
angular.module('alertModule').directive('ffAlert', function() {
return {
templateUrl: 'components/alert/ff-alert-directive.html',
controller: ['$scope','alertService',function($scope,alertService) {
$scope.alerts = alertService;
}],
link: function (scope, elem, attrs, ctrl) {
scope.$watch(scope.alerts, function (newValue, oldValue) {
if(newValue === oldValue) return;
console.log("alerts is now:",scope.alerts,oldValue, newValue);
for(var i = oldValue.list.length; i < newValue.list.length; i++) {
scope.alerts.list[i].isVisible = true;
if (scope.alerts.list[i].timeout > 0) {
$timeout(function (){
scope.alerts.list[i].isVisible = false;
}, scope.alerts.list[i].timeout);
}
}
}, true);
}
}
});
if you have jQuery available, try this
if(jQuery.isEmptyObject(newValue)){
return;
}
inside scope.$watch

scope.$on() invokes several times for one broadcast

In Angular.js, scope.$on("UPDATE", function(event, account, vipLabel, nodeName) is firing about 20 times when it should only invokes once for each $rootScope.$broadcast("UPDATE", this.account,this.vipLabel, this.nodeName). I know that scope.$on() contains an event object, but I haven't been able to find a use for it to help my issue.
I am stuck on this and I need the scope.$on() to fire only once for each broadcast.
angular.module('main.vips')
.factory("StatusTrackerService", function($timeout, $rootScope, Restangular) {
var CHECK_ITERATIONS = 1;
var TIME_ITERATION = 1000;
var eventCollection = {};
eventCollection.vipLabel;
function urlBuilder(account, eventId) {
var account = Restangular.one("account", account);
var eventId = account.one("event", eventId);
return eventId;
}
function NewStatusEvent(account, eventId, url, vipLabel, nodeName) {
this.iteration = 0;
this.account = account;
this.eventId = eventId;
this.url = url;
this.vipLabel = vipLabel;
this.nodeName = nodeName;
}
NewStatusEvent.prototype.runCheckLoop = function() {
this.url.get()
.then(function(data) {
if (data.Automation.Status != "SUCCESS") {
console.log("yes");
console.log(this.iteration);
console.log(CHECK_ITERATIONS);
if (this.iteration < CHECK_ITERATIONS) {
this.iteration++;
$rootScope.$broadcast("UPDATE", this.account,
this.vipLabel, this.nodeName);
console.log('-------------------');
console.log('checking ' + JSON.stringify(this.url));
console.log('iteration ' + this.iteration);
console.log('-------------------');
$timeout(function (){
this.runCheckLoop();
}.bind(this), TIME_ITERATION);
}
}
}.bind(this));
}
function runEventCheck(account, eventId, nodeName) {
url = urlBuilder(account, eventId);
eventCollection[eventCollection.vipLabel] = new NewStatusEvent(account, eventId,
url, eventCollection.vipLabel, nodeName);
eventCollection[eventCollection.vipLabel].runCheckLoop();
}
return { runEventCheck: runEventCheck,
eventCollection: eventCollection
};
});
Directive:
return {
templateUrl: "vips/directives/action_button.html",
restrict: "AE",
replace: true,
transclude: false,
scope: {
label: "#?",
icon: "#?",
type: "#?",
actions: "=?"
},
link: function(scope, element, attrs) {
element.on("click", function(event) {
var vipLabel = event.currentTarget.id;
StatusTrackerService.eventCollection.vipLabel = vipLabel;
});
scope.$on("UPDATE", function(event, account,
vipLabel, nodeName) {
console.log("Scope.on has fired this many times");
var nodeSelector = "#node-"+vipLabel+"-"+nodeName;
var nodeElement = angular.element(document.querySelector(nodeSelector));
if (!nodeElement.length) {
var vipElement = angular.element(document.querySelector('#vipLabel-'+vipLabel));
var elementGlobe = '<div id="'+vipLabel+nodeName+'">Element modified</div>';
vipElement.append(elementGlobe);
}
});
return scope.actions = services[attrs.type];
}
}
});
Logged output:
21 -Scope.on has fired this many times actionButton.js:52
------------------- status-checker.js:35
checking {"id":"0fd6afd9-2367-4a0-a5c9-ff0b3e60cdcf","route":"event","reqParams":null,"$fromServer":false,"parentResource":{"route":"account","parentResource":null,"id":"99006"},"restangularCollection":false} status-checker.js:36
iteration 1 status-checker.js:37
-------------------
Html:
<div action-button type="loadbalancer" label="Actions" icon="glyphicon glyphicon-cog"
actions="actions.loadbalancer"></div>
</div>
<div id="{{vip.label}}" action-button type="vip" icon="glyphicon glyphicon-pencil" actions="actions.vips"
class="pull-right"></div>
<div action-button type="node" icon="glyphicon glyphicon-pencil" actions="actions.nodes"
class="pull-right"></div>
UPDATE:
I added if (scope.type !== "vip") and it worked...now the event fires only once. It doesn't make sense to me because it is the <div id="{{vip.label}}" action-button type="vip" that I am using. It seems like it should be if (scope.type == "vip") instead...but it's the opposite.
scope.$on("UPDATE", function(event, account,
vipLabel, nodeName) {
if (scope.type !== "vip") {
console.log(scope);
var nodeSelector = "#node-"+vipLabel+"-"+nodeName;
var nodeElement = angular.element(document.querySelector(nodeSelector));
console.log(nodeElement.length);
if (nodeElement.length == 0) {
console.log("create node");
var vipElement = angular.element(document.querySelector('#vipLabel-'+vipLabel));
var elementGlobe = '<div id="'+vipLabel+nodeName+'">Element modified</div>';
vipElement.append(elementGlobe);
}
}
});

How to create a AngularJS jQueryUI Autocomplete directive

I am trying to create a custom directive that uses jQueryUI's autocomplete widget. I want this to be as declarative as possible. This is the desired markup:
<div>
<autocomplete ng-model="employeeId" url="/api/EmployeeFinder" label="{{firstName}} {{surname}}" value="id" />
</div>
So, in the example above, I want the directive to do an AJAX call to the url specified, and when the data is returned, show the value calculated from the expression(s) from the result in the textbox and set the id property to the employeeId. This is my attempt at the directive.
app.directive('autocomplete', function ($http) {
return {
restrict: 'E',
replace: true,
template: '<input type="text" />',
require: 'ngModel',
link: function (scope, elem, attrs, ctrl) {
elem.autocomplete({
source: function (request, response) {
$http({
url: attrs.url,
method: 'GET',
params: { term: request.term }
})
.then(function (data) {
response($.map(data, function (item) {
var result = {};
result.label = item[attrs.label];
result.value = item[attrs.value];
return result;
}))
});
},
select: function (event, ui) {
ctrl.$setViewValue(elem.val(ui.item.label));
return false;
}
});
}
}
});
So, I have two issues - how to evaluate the expressions in the label attribute and how to set the property from the value attribute to the ngModel on my scope.
Here's my updated directive
(function () {
'use strict';
angular
.module('app')
.directive('myAutocomplete', myAutocomplete);
myAutocomplete.$inject = ['$http', '$interpolate', '$parse'];
function myAutocomplete($http, $interpolate, $parse) {
// Usage:
// For a simple array of items
// <input type="text" class="form-control" my-autocomplete url="/some/url" ng-model="criteria.employeeNumber" />
// For a simple array of items, with option to allow custom entries
// <input type="text" class="form-control" my-autocomplete url="/some/url" allow-custom-entry="true" ng-model="criteria.employeeNumber" />
// For an array of objects, the label attribute accepts an expression. NgModel is set to the selected object.
// <input type="text" class="form-control" my-autocomplete url="/some/url" label="{{lastName}}, {{firstName}} ({{username}})" ng-model="criteria.employeeNumber" />
// Setting the value attribute will set the value of NgModel to be the property of the selected object.
// <input type="text" class="form-control" my-autocomplete url="/some/url" label="{{lastName}}, {{firstName}} ({{username}})" value="id" ng-model="criteria.employeeNumber" />
var directive = {
restrict: 'A',
require: 'ngModel',
compile: compile
};
return directive;
function compile(elem, attrs) {
var modelAccessor = $parse(attrs.ngModel),
labelExpression = attrs.label;
return function (scope, element, attrs) {
var
mappedItems = null,
allowCustomEntry = attrs.allowCustomEntry || false;
element.autocomplete({
source: function (request, response) {
$http({
url: attrs.url,
method: 'GET',
params: { term: request.term }
})
.success(function (data) {
mappedItems = $.map(data, function (item) {
var result = {};
if (typeof item === 'string') {
result.label = item;
result.value = item;
return result;
}
result.label = $interpolate(labelExpression)(item);
if (attrs.value) {
result.value = item[attrs.value];
}
else {
result.value = item;
}
return result;
});
return response(mappedItems);
});
},
select: function (event, ui) {
scope.$apply(function (scope) {
modelAccessor.assign(scope, ui.item.value);
});
if (attrs.onSelect) {
scope.$apply(attrs.onSelect);
}
element.val(ui.item.label);
event.preventDefault();
},
change: function () {
var
currentValue = element.val(),
matchingItem = null;
if (allowCustomEntry) {
return;
}
if (mappedItems) {
for (var i = 0; i < mappedItems.length; i++) {
if (mappedItems[i].label === currentValue) {
matchingItem = mappedItems[i].label;
break;
}
}
}
if (!matchingItem) {
scope.$apply(function (scope) {
modelAccessor.assign(scope, null);
});
}
}
});
};
}
}
})();
Sorry to wake this up... It's a nice solution, but it does not support ng-repeat...
I'm currently debugging it, but I'm not experienced enough with Angular yet :)
EDIT:
Found the problem. elem.autocomplete pointed to elem parameter being sent into compile function. IT needed to point to the element parameter in the returning linking function. This is due to the cloning of elements done by ng-repeat. Here is the corrected code:
app.directive('autocomplete', function ($http, $interpolate, $parse) {
return {
restrict: 'E',
replace: true,
template: '<input type="text" />',
require: 'ngModel',
compile: function (elem, attrs) {
var modelAccessor = $parse(attrs.ngModel),
labelExpression = attrs.label;
return function (scope, element, attrs, controller) {
var
mappedItems = null,
allowCustomEntry = attrs.allowCustomEntry || false;
element.autocomplete({
source: function (request, response) {
$http({
url: attrs.url,
method: 'GET',
params: { term: request.term }
})
.success(function (data) {
mappedItems = $.map(data, function (item) {
var result = {};
if (typeof item === "string") {
result.label = item;
result.value = item;
return result;
}
result.label = $interpolate(labelExpression)(item);
if (attrs.value) {
result.value = item[attrs.value];
}
else {
result.value = item;
}
return result;
});
return response(mappedItems);
});
},
select: function (event, ui) {
scope.$apply(function (scope) {
modelAccessor.assign(scope, ui.item.value);
});
elem.val(ui.item.label);
event.preventDefault();
},
change: function (event, ui) {
var
currentValue = elem.val(),
matchingItem = null;
if (allowCustomEntry) {
return;
}
for (var i = 0; i < mappedItems.length; i++) {
if (mappedItems[i].label === currentValue) {
matchingItem = mappedItems[i].label;
break;
}
}
if (!matchingItem) {
scope.$apply(function (scope) {
modelAccessor.assign(scope, null);
});
}
}
});
}
}
}
});

angularjs autosave form is it the right way?

My goal is to autosave a form after is valid and update it with timeout.
I set up like:
(function(window, angular, undefined) {
'use strict';
angular.module('nodblog.api.article', ['restangular'])
.config(function (RestangularProvider) {
RestangularProvider.setBaseUrl('/api');
RestangularProvider.setRestangularFields({
id: "_id"
});
RestangularProvider.setRequestInterceptor(function(elem, operation, what) {
if (operation === 'put') {
elem._id = undefined;
return elem;
}
return elem;
});
})
.provider('Article', function() {
this.$get = function(Restangular) {
function ngArticle() {};
ngArticle.prototype.articles = Restangular.all('articles');
ngArticle.prototype.one = function(id) {
return Restangular.one('articles', id).get();
};
ngArticle.prototype.all = function() {
return this.articles.getList();
};
ngArticle.prototype.store = function(data) {
return this.articles.post(data);
};
ngArticle.prototype.copy = function(original) {
return Restangular.copy(original);
};
return new ngArticle;
}
})
})(window, angular);
angular.module('nodblog',['nodblog.route'])
.directive("autosaveForm", function($timeout,Article) {
return {
restrict: "A",
link: function (scope, element, attrs) {
var id = null;
scope.$watch('form.$valid', function(validity) {
if(validity){
Article.store(scope.article).then(
function(data) {
scope.article = Article.copy(data);
_autosave();
},
function error(reason) {
throw new Error(reason);
}
);
}
})
function _autosave(){
scope.article.put().then(
function() {
$timeout(_autosave, 5000);
},
function error(reason) {
throw new Error(reason);
}
);
}
}
}
})
.controller('CreateCtrl', function ($scope,$location,Article) {
$scope.article = {};
$scope.save = function(){
if(typeof $scope.article.put === 'function'){
$scope.article.put().then(function() {
return $location.path('/blog');
});
}
else{
Article.store($scope.article).then(
function(data) {
return $location.path('/blog');
},
function error(reason) {
throw new Error(reason);
}
);
}
};
})
I'm wondering if there is a best way.
Looking at the code I can see is that the $watch will not be re-fired if current input is valid and the user changes anything that is valid too. This is because watch functions are only executed if the value has changed.
You should also check the dirty state of the form and reset it when the form data has been persisted otherwise you'll get an endless persist loop.
And your not clearing any previous timeouts.
And the current code will save invalid data if a current timeout is in progress.
I've plunked a directive which does this all and has better SOC so it can be reused. Just provide it a callback expression and you're good to go.
See it in action in this plunker.
Demo Controller
myApp.controller('MyController', function($scope) {
$scope.form = {
state: {},
data: {}
};
$scope.saveForm = function() {
console.log('Saving form data ...', $scope.form.data);
};
});
Demo Html
<div ng-controller="MyController">
<form name="form.state" auto-save-form="saveForm()">
<div>
<label>Numbers only</label>
<input name="text"
ng-model="form.data.text"
ng-pattern="/^\d+$/"/>
</div>
<span ng-if="form.state.$dirty && form.state.$valid">Updating ...</span>
</form>
</div>
Directive
myApp.directive('autoSaveForm', function($timeout) {
return {
require: ['^form'],
link: function($scope, $element, $attrs, $ctrls) {
var $formCtrl = $ctrls[0];
var savePromise = null;
var expression = $attrs.autoSaveForm || 'true';
$scope.$watch(function() {
if($formCtrl.$valid && $formCtrl.$dirty) {
if(savePromise) {
$timeout.cancel(savePromise);
}
savePromise = $timeout(function() {
savePromise = null;
// Still valid?
if($formCtrl.$valid) {
if($scope.$eval(expression) !== false) {
console.log('Form data persisted -- setting prestine flag');
$formCtrl.$setPristine();
}
}
}, 500);
}
});
}
};
});
UPDATE:
to stopping timeout
all the logic in the directive
.directive("autosaveForm", function($timeout,$location,Post) {
var promise;
return {
restrict: "A",
controller:function($scope){
$scope.post = {};
$scope.save = function(){
console.log(promise);
$timeout.cancel(promise);
if(typeof $scope.post.put === 'function'){
$scope.post.put().then(function() {
return $location.path('/post');
});
}
else{
Post.store($scope.post).then(
function(data) {
return $location.path('/post');
},
function error(reason) {
throw new Error(reason);
}
);
}
};
},
link: function (scope, element, attrs) {
scope.$watch('form.$valid', function(validity) {
element.find('#status').removeClass('btn-success');
element.find('#status').addClass('btn-danger');
if(validity){
Post.store(scope.post).then(
function(data) {
element.find('#status').removeClass('btn-danger');
element.find('#status').addClass('btn-success');
scope.post = Post.copy(data);
_autosave();
},
function error(reason) {
throw new Error(reason);
}
);
}
})
function _autosave(){
scope.post.put().then(
function() {
promise = $timeout(_autosave, 2000);
},
function error(reason) {
throw new Error(reason);
}
);
}
}
}
})
Here's a variation of Null's directive, created because I started seeing "Infinite $digest Loop" errors. (I suspect something changed in Angular where cancelling/creating a $timeout() now triggers a digest.)
This variation uses a proper $watch expression - watching for the form to be dirty and valid - and then calls $setPristine() earlier so the watch will re-fire if the form transitions to dirty again. We then use an $interval to wait for a pause in those dirty notifications before saving the form.
app.directive('autoSaveForm', function ($log, $interval) {
return {
require: ['^form'],
link: function (scope, element, attrs, controllers) {
var $formCtrl = controllers[0];
var autoSaveExpression = attrs.autoSaveForm;
if (!autoSaveExpression) {
$log.error('autoSaveForm missing parameter');
}
var savePromise = null;
var formModified;
scope.$on('$destroy', function () {
$interval.cancel(savePromise);
});
scope.$watch(function () {
// note: formCtrl.$valid is undefined when this first runs, so we use !$formCtrl.$invalid instead
return !$formCtrl.$invalid && $formCtrl.$dirty;
}, function (newValue, oldVaue, scope) {
if (!newValue) {
// ignore, it's not "valid and dirty"
return;
}
// Mark pristine here - so we get notified again if the form is further changed, which would make it dirty again
$formCtrl.$setPristine();
if (savePromise) {
// yikes, note we've had more activity - which we interpret as ongoing changes to the form.
formModified = true;
return;
}
// initialize - for the new interval timer we're about to create, we haven't yet re-dirtied the form
formModified = false;
savePromise = $interval(function () {
if (formModified) {
// darn - we've got to wait another period for things to quiet down before we can save
formModified = false;
return;
}
$interval.cancel(savePromise);
savePromise = null;
// Still valid?
if ($formCtrl.$valid) {
$formCtrl.$saving = true;
$log.info('Form data persisting');
var autoSavePromise = scope.$eval(autoSaveExpression);
if (!autoSavePromise || !autoSavePromise.finally) {
$log.error('autoSaveForm not returning a promise');
}
autoSavePromise
.finally(function () {
$log.info('Form data persisted');
$formCtrl.$saving = undefined;
});
}
}, 500);
});
}
};
});

Resources