Need to re-render view and re-initialise controller in AngularJS - angularjs

I have a form that I want to reset. If I use the $setPristine method in Angular, this gets me some of the way.
However, if you look at the following isolated case, you will see that when you press the reset button to reset the form, a save message will be logged to the console. This is where I would write the code to save any changes made to the form to the server.
http://jsbin.com/OLIziRe/14/edit
In this example, save is logged when the value is different. This includes when the form is set to pristine, as the previous value will be whatever it was before the form was reset, and the new value will be undefined.
I do not want this behaviour. I only want to log the save message if there are changes — if the form is reset, there should be no "previous value".
The only solution I can come up with is to do some trickery in order to re-render the view and re-initialise the controller, thus losing all previous state.
http://jsbin.com/OLIziRe/12/edit
What would be the AngularJS way to do this?

You can do just like that with your first solution :
var app = angular.module('myApp', []);
app.directive('someDirective', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ctrl) {
scope.$watch(function () {
return ctrl.$modelValue;
}, function (newValue, lastValue) {
// Only save if the values do not match
if(scope.someForm.$dirty){
console.log(newValue !== lastValue ? 'save' : '');
}
});
}
}
});
Before the console.log you check if the form is in $dirty state, i.e if the user have set a value to the form.
When you do a $setPristine() you reset the form in $pristine state so the $dirty state is false.
Hope it helps !

Related

Why does the ngModel validator code seem to run before the scope.$watch changes?

I am attempting to create an AngularJS directive with a custom validator so that I can show error messages based on the validator. However, I am running into an error because it seems that the validator is running prior to the scope.$watch() per the console.log() messages I've input.
Here is the directive:
angular
.module('app')
.directive('validateRefundAmount', validateRefundAmount);
validateRefundAmount.$inject = [ 'AmountConversionService', '$q' ];
function validateRefundAmount(AmountConversionService, $q) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, control) {
scope.$watch('orderDetails.refundAmount', function(newValue, oldValue) {
if (newValue === oldValue) {
console.log(newValue, oldValue);
return;
}
// Get Already Refunded Amount
var refundedAmount = scope.orderDetails.refundedAmount;
// Converts Amount To Pure Integers, Removing Decimal
var totalPaymentAmount = AmountConversionService.prepareAmountForCalculations(scope.orderDetails.paymentAmount, 10);
var totalRefundAmount = AmountConversionService.prepareAmountForCalculations(newValue || 0);
// Add Already Refunded Amount to Previously Refunded Amount to Get Total Refund
if (refundedAmount) {
totalRefundAmount += AmountConversionService.prepareAmountForCalculations(refundedAmount);
}
control.$validators.refundAmountTooHigh = function() {
if (totalRefundAmount > totalPaymentAmount) {
return false
}
return true;
}
});
}
};
}
The element that this is applied to is an text input box that starts with no value. When i type '1' into the field the validator doesn't run. When I add a '2' to the field, making '12', the validator runs with '1' as the input. The same thing occurs when I add a '3' to the field making '123' -- it runs with '12', instead of the new value of '123'.
When I've inserted console.log() statements to see what is occurring when, it looks like the validator code runs and then the scope.watch() runs creating the new value after the validator has run but I don't know why or what to search for to find out.
There may be another way to do what I'm trying to do -- what I want to do is run the validator everytime the element value changes. I can't use bind on keypress due to keypress not detecting backspaces and may be able to use bind on keydown -- not sure.
Please check doc for $setViewValue, apparently $validators is used before ngModelValue update.
And your scope.$watch will only be invoked after $modelValue is updated.
https://docs.angularjs.org/api/ng/type/ngModel.NgModelController#$setViewValue
When $setViewValue is called, the new value will be staged for committing through the $parsers and $validators pipelines. If there are no special ngModelOptions specified then the staged value is sent directly for processing through the $parsers pipeline. After this, the $validators and $asyncValidators are called and the value is applied to $modelValue. Finally, the value is set to the expression specified in the ng-model attribute and all the registered change listeners, in the $viewChangeListeners list are called.

event.preventDefault() on $locationChangeStart doesn't work as expected

I have a strange problem with calling the e.preventDefault inside handler for a $locationChangeStart event. I have something like this:
var unregisterCallback = _this.$rootScope.$on('$locationChangeStart', function (e) {
e.preventDefault();
});
This is happens in a link function for one of the directivies. The problem is that when I click an anchor element with some path, it actually navigates to it and then back.
I'm using component router from Angular 1.5. This pretty much makes this usless, as I'm trying to show a confirmation dialog before user nagivates away without saving changes. The thing is, due to this re-navigation he looses all changes anyways. Any idea what's going on?
In the code you are trying to prevent the default event in the $locationChangeStart, but you don't want to navigate to a new page on click of a anchor() tag. In that case you can remove the href ="#" in the anchor tag and follow the below method
Link
Another method to prevent the default action for all the anchor tags you can have directive to do so
app.directive('a', function() {
return {
restrict: 'E', // restricts to html elment
link: function(scope, elm, attrs) {
if(attrs.ngClick || attrs.href === '' || attrs.href === '#'){
elm.on('click', function(ev){
ev.preventDefault(); // prevents the default functionality of the tag
});
}
}
};
});

