Angularjs form.$dirty - angularjs

I'm able to find form data is changed or not using $dirty.
ex: I changed text box or drop down and then $dirty become true. If I reverted to old data still it is true. I need to know if my changes are reverted or not. Do we have any property in Angularjs? If property is true I want to enable save button otherwise it should be disable.
https://docs.angularjs.org/api/ng/type/form.FormController
I need to implement around 10 pages and each page has 10 text boxes and a couple of drop downs. So I don't want track each control manually in my pages.

You can try using this module: https://github.com/betsol/angular-input-modified
From the README file:
This Angular.js module adds additional properties and methods to the
ngModel and ngForm controllers, as well as CSS classes to the
underlying form elements to provide end-user with facilities to detect
and indicate changes in form data.
This extra functionality allows you to provide better usability with
forms. For example, you can add decorations to the form elements that
are actually changed. That way, user will see what values has changed
since last edit.
Also, you can reset an entire form or just a single field to it's
initial state (cancel all user edits) with just a single call to the
reset() method or lock new values (preserve new state) just by calling
overloaded $setPristine() method.
DISCLAIMER: I haven't tried it myself and I notice the author overwrites the ngModel directive instead of adding a decorator, which could be dangerous...but at the very least, you can look at the source and get an idea of how to write your own service or directive with similar functionality.

Even though it does not follow the usage of $dirty, but an implementation similar to this might be helpful for you in the case of a Save button on update.
Inside your html:
<form name="testForm" ng-controller="ExampleController" ng-submit=" save()">
<input ng-model="val" ng-change="change()"/>
<button ng-disabled="disableSave">Save</button>
</form>
Inside your controller:
.controller('ExampleController', ['$scope', function($scope) {
$scope.disableSave = true; // Keep save button disabled initially
$scope.val = 'Initial'; // Initial value of the variable
var copyVal = $scope.val; // Copy Initial value into a temp variable
$scope.change = function() {
$scope.disableSave = $scope.val === copyVal;
};
$scope.save = function() {
// Save the updated value (inside $scope.val)
console.log($scope.val);
// Re-disable the input box (on successful updation)
copyVal = $scope.val;
$scope.disableSave = true;
};
}]);
Here is a working plunkr for the same.

Related

Angular scope variable update not reflected in UI

We are working on an HTML page which makes use of a Bootstrap tooltip on a certain <span> tag. For those who have not heard of tooltip, it is a popup of sorts which appears when hovering over the element to which it is attached. Here is a screenshot showing the <span> in question, and what happens on hover:
The premise behind adding the tooltip was that in the event that we truncate the text, the tooltip would provide an option for viewing the entire text.
However, we would now like to condtionally show the tooltip only when there is no ellipsis in the text. We defined the tooltip-enable property in the <span>:
<span uib-tooltip="{{someName}}" tooltip-placement="right" tooltip-enable="{{showToolTip}}">{{someNameShortened}}</span>
The key thing here is tooltip-enable="{{showToolTip}}", which binds the property to a scoped variable in the controller for this page. And here is the relevant (and abbreviated) controller code:
mainApp.controller('repoListController',['$scope', '$rootScope', ...,
function($scope,$rootScope, ...) {
$scope.showToolTip = false;
var repositoryList= function(){
repositoryService.getRepositoryList(function(data) {
var repoList = data;
repoList.shortenedDisplayName = repositoryService.getShortRepoName(repoList.repoName, DISPLAY_NAME_MAX_LENGTH);
// if the repository's name be sufficiently large (i.e. it has an ellipsis)
// then show the tooltip. Otherwise, the default value is false (see above)
if (repoList.repoName.length > DISPLAY_NAME_MAX_LENGTH) {
$scope.showTooltip = true;
}
});
}
repositoryList();
}]);
Based on the research I have done, the common solution for why a change to a scoped variable is not reflected in the UI is to run $scope.$apply(), or some variation on this. Running apply(), as I understand it, will tell Angular JS to do a digest cycle, which will propagate changes in the scope to the UI. However, trying to do an apply() from the code which toggles showToolTip resulted in errory. I inspected the value of $scope.$root.$$phase while running the code which updates the showToolTip variable, and the phase was digest.
So now I am at a loss to explain this. If the code is already in a digest, then why would changes not be reflected in the UI? Also, if the code is already in digest, then how could I force Angular to sync the changes to the UI?
Two things need fixing...
Don't use string interpolation for your boolean showToolTip
<span uib-tooltip="{{someName}}" tooltip-placement="right"
tooltip-enable="showToolTip">{{someNameShortened}}</span>
JavaScript variables / properties are case sensitive. In your getRepositoryList handler, you have $scope.showTooltip. It should be $scope.showToolTip (two capital "T"s)
Crappy Plunker demo ~ http://plnkr.co/edit/W7tgJmeQAJj0fmfT72PR?p=preview

Using a variable for ng-required doesn't re-evaluate fields

