Angular - clear form input after submit - angularjs

I have a simple form like so:
<form name="add-form" data-ng-submit="addToDo()">
<label for="todo-name">Add a new item:</label>
<input type="text" data-ng-model="toDoName" id="todo-name" name="todo-name" required>
<button type="submit">Add</button>
</form>
with my controller as follows:
$scope.addToDo = function() {
if ($scope.toDoName !== "") {
$scope.toDos.push(createToDo($scope.toDoName));
}
}
what I'd like to do is clear the text input after submission so I simply clear the model value:
$scope.addToDo = function() {
if ($scope.toDoName !== "") {
$scope.toDos.push(createToDo($scope.toDoName));
$scope.toDoName = "";
}
}
Except now, because the form input is 'required' I get a red border around the form input. This is the correct behaviour, but not what I want in this scenario... so instead I'd like to clear the input and then blur the input element. Which leads me to:
$scope.addToDo = function() {
if ($scope.toDoName !== "") {
$scope.toDos.push(createToDo($scope.toDoName));
$scope.toDoName = "";
$window.document.getElementById('todo-name').blur();
}
}
Now, I know that modifying the DOM from the controller like this is frowned upon in the Angular documentation - but is there a more Angular way of doing this?

When you give a name to your form it automatically gets added to the $scope.
So if you change your form name to "addForm" ('cause I don't think add-from is a valid name for angular, not sure why), you'll have a reference to $scope.addForm.
If you use angular 1.1.1 or above, you'll have a $setPristine() method on the $scope.addForm. which should recursively take care of resetting your form. or if you don't want to use the 1.1.x versions, you can look at the source and emulate it.

For those not switching over to 1.1.1 yet, here is a directive that will blur when a $scope property changes:
app.directive('blur', function () {
return function (scope, element, attrs) {
scope.$watch(attrs.blur, function () {
element[0].blur();
});
};
});
The controller must now change a property whenever a submit occurs. But at least we're not doing DOM manipulation in a controller, and we don't have to look up the element by ID:
function MainCtrl($scope) {
$scope.toDos = [];
$scope.submitToggle = true;
$scope.addToDo = function () {
if ($scope.toDoName !== "") {
$scope.toDos.push($scope.toDoName);
$scope.toDoName = "";
$scope.submitToggle = !$scope.submitToggle;
}
};
}
HTML:
<input type="text" data-ng-model="toDoName" name="todo-name" required
blur="submitToggle">
Plnkr

I have made it work it as below code.
HTML SECTION
<td ng-show="a">
<input type="text" ng-model="e.FirstName" />
</td>
Controller SECTION
e.FirstName= '';

Related

Reset form as pristine if user changed input values to their initial value