Angularjs >1.3 $validator causes modelValue to go undefined

I am using $validator to write a custom form validation directive.
Currently it looks like this :
module.directive('tweetLength', function(URLDetector) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
var allowedCharacters;
allowedCharacters = parseInt(attrs.tweetLength);
ctrl.$validators.tweetLength = function(modelValue, viewValue) {
var result;
return result = URLDetector.urlAdjustedCharacterCount(modelValue) <= allowedCharacters;
};
}
};
});
It checks the model of the element it is attached to for the number of characters, whilst taking into account link shortening (so ng-minlength and ng-maxlength don't work). It returns false when the requirements aren't met. The problem is that when it returns false modelValue goes undefined. I know at this point the value is meant to be stored in $$invalidModelValue, but I still need the value in the original model since it is being used elsewhere in the view.
Is there a way to stop Angular from moving it and making the original model undefined? I know this problem could be solved in the form controller, but I don't think that is the correct way to do it since I want to disable the form submission button using the form state and not some external variable. Is there an alternate way to approach this problem whilst using Angular form validation?
Beginning in Angular v. 1.3, when $validate() returns false, the value of ng-model is set to undefined (docs here)
To prevent this behavior, set allowInvalid property of ngModelOptions to true, like so:
ng-model-options="{allowInvalid: true}"

Modifying a directive to 'watch' a ctrl property?

I have a directive which checks if they click submit on a form. If so, it adds the 'submitted' class. This allows me to style the form inputs as red only when they have submitted a form (I hate having it red in real-time as they're typing).
'use strict';
app.directive('formSubmitted', function() {
return {
restrict: 'A',
require: 'form',
link: function(scope, element, attrs, ctrl) {
ctrl.$submitted = false;
element.on('submit', function() {
scope.$apply(function() {
ctrl.$submitted = true;
element.addClass('submitted');
});
});
}
};
});
The problem is 'resetting' a form once it has been submitted successfully... I need to remove the submitted class from inside the controller. I have tried a lot of ways to do this without success... as in this pseudo-code...
angular.element($scope.myForm).removeClass('submitted');
What I am thinking is instead of doing that from the controller (which doesn't work anyway), that I try to make the 'submitted' class mirror the $submitted property on ctrl... This way I could do...
$scope.myForm.$submitted = false and the class would update appropriately.
I have no idea even where to begin with though, and googling isn't helping...
Thanks!
A simple approach I have used in situations like this is leveraging the Angular ngClass directive and binding to a property on the controller that maintains whether the state is submitted or not. Something like so:
<button ng-click="isSubmitted = !isSubmitted">Submit</button>
<form ng-class="{submitted: isSubmitted}">
</form>
You can use the ng-class directive:
<form name="myForm" ng-class="{submitted: $submitted}">
See the doc here: https://docs.angularjs.org/api/ng/directive/ngClass
Within the controller handling the form submission, you certainly have a submit function:
$scope.submit = function (form) {
$scope.$submitted = true;
if (form.$invalid) {
return;
}
// Actually send data to backend, eventually receiving a promise
promiseFormBackend = MyService.sendForm();
promiseFromBackend.then(function () {
$scope.$submitted = false: // resetting the form class
});
}

AngularJS directive not being trigged on manual change to a field value

I've got an input field and a button that manually updates the input field with a value. The field also has a directive that monitors changes for error handling.
When typing into the input field, the directive triggers just fine. When the button is pressed to manually update the input field, the directive does not get triggered, and I can't figure out why. This is causing my validation to not update, sometimes causing an error to persist when the value is actually not an error.
Here is the input field in question:
<input testchange type="text" name="hello" ng-model="formData.hello"/>
Here is the button to update the value:
update text
Here is the directive:
angular.module('app').directive('testchange', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function(viewValue) {
console.log('value changed to ' + viewValue);
scope.history.push(viewValue);
})
}
}
});
I have made a simple example of the problem: http://jsfiddle.net/gakman/7zF9j/5/
You need to use a formatter in the case when you are updating the model value from button click. Understand your are updating model now not the view when you set formData.hello='hello world'.
You need to create a formatter and push it into $formatters collection on ngModelController. See documentation http://docs.angularjs.org/api/ng/type/ngModel.NgModelController
See my updated fiddle http://jsfiddle.net/cmyworld/N57JM/

Resources