I have a form where my intent is for required fields to not always be enforced. For example if the user is saving the document as a draft they can enter as little information as they like, if they try and publish the document then they have to enter all the required fields. I'm using a boolean on the controller which changes according to which button has been pressed e.g.
<input type="text" ng-model="field2" ng-required="enforceRequired" />
The problem is that the fields are not re-evaluated when the boolean changes so the form is submitted and then it becomes invalid. Please see this JSFiddle to see what I mean. If you fill in field1 and then click publish it will succeed on the first click and THEN become invalid.
How can I force the validation to run before the form is submitted?
Yarons is right, you are changing the value too late, by late I mean after the form validations has been run. What you can do as a workaround is, after changing the required value, let angular do another cycle and then show your alert. This can be done via $timeout service, although I must mention that it is usually not a good practise to change the flow of your digest actions. It gets pretty messy pretty soon.
Change your publish function like this (and don't forget to inject $timeout)
$scope.publish = function () {
$scope.enforceRequired = true;
$timeout(function () {
if ($scope.form.$valid) {
alert("Published!");
}
});
};
Working fiddle: http://jsfiddle.net/bh9q00Le/14/
The problem is that you are changing the value of enforceRequired in the middle of the digest loop, so the watchers are not re-rendered before you check the input fields' validity (read about digest here).
If you want to get around it, I suggest one of the following methods:
change the value of enforceRequired before you call saveDraft or publish. see example.
call $scope.$apply() after you change the value of enforceRequired. see another example.

angularjs - streamline form (automatic) submission based on dirty scope

Problem space
I have a problem where I'm submitting a form based on criteria being fulfilled, rather than having a form submission button.
Let's say I have 3 drop downs, the first two are grouped but one needs to be selected, meaning I can select one or the other but I can't leave them empty, the 3rd one is a required field.
After that, the page automatically fetches in results.
Lets say I have checkboxes and a few more dropdowns. Any future selections on the 3 dropdowns mentioned, checkboxes, and dropdowns automatically filters the results.
What I know
Now after reading angular documentation, I was checking up on $dirty, $pristine and operations on both, like $setDirty and $setPristine; however, it seems that this is for a FormController
So I'm assuming this is useful for an entire scope. I didn't see any inclination that I can figure out for selected scopes.
What I have so far
So basically, I was hoping that I'd be making use of the scope's tracking features, but I don't know much about it. I created a single controller for my application and a single scope, since that's what seemed easiest for me. I have 3rd party plugins that play a role into the scope like:
$scope.3rdpartyConfig = {
prop1: [],
prop2: getData()
}
I don't think something like that would be useful in checking to see form submission if I was going to check the $dirty state of my form.
Then I thought about the old way I used to do things, but "angularlizing" it:
so I'd have something like:
<input type="checkbox" ng-model="state.Checked" ng-change="checkIfWeCanSubmitThenSubmit()" id="ng-change-example1" />
So I'd be having ng-changes and ng-clicks all over my html form, hitting that function, where the function would look like this pseudocode:
$scope.checkIfWeCanSubmitThenSubmit= function() {
var validated = false;
//check to see if dropdown1 or dropdown2 are selected
//check to see if dropdown3 is selected
// add more here per requirement
//if the above are true, then validated = true
if (validated)
{
//add dropdown4 and 5, and checkbox groups into filter
}
submit();
}
But I was thinking this isn't the angular way of doing things since this certainly isn't facilitated.
I was hoping that the scope would offer some kind of way, where I can check to see what pieces of my scope is dirty or not before I can submit and fetch data, or if there is a better way than appending this function to every html element; like having some kind of scope tracker that I can check up on and watch.
Which reminds me, I don't want to have a series of $scope.$watch either, its just that it'd be way too much work to bind to every piece of html code, unless there's way to watch the scope of a collection of specific scope variables, then, I wouldn't mind.
like (forgive the pseudocode):
$scope.$watch('dropdown1, dropdown2, dropdown4', function(dirty, pristine)
{
if (dirty)
{ blah blah blah }
});
Edit (2/28/2013):
I tried doing it this way:
$scope.masterCriteria =
[
{ DropDown1: $scope.AppModel.Dropdown1},
{ DropDown2: $scope.AppModel.Dropdown2 },
{ DropDown3: $scope.AppModel.Dropdown3 },
{ Checkbox1: $scope.AppModel.Checkbox1 },
{ Checkbox2: $scope.AppModel.Checkbox2 }
];
$scope.$watch('masterCriteria', function (newVal) {
if (newVal) { logger.info("did I change?"); }
}, true);
The watcher detected nothing, and any values I changed to the scope of AppModel wasn't being picked up in the $watch. Was worth a try, still trying to figure this out.
You can slightly change your model and group fields related to input form together. Put them into single object. Like this:
$scope.state = { checkbox1: false, checkbox2: true, ... }
Later bind input boxes to field of state object:
<input ng-model="state.checkbox1" ... >
And watch state object to catch all updates of nested fields:
$scope.$watch('state', ...
JsFiddle example here

Reset file selection with single file upload with blueimp plugin and AngularJS

My settings are as following:
singleFileUploads: false,
// To limit the number of files uploaded with one XHR request,
// set the following option to an integer greater than 0:
limitMultiFileUploads: undefined,
maxNumberOfFiles: 1,
I am trying to allow my client to upload a personal image, after successfully uploading this image,
I would like to reset the file selection, and allow my user to upload another image instead of the first one.
Before setting the maxNumberOfFiles setting to 1, the plugin just tried to upload any new file that the user would select along with the old ones, now, it allows the user to upload a file only once per each visit to the page which contains the plugin instance.
Here is what I've tried so far:
$scope.model.resetFiles = function(){
angular.forEach(
angular.element("input[type='file']"),
function(inputElem){
// angular.element(inputElem).val(null);
angular.element(inputElem).find('.files').empty();
angular.element(inputElem).scope().clear()
});
}
For security reasons, Javascript doesn't allow you to clear the file fields in the manner you'd think. It seems as if creating a new element and replacing the old file field will do the trick though. Check out this link for some more detail, and a code example.
I've seen some people suggesting wrapping the element in a <form> tag and using jQuery's reset() function to reset the form, but if you're not using the full version of jQuery, I don't believe this is included with the jqLite that's used by default by AngularJS.
I've found a temporary solution until the plugin gets updated for better Angular support.
var filePicker = null;
$scope.options = {
done: function (e, data) {
filePicker = data;
}
};
$scope.clearFiles = function () {
filePicker.scope.clear(filePicker.files);
};
Just add the options to the directive like this:
<form file-upload="options" action="import/excelupload" method="POST" enctype="multipart/form-data">
I simply solved it using this simple form reset
document.forms["mediaUploader"].reset();
my directive on the file input was autobinding the files to a scope variable.
after calling forms.reset(); also autobinds files.length to 0 zero so works marvelously!
also check out
https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement.reset
If file field is wrapped inside form tag then use standard html form reset button type.
<button type="reset" value="Reset">Reset</button>
Also you can put ng-click directive on reset button to perform any changes in $scope.
<button type="reset" value="Reset" ng-click="formReset()">Reset</button>
$scope.resetForm = function (){
// Your business logic after form reset.
}

AngularJS : What's the Angular way to interact with a form?

I understand one of the key principals of Angular is:
Thou shalt not reference thy DOM from withinst thou's controllers.
I'm trying to process a credit card payment, which requires the following steps:
User fills out a form, and clicks a submit button
A portion of that form is sent to our servers, which starts a transaction with the payment gateway
The response from our servers updates values in the form, which must then be submitted directly to the payment gateway, via a form POST.
Other stuff happens.
In this scenario, how do I:
Update the data in the form (without referencing the form from the controller)
Get the form to submit?
The form binds to a model on my controller, so I've tried something like the following:
<form action="{{paymentModel.urlFromTheResponse}}">
<input type="hidden" name="accessCode" value="{{paymentModelaccessCodeFromResponse}}" />
<button ng-click="startTransaction(paymentModel)"></button>
</form>
// in my success handler
.success(function(data) {
paymentModel.urlFromTheResponse = data.url;
paymentModel.accessCode = data.accessCode;
$scope.apply();
}
the theory being here that if I can immediately get the form into the correct state via databinding, I can then do something to submit the form. However, this throws an error:
Digest already in progress
What's the Angular way to support this type of flow? It seems I'm required to interact directly with the DOM, which goes against the nature of controllers.
As others have stated, you shouldn't need to call $scope.$apply() because the form should already be tied to angular by setting ng-model attributes on each of the fields.
However, occasionally it is necessary to call $scope.$apply() to update display when data is pulled in from some other source outside of angular...
In those cases, I've had great luck with this:
// This method will be inherited by all other controllers
// It should be used any time that $scope.$apply() would
// otherwise be used.
$scope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if(phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
I place that in my outermost controller, so all other controllers on the page inherit the function from it.. Any time I find I need a call to apply, I instead call $scope.safeApply() which will call apply if there is not already an apply or digest in progress, otherwise, those changes will already be picked up by the currently running apply/digest.
In your code I would change this:
<input type="hidden" name="accessCode" value="{{paymentModelaccessCodeFromResponse}}" />
To this:
<input type="hidden" name="accessCode" ng-model="paymentModel.accessCode" />
I would probably also remove the form action, and instead add something like this in the controller:
$scope.$watch('paymentModel.accessCode', function() {
// Fire off additional form submission here.
})
The error is generated because your success callback is already "inside Angular", so $scope.apply() will be called automatically for you.
If you use ng-model (instead of value) on your form elements, then you can modify the model/$scope properties in your success callback and the form will automatically update (due to two-way databinding via ng-model). However, instead of trying to submit the form, why not just use the $http or $resource service inside your controller to call the web service? (That's why I asked if the user needed to be involved in my comment.)
Assuming you are using something like $http, you are already inside of the angular scope and should not need to manually call $scope.apply(); as you are inside of the angular execution already.
You should be able to ditch the $scope.apply() and simply have an
.success(function(data) {
paymentModel.urlFromTheResponse = data.url;
paymentModel.accessCode = data.accessCode;
$http.post("/finalstep",paymentModel).success(function(data)
{
// other stuff
});
}

Resources