Angular has $dirty and $pristine properties on FormController that we can use to detect whether user has interacted with the form or not. Both of these properties are more or less the opposite face of the same coin.
I would like to implement the simplest way to actually detect when form is still in its pristine state despite user interacting with it. The form may have any inputs. If user for instance first changes something but then changes it back to initial value, I would like my form to be $pristine again.
This may not be so apparent with text inputs, but I'm having a list of checkboxes where user can toggle some of those but then changes their mind... I would only like the user to be able to save actual changes. The problem is that whenever user interacts with the list the form becomes dirty, regardless whether user re-toggled the same checkbox making the whole form back to what it initially was.
One possible way would be I could have default values saved with each checkbox and add ngChange to each of them which would check all of them each time and call $setPristine if all of them have initial values.
But I guess there're better, simpler more clever ways of doing the same. Maybe (ab)using validators or even something more ingenious?
Question
What would be the simplest way to detect forms being pristine after being interacted with?
It can be done by using a directive within the ngModel built-in directive and watch the model value and make changes to pristine when needed. It's less expensive than watching the entire form but still seems an overkill and I'm not sure about the performance in a large form.
Note: The following snippet is not the newest version of this solution, check on UPDATE 1 for a newest and optimized solution.
angular.module('app', [])
.directive('ngModel', function() {
return {
restrict: 'A',
require: ['ngModel', '^?form'],
priority: 1000, // just to make sure it will run after the built-in
link: function(scope, elem, attr, ctrls) {
var
rawValue,
ngModelCtrl = ctrls[0],
ngFormCtrl = ctrls[1],
isFormValue = function(value) {
return typeof value === 'object' && value.hasOwnProperty('$modelValue');
};
scope.$watch(attr.ngModel, function(value) {
// store the raw model value
// on initial state
if (rawValue === undefined) {
rawValue = value;
return;
}
if (value == rawValue) {
// set model pristine
ngModelCtrl.$setPristine();
// don't need to check if form is not defined
if (!ngFormCtrl) return;
// check for other named models in case are all pristine
// sets the form to pristine as well
for (key in ngFormCtrl) {
var value = ngFormCtrl[key];
if (isFormValue(value) && !value.$pristine) return;
}
// if haven't returned yet, means that all model are pristine
// so then, sets the form to pristine as well
ngFormCtrl.$setPristine();
}
});
}
};
})
.controller('myController', function($rootScope, $timeout) {
var $ctrl = this;
$ctrl.model = {
name: 'lenny',
age: 23
};
$timeout(function() {
console.log('watchers: ' + $rootScope.$$watchersCount)
}, 1000);
});
angular.element(document).ready(function() {
angular.bootstrap(document, ['app']);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.1/angular.js"></script>
<div ng-controller="myController as $ctrl">
<form name="$ctrl.myForm" novalidate>
<label>Name :
<input name="test1" ng-model="$ctrl.model.name">
</label>
<label>age :
<input name="test2" ng-model="$ctrl.model.age">
</label>
<label>Pristine: {{ $ctrl.myForm.$pristine }}</label>
<div><pre>
</pre>
</div>
</form>
</div>
UPDATE 1
Changed the watching system to watch once and get rid of the extra watchers.Now the changes comes from the change listeners of ngModelController and the watcher is unbinded on the first model set . As can be noticed by a console log, the numbers of watchers on root was always doubling the number of watchers, by doing this the number of watchers remains the same.
angular.module('app', [])
.directive('ngModel', function() {
return {
restrict: 'A',
require: ['ngModel', '^?form'],
priority: 1000,
link: function(scope, elem, attr, ctrls) {
var
rawValue,
ngModelCtrl = ctrls[0],
ngFormCtrl = ctrls[1],
isFormValue = function(value) {
return typeof value === 'object' && value.hasOwnProperty('$modelValue');
};
var unbindWatcher = scope.$watch(attr.ngModel, function(value) {
// set raw value
rawValue = value;
// add a change listenner
ngModelCtrl.$viewChangeListeners.push(function() {
if (rawValue === undefined) {
//rawValue = ngModelCtrl.$lastCommit;
}
if (ngModelCtrl.$modelValue == rawValue) {
// set model pristine
ngModelCtrl.$setPristine();
// check for other named models in case are all pristine
// sets the form to pristine as well
for (key in ngFormCtrl) {
var value = ngFormCtrl[key];
if (isFormValue(value) && !value.$pristine) return;
}
ngFormCtrl.$setPristine();
}
});
// unbind the watcher at the first change
unbindWatcher();
});
}
};
})
.controller('myController', function($rootScope, $timeout) {
var $ctrl = this;
$ctrl.model = {
name: 'lenny',
age: 23
};
$timeout(function() {
console.log('watchers: ' + $rootScope.$$watchersCount)
}, 1000);
});
angular.element(document).ready(function() {
angular.bootstrap(document, ['app']);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.1/angular.js"></script>
<div ng-controller="myController as $ctrl">
<form name="$ctrl.myForm" novalidate>
<label>Name :
<input name="test1" ng-model="$ctrl.model.name">
</label>
<label>age :
<input name="test2" ng-model="$ctrl.model.age">
</label>
<label>Pristine: {{ $ctrl.myForm.$pristine }}</label>
<div><pre>
</pre>
</div>
</form>
</div>
When your controller initialises:
constructor() {
super(arguments);
this._copy = angular.copy(this._formModel);
}
Then you can place a watch on the model.
this._$scope.$watch('this._formModel', (new, old) => {
if (_.eq(this._copy, this._formModel)) {
formObject.$setPristine();
}
});
If the copy is the same as the model, it's still pristine.
Edit: 2nd option is to add ngChange to each input to call a method on your controller, and then do the same procedure as above. This still relies on your copying the original (blank) model in the constructor.
<input ng-change="vm.noticeInputChange(t)" id="some_element" class="some_class" />
Then in the controller:
noticeInputChange() {
if (_.eq(this._copy, this._formModel)) {
formObject.$setPristine();
}
}
That should do the same, but as has been pointed out, the $watch might become quite expensive depending on the size of your form. Also, as someone here pointed out, the _.eq() is a lodash method

Resetting the model values [duplicate]

I have a simple form like so:
<form name="add-form" data-ng-submit="addToDo()">
<label for="todo-name">Add a new item:</label>
<input type="text" data-ng-model="toDoName" id="todo-name" name="todo-name" required>
<button type="submit">Add</button>
</form>
with my controller as follows:
$scope.addToDo = function() {
if ($scope.toDoName !== "") {
$scope.toDos.push(createToDo($scope.toDoName));
}
}
what I'd like to do is clear the text input after submission so I simply clear the model value:
$scope.addToDo = function() {
if ($scope.toDoName !== "") {
$scope.toDos.push(createToDo($scope.toDoName));
$scope.toDoName = "";
}
}
Except now, because the form input is 'required' I get a red border around the form input. This is the correct behaviour, but not what I want in this scenario... so instead I'd like to clear the input and then blur the input element. Which leads me to:
$scope.addToDo = function() {
if ($scope.toDoName !== "") {
$scope.toDos.push(createToDo($scope.toDoName));
$scope.toDoName = "";
$window.document.getElementById('todo-name').blur();
}
}
Now, I know that modifying the DOM from the controller like this is frowned upon in the Angular documentation - but is there a more Angular way of doing this?
When you give a name to your form it automatically gets added to the $scope.
So if you change your form name to "addForm" ('cause I don't think add-from is a valid name for angular, not sure why), you'll have a reference to $scope.addForm.
If you use angular 1.1.1 or above, you'll have a $setPristine() method on the $scope.addForm. which should recursively take care of resetting your form. or if you don't want to use the 1.1.x versions, you can look at the source and emulate it.
For those not switching over to 1.1.1 yet, here is a directive that will blur when a $scope property changes:
app.directive('blur', function () {
return function (scope, element, attrs) {
scope.$watch(attrs.blur, function () {
element[0].blur();
});
};
});
The controller must now change a property whenever a submit occurs. But at least we're not doing DOM manipulation in a controller, and we don't have to look up the element by ID:
function MainCtrl($scope) {
$scope.toDos = [];
$scope.submitToggle = true;
$scope.addToDo = function () {
if ($scope.toDoName !== "") {
$scope.toDos.push($scope.toDoName);
$scope.toDoName = "";
$scope.submitToggle = !$scope.submitToggle;
}
};
}
HTML:
<input type="text" data-ng-model="toDoName" name="todo-name" required
blur="submitToggle">
Plnkr
I have made it work it as below code.
HTML SECTION
<td ng-show="a">
<input type="text" ng-model="e.FirstName" />
</td>
Controller SECTION
e.FirstName= '';

how to watch changes on hidden fields with Angular?

I have dropdown made from divs. It's not my doing, is from external agency. But this is not important. The div with a dropdown looks like this:
<label class='label-block' for='kraj-dokument'>Kraj wydania dokumentu tożsamości *</label>
<input type='hidden' name='kraj-dokument' id='kraj-dokument' value="" drop-down-validation-directive /> <div class="select kraj-dokument" data-destination="kraj-dokument">
<p class="label">Wybierz z listy</p>
<div class="options">
<p class="option" data-ng-repeat="country in countries">{{country.Name}}</p>
</div>
</div>
When you click on a choice in this dropdown an external javascript is adding th the hidden field attribute called value and it insert a text from the choise to that value. As you can see I have a directive on that hidden inputu which looks like this (after several searches, and everything):
myapp.directive("dropDownValidationDirective", function () {
return function(scope, elem, attr, ctrl) {
scope.$watch(attr['value'], function(nv) {
elem.val(nv);
}
);
}
});
The problem is when I'm doing watch nothing happens, the value is not geting watched although the change is seen when debuging this in chrome. By the way. I'm trying to also do a validation on this dropdowwn. My idea was to check if this value is filled or not and tell to the user to fill the dropdown by adding a class to it wich marks this thing red. Is that the way to do it?
UPDATE I forgot to add, and I think this is also important that the click event on that div is done with the mentioned external javascript. I'm pasting it below. I've put the external javascript functionality into the service:
myApp.service('DropDownService', function () {
this.renderDropDown = function () {
function initEvents() {
var selectClicked = $(".selectClicked");
$(".select").each(function () {
var destination = $(this).attr("data-destination");
var option = $(this).find(".option");
var options = $(this).find(".options");
var label = $(this).find(".label");
var select = $(this);
label.click(function () {
if (label.hasClass("clicked")) {
$(".select .options").hide();
$(".select .label").removeClass("clicked");
$(".select").removeClass("clicked");
selectClicked.removeClass("clicked");
} else {
$(".select .label").removeClass("clicked");
$(".select").removeClass("clicked");
label.addClass("clicked");
select.addClass("clicked");
selectClicked.addClass("clicked");
$(".select .options").hide();
options.show();
}
});
option.unbind("click").bind("click", function () {
$("#" + destination).attr("value", $(this).text());
label.text($(this).text());
options.hide();
$(".select .label").removeClass("clicked");
$(".select").removeClass("clicked");
$(".select").removeClass("error");
selectClicked.removeClass("clicked");
});
});
}
angular.element(document).ready(function () {
initEvents();
if (navigator.appVersion.indexOf("Mac") !== -1) {
$('body').addClass("MacOS");
}
});
}
Just use interpolation & attrs.$observe instead:
HTML:
<div ng-controller="AppController">
<label class="label-block" for="kraj-dokument">Kraj wydania dokumentu tożsamości *</label>
<input type="hidden" name="kraj-dokument" id="kraj-dokument" value="{{ TotalPrice }}" drop-down-validation-directive />
<div class="select kraj-dokument" data-destination="kraj-dokument" ng-click="AddItem()">
Click to change and see console output
</div>
</div>
JS:
var app = angular.module('my-app', [], function() {})
app.controller('AppController', function($scope) {
$scope.Quantity = 0;
$scope.TotalPrice = 0;
$scope.Price = 100;
$scope.AddItem = function() {
$scope.Quantity++;
$scope.TotalPrice = $scope.Price * $scope.Quantity;
};
});
app.directive("dropDownValidationDirective", function() {
return {
link: function(scope, elem, attrs, ctrl) {
attrs.$observe('value', function(nv) {
console.log(nv);
});
}
}
});
Working example: http://jsfiddle.net/ghd9c8q3/56/
I'm not sure the way you're doing things is the best way, but I know why your watch doesn't do anything.
$scope.$watch() expects either a function, or an expression.
If you pass a function, it's called at each digest loop and its result is the value passed to the change listener.
If you pass an expression, it's evaluated on the scope, and the result of the evaluation is the value passed to the change listener.
So what you actually need is:
scope.$watch(function() {
return attr['value'];
}, function(nv) {
elem.val(nv);
});

Pro/con of using Angular directives for complex form validation/ GUI manipulation

I am building a new SPA front end to replace an existing enterprise's legacy hodgepodge of systems that are outdated and in need of updating. I am new to angular, and wanted to see if the community could give me some perspective. I'll state my problem, and then ask my question.
I have to generate several series of check boxes based on data from a .js include, with data like this:
$scope.fieldMappings.investmentObjectiveMap = [
{'id':"CAPITAL PRESERVATION", 'name':"Capital Preservation"},
{'id':"STABLE", 'name':"Moderate"},
{'id':"BALANCED", 'name':"Moderate Growth"},
// etc
{'id':"NONE", 'name':"None"}
];
The checkboxes are created using an ng-repeat, like this:
<div ng-repeat="investmentObjective in fieldMappings.investmentObjectiveMap">
...
</div>
However, I needed the values represented by the checkboxes to map to a different model (not just 2-way-bound to the fieldmappings object). To accomplish this, I created a directive, which accepts a destination array destarray which is eventually mapped to the model. I also know I need to handle some very specific gui controls, such as unchecking "None" if anything else gets checked, or checking "None" if everything else gets unchecked. Also, "None" won't be an option in every group of checkboxes, so the directive needs to be generic enough to accept a validation function that can fiddle with the checked state of the checkbox group's inputs based on what's already clicked, but smart enough not to break if there is no option called "NONE". I started to do that by adding an ng-click which invoked a function in the controller, but in looking around stack overflow, I read people saying that its bad to put DOM manipulation code inside your controller - it should go in directives. So do I need another directive?
So far:
(html):
<input my-checkbox-group
type="checkbox"
fieldobj="investmentObjective"
ng-click="validationfunc()"
validationfunc="clearOnNone()"
destarray="investor.investmentObjective" />
Directive code:
.directive("myCheckboxGroup", function () {
return {
restrict: "A",
scope: {
destarray: "=", // the source of all the checkbox values
fieldobj: "=", // the array the values came from
validationfunc: "&" // the function to be called for validation (optional)
},
link: function (scope, elem, attrs) {
if (scope.destarray.indexOf(scope.fieldobj.id) !== -1) {
elem[0].checked = true;
}
elem.bind('click', function () {
var index = scope.destarray.indexOf(scope.fieldobj.id);
if (elem[0].checked) {
if (index === -1) {
scope.destarray.push(scope.fieldobj.id);
}
}
else {
if (index !== -1) {
scope.destarray.splice(index, 1);
}
}
});
}
};
})
.js controller snippet:
.controller( 'SuitabilityCtrl', ['$scope', function ( $scope ) {
$scope.clearOnNone = function() {
// naughty jQuery DOM manipulation code that
// looks at checkboxes and checks/unchecks as needed
};
The above code is done and works fine, except the naughty jquery code in clearOnNone(), which is why I wrote this question.
And here is my question: after ALL this, I think to myself - I could be done already if I just manually handled all this GUI logic and validation junk with jQuery written in my controller. At what point does it become foolish to write these complicated directives that future developers will have to puzzle over more than if I had just written jQuery code that 99% of us would understand with a glance? How do other developers draw the line?
I see this all over stack overflow. For example, this question seems like it could be answered with a dozen lines of straightforward jQuery, yet he has opted to do it the angular way, with a directive and a partial... it seems like a lot of work for a simple problem.
I don't want this question to violate the rules, so specifically, I suppose I would like to know: how SHOULD I be writing the code that checks whether "None" has been selected (if it exists as an option in this group of checkboxes), and then check/uncheck the other boxes accordingly? A more complex directive? I can't believe I'm the only developer that is having to implement code that is more complex than needed just to satisfy an opinionated framework. Is there another util library I need to be using?
I posted this on Programmers.StackExchange.com as per Jim's suggestion. In the meantime, I settled on a solution for handling all the tricky DOM manipulations.
I tried it both ways - handling the DOM event in the controller, and handling it via a directive:
(Via Controller) - .js code:
$scope.clearOnNone = function(groupName, $event) {
var chkboxArr = $('input[name^=' + groupName + ']'),
nonNoneValChecked = false,
targetElem = null,
labelText = "";
// get the target of the click event by looking at the <label> sibling's text
targetElem = event.target.nextElementSibling.textContent.trim();
// if target was the None option, uncheck all others
if (targetElem === "None") {
chkboxArr.each(function() {
labelText = this.nextElementSibling.textContent.trim();
if (labelText !== "None") {
this.checked = false;
}
});
}
// if the target was anything BUT the None option, uncheck None
else {
chkboxArr.each(function() {
labelText = this.nextElementSibling.textContent.trim();
if (labelText === "None") {
this.checked = false;
}
});
}
};
(Via Controller) - html code:
<div ng-repeat="investmentObjective in fieldMappings.secondaryInvestmentObjectiveMap">
<input checkbox-group
type="checkbox"
name="secondaryInvestmentObjective"
ng-click="validationfunc('secondaryInvestmentObjective', $event)"
validationfunc="clearOnNone('secondaryInvestmentObjective', $event)"
fieldobj="investmentObjective"
destarray="suitabilityHolder.suitability.secondaryInvestmentObjective" />
<label class="checkbox-label"
popover-title="{{investmentObjective.name}}"
popover="{{investmentObjective.help}}"
popover-trigger="mouseenter">{{investmentObjective.name}}
</label>
</div>
(Via Controller) - directive code:
.directive("checkboxGroup", function () {
return {
restrict: "A",
scope: {
destarray: "=", // the source of all the checkbox values
fieldobj: "=", // the array the values came from
validationfunc: "&" // the function to be called for validation (optional)
},
link: function (scope, elem, attrs) {
if (scope.destarray.indexOf(scope.fieldobj.id) !== -1) {
elem[0].checked = true;
}
elem.bind('click', function () {
var index = scope.destarray.indexOf(scope.fieldobj.id);
if (elem[0].checked) {
if (index === -1) {
scope.destarray.push(scope.fieldobj.id);
}
}
else {
if (index !== -1) {
scope.destarray.splice(index, 1);
}
}
});
}
};
})
I then decided that I hated the event.target.nextElementSibling.textContent.trim() lines... I feel like I should be double checking that all of those methods exist, or using a try/catch. So I rewrote the directive to include the logic from the controller:
(via directive) - html code:
<div ng-repeat="otherInvestment in fieldMappings.otherInvestmentsMap">
<input type="checkbox"
checkbox-group
groupname="otherInvestment"
labelvalue="{{otherInvestment.name}}"
fieldobj="otherInvestment"
destarray="suitabilityHolder.suitability.otherInvestment" />
<label class="checkbox-label"
popover-title="{{otherInvestment.name}}"
popover="{{otherInvestment.help}}"
popover-trigger="mouseenter">{{otherInvestment.name}}
</label>
</div>
(via directive) - directive code:
.directive("checkboxGroup", function () {
return {
restrict: "A",
scope: {
destarray: "=", // the source of all the checkbox values
fieldobj: "=", // the array the values came from
groupname: "#", // the logical name of the group of checkboxes
labelvalue: "#" // the value that corresponds to this checkbox
},
link: function (scope, elem, attrs) {
// Determine initial checked boxes
// if the fieldobj.id exists in the destarray, check this checkbox
if (scope.destarray.indexOf(scope.fieldobj.id) !== -1) {
elem[0].checked = true;
}
// Update array on click
elem.bind('click', function () {
// store the index where the fieldobj.id exists in the destarray
var index = scope.destarray.indexOf(scope.fieldobj.id),
// get the array of checkboxes that form this checkbox group
chkboxArr = $('input[groupname^=' + scope.groupname + ']');
// Add if checked
if (elem[0].checked) {
if (scope.labelvalue === "None") {
// loop through checkboxes and uncheck all the ones that are not "None"
chkboxArr.each(function() {
// have to noodle through the checkbox DOM element to get at its attribute list
// - is there a cleaner way?
var tmpLabelValue = this.attributes.labelvalue.nodeValue.trim();
if (tmpLabelValue !== "None") {
this.checked = false;
}
});
}
// if the target was anything BUT the None option, uncheck None
else {
chkboxArr.each(function() {
var tmpLabelValue = this.attributes.labelvalue.nodeValue.trim();
if (tmpLabelValue === "None") {
this.checked = false;
}
});
}
if (index === -1) {
// add the id to the end of the dest array
// **will not maintain original order if several are unchecked then rechecked**
scope.destarray.push(scope.fieldobj.id);
}
}
// Remove if unchecked
else {
if (index !== -1) {
scope.destarray.splice(index, 1);
}
}
});
}
};
})
In retrospect, I suppose I prefer to house all the code in a directive, even though I think it's less intuitive and more complex than tossing all the handling in the controller via jQuery. It cuts out the clearOnNone() function from the controller, meaning that all the code that deals with this functionality it in the html markup and the directive.
I am not a fan of code like this.attributes.labelvalue.nodeValue.trim(), which I still ended up with in my directive. For scenarios like mine, where the business unit has certain requirements that (no other way to put it) are tedious and cumbersome, I don't know that there is really a 'clean' way to code it all up.
I'm still new to AngularJS but I this case I think I would solve it by using either an ng-click handler or $scope.$watch to update the state of the NONE model whenever the other models change.
Using ng-click
I've whipped up a jsFiddle that shows how it could work with ng-click:
http://jsfiddle.net/Dzj6K/1/
HTML:
<div ng-controller="myCtrl">
<div ng-repeat="objective in objectives">
<label><input type="checkbox" ng-model="objective.selected" ng-click="click(objective)" /> {{objective.name}}</label>
</div>
</div>
JavaScript:
var app = angular.module('myApp', []);
function myCtrl($scope) {
$scope.objectives = [
{'id':"CAPITAL PRESERVATION", 'name':"Capital Preservation"},
{'id':"STABLE", 'name':"Moderate"},
{'id':"BALANCED", 'name':"Moderate Growth"},
{'id':"NONE", 'name':"None"}
];
$scope.click = function(objective) {
if (objective.id === "NONE") {
if (objective.selected) {
angular.forEach($scope.objectives, function(objective) {
if (objective.id !== "NONE") {
objective.selected = false;
}
});
}
} else {
angular.forEach($scope.objectives, function(objective) {
if (objective.id === "NONE") {
objective.selected = false;
}
});
}
};
}
Using $scope.$watch
And a version of the jsFiddle that shows how it could work with $scope.$watch:
http://jsfiddle.net/Dzj6K/
HTML:
<div ng-controller="myCtrl">
<div ng-repeat="objective in objectives">
<label><input type="checkbox" ng-model="objective.selected" /> {{objective.name}}</label>
</div>
</div>
JavaScript:
var app = angular.module('myApp', []);
function myCtrl($scope) {
$scope.objectives = [
{'id':"CAPITAL PRESERVATION", 'name':"Capital Preservation"},
{'id':"STABLE", 'name':"Moderate"},
{'id':"BALANCED", 'name':"Moderate Growth"},
{'id':"NONE", 'name':"None"}
];
$scope.$watch('objectives', function() {
var anySelected = false;
var noneModel = null;
angular.forEach($scope.objectives, function(objective) {
if (objective.id === "NONE") {
noneModel = objective;
} else {
anySelected = anySelected || objective.selected;
}
});
if (noneModel) {
noneModel.selected = !anySelected;
}
}, true);
}

Reset form to pristine state (AngularJS 1.0.x)

A function to reset form fields to pristine state (reset dirty state) is on the roadmap for AngularJS 1.1.x. Unfortunately such a function is missing from the current stable release.
What is the best way to reset all form fields to their initial pristine state for AngularJS 1.0.x.?
I would like to know if this is fixable with a directive or other simple workaround. I prefer a solution without having to touch the original AngularJS sources. To clarify and demonstrate the problem, a link to JSFiddle. http://jsfiddle.net/juurlink/FWGxG/7/
Desired feature is on the Roadmap - http://blog.angularjs.org/2012/07/angularjs-10-12-roadmap.html
Feature request - https://github.com/angular/angular.js/issues/856
Proposed solution Pull request - https://github.com/angular/angular.js/pull/1127
Updated with possible workaround
Good enough workaround?
I just figured out I can recompile the HTML part and put it back into the DOM. It works and it's fine for a temporarily solution, but also as #blesh mentioned in the comments:
Controllers should be used for business logic only, not for DOM!
<div id="myform">
<form class="form-horizontal" name="form">
</form>
</div>
And in my Controller on resetForm():
Save the original untouched HTML
Recompile the saved original HTML
Remove the current form from the DOM
Insert the new compiled template into the DOM
The JavaScript:
var pristineFormTemplate = $('#myform').html();
$scope.resetForm = function () {
$('#myform').empty().append($compile(pristineFormTemplate)($scope));
}
I think it's worth mentioning that in later versions of Angular (e.g. 1.1.5), you can call $setPristine on the form.
$scope.formName.$setPristine(true)
This will set all the form controls to pristine state as well.
FormController.$setPristine
Solution without a workaround
I came up with a solution which uses AngularJS without any workaround. The trick here is to use AngularJS ability to have more than one directive with the same name.
As others mentioned there is actually a pull request (https://github.com/angular/angular.js/pull/1127) which made it into the AngularJS 1.1.x branch which allows forms to be reset. The commit to this pull request alters the ngModel and form/ngForm directives (I would have liked to add a link but Stackoverflow doesn't want me to add more than two links).
We can now define our own ngModel and form/ngForm directives and extend them with the functionality provided in the pull request.
I have wrapped these directives in a AngularJS module named resettableForm. All you have to do is to include this module to your project and your AngularJS version 1.0.x behaves as if it was an Angular 1.1.x version in this regard.
''Once you update to 1.1.x you don't even have to update your code, just remove the module and you are done!''
This module also passes all tests added to the 1.1.x branch for the form reset functionality.
You can see the module working in an example in a jsFiddle (http://jsfiddle.net/jupiter/7jwZR/1/) I created.
Step 1: Include the resettableform module in your project
(function(angular) {
// Copied from AngluarJS
function indexOf(array, obj) {
if (array.indexOf) return array.indexOf(obj);
for ( var i = 0; i < array.length; i++) {
if (obj === array[i]) return i;
}
return -1;
}
// Copied from AngularJS
function arrayRemove(array, value) {
var index = indexOf(array, value);
if (index >=0)
array.splice(index, 1);
return value;
}
// Copied from AngularJS
var PRISTINE_CLASS = 'ng-pristine';
var DIRTY_CLASS = 'ng-dirty';
var formDirectiveFactory = function(isNgForm) {
return function() {
var formDirective = {
restrict: 'E',
require: ['form'],
compile: function() {
return {
pre: function(scope, element, attrs, ctrls) {
var form = ctrls[0];
var $addControl = form.$addControl;
var $removeControl = form.$removeControl;
var controls = [];
form.$addControl = function(control) {
controls.push(control);
$addControl.apply(this, arguments);
}
form.$removeControl = function(control) {
arrayRemove(controls, control);
$removeControl.apply(this, arguments);
}
form.$setPristine = function() {
element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS);
form.$dirty = false;
form.$pristine = true;
angular.forEach(controls, function(control) {
control.$setPristine();
});
}
},
};
},
};
return isNgForm ? angular.extend(angular.copy(formDirective), {restrict: 'EAC'}) : formDirective;
};
}
var ngFormDirective = formDirectiveFactory(true);
var formDirective = formDirectiveFactory();
angular.module('resettableForm', []).
directive('ngForm', ngFormDirective).
directive('form', formDirective).
directive('ngModel', function() {
return {
require: ['ngModel'],
link: function(scope, element, attrs, ctrls) {
var control = ctrls[0];
control.$setPristine = function() {
this.$dirty = false;
this.$pristine = true;
element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS);
}
},
};
});
})(angular);
Step 2: Provide a method on your controller which resets the model
Please be aware that you must reset the model when you reset the form. In your controller you can write:
var myApp = angular.module('myApp', ['resettableForm']);
function MyCtrl($scope) {
$scope.reset = function() {
$scope.form.$setPristine();
$scope.model = '';
};
}
Step 3: Include this method in your HTML template
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<form name="form">
<input name="requiredField" ng-model="model.requiredField" required/> (Required, but no other validators)
<p ng-show="form.requiredField.$errror.required">Field is required</p>
<button ng-click="reset()">Reset form</button>
</form>
<p>Pristine: {{form.$pristine}}</p>
</div>
</dvi>
EDIT... I'm removing my old answer, as it was not adequate.
I actually just ran into this issue myself and here was my solution: I made an extension method for angular. I did so by following a bit of what $scope.form.$setValidity() was doing (in reverse)...
Here's a plnkr demo of it in action
Here's the helper method I made. It's a hack, but it works:
angular.resetForm = function (scope, formName, defaults) {
$('form[name=' + formName + '], form[name=' + formName + '] .ng-dirty').removeClass('ng-dirty').addClass('ng-pristine');
var form = scope[formName];
form.$dirty = false;
form.$pristine = true;
for(var field in form) {
if(form[field].$pristine === false) {
form[field].$pristine = true;
}
if(form[field].$dirty === true) {
form[field].$dirty = false;
}
}
for(var d in defaults) {
scope[d] = defaults[d];
}
};
Hopefully this is helpful to someone.
Your form fields should be linked to a variable within your $scope. You can reset the form by resetting the variables. It should probably be a single object like $scope.form.
Lets say you have a simple form for a user.
app.controller('Ctrl', function Ctrl($scope){
var defaultForm = {
first_name : "",
last_name : "",
address: "",
email: ""
};
$scope.resetForm = function(){
$scope.form = defaultForm;
};
});
This will work great as long as your html looks like:
<form>
<input ng-model="form.first_name"/>
<input ng-model="form.last_name"/>
<input ng-model="form.address"/>
<input ng-model="form.email"/>
<button ng-click="resetForm()">Reset Form</button>
</form>
Maybe I'm not understanding the issue here, so if this does not address your question, could you explain why exactly?
Here I have found a solution for putting the from to its pristine state.
var def = {
name: '',
password: '',
email: '',
mobile: ''
};
$scope.submited = false;
$scope.regd = function (user) {
if ($scope.user.$valid) {
$http.post('saveUser', user).success(function (d) {
angular.extend($scope.user, def);
$scope.user.$setPristine(true);
$scope.user.submited = false;
}).error(function (e) {});
} else {
$scope.user.submited = true;
}
};
Just write angular.extends(src,dst) ,so that your original object is just extends the blank object, which will appear as blank and rest all are default.
Using an external directive and a lot of jquery
app.controller('a', function($scope) {
$scope.caca = function() {
$scope.$emit('resetForm');
}
});
app.directive('form', function() {
return {
restrict: 'E',
link: function(scope, iElem) {
scope.$on('resetForm', function() {
iElem.find('[ng-model]').andSelf().add('[ng-form]').each(function(i, elem) {
var target = $(elem).addClass('ng-pristine').removeClass('ng-dirty');
var control = target.controller('ngModel') || target.controller('form');
control.$pristine = true;
control.$dirty = false;
});
});
}
};
});
http://jsfiddle.net/pPbzz/2/
The easy way: just pass the form into the controller function. Below the form "myForm" is referenced by this, which is equivalent to $scope.
<div ng-controller="MyController as mc">
<ng-form name="myform">
<input ng-model="mc.myFormValues.name" type="text" required>
<button ng-click="mc.doSometing(this.myform)" type="submit"
ng-disabled="myform.$invalid||myform.$pristine">Do It!</button>
</ng-form>
</div>
The Controller:
function MyController(MyService) {
var self = this;
self.myFormValues = {
name: 'Chris'
};
self.doSomething = function (form) {
var aform = form;
MyService.saveSomething(self.myFromValues)
.then(function (result) {
...
aform.$setPristine();
}).catch(function (e) {
...
aform.$setDirty();
})
}
}

Resources