Let's say I have an input, like this:
<input ng-model="myinput" />
I want it to display a default value of Hello on page load/render but not touch myinput.
I can't use a placeholder since I want the user to have the ability to edit the default value of hello. Once that default value is changed by the user, then that change should be reflected in myinput.
Anyone have any ideas?
Edit: For context, these input field default values are settings that the user saved which are being displayed on an item page. If the default value is saved to the model and the item is updated, then that default value sticks. Meaning, if they change the setting, then that item won't have the new setting value. Only if the user explicitly changed the field should it save that data to the item.
If I understand you correctly, one way of doing this is like this:
$scope.myinput= '';
$scope.tempVal = "some default value";
$scope.$watch('tempVal', function(newValue, oldValue) {
if (newValue != "some default value") {
$scope.myinput= newValue;
}
});
in the html:
<input type="text" ng-model="tempVal" />
you use a temp variable for the input and only when the value is not the default change myinput
note:
Are you sure you don't want to save the default value in the variable? I find it hard to imagine a use case in which it's not the best approach. Can you describe your use case?
Initialize myinput inside of your controller:
JavaScript
app.controller('ctrl', function($scope) {
$scope.myinput = 'hello';
});
HTML
<div ng-controller='ctrl'>
<input ng-model="myinput" />
</div>
I write a directive base on how I understand the requirement , hope it can help:
https://jsfiddle.net/tm5yv4Lk/
angular.module('myApp',[])
.controller('MainController', ['$scope', function($scope) {
$scope.myinput = 'my text';
$scope.myFakeInput = 'fake text';
}])
.directive('inputMask', ['$timeout', function($timeout) {
return {
restrict: 'A',
require: 'ngModel',
scope: {
inputMask: '='
},
link: function(scope, element, attrs, ctrl ) {
$timeout(function() {
element[0].value = scope.inputMask;
}, 0);
}
};
}]);
HTML:
<input ng-model="myinput" input-mask="myFakeInput"/>
Related
I need to format the input values so I create a directive that use a template with require: 'ngModel' because I have to use ngModelController functions ($parsers, $formatters, etc.).
This is my HTML:
<div ng-model="myInputValue" amount-input-currency=""></div>
{{myInputValue}}
This is my directive:
.directive('amountInputCurrency', [function(){
return {
templateUrl: '../amountInputCurrency.tmpl.html',
require: 'ngModel',
restrict: 'A',
link: function(scope, elem, attrs, model) {
// ...
}
}
}
And this is my template:
<input type="text" ng-model="myInputValue">
The problem is that I can't updated the view after formatting the inserted value. For example if I write '1' I want change the value in this way:
model.$formatters.push(function(value) {
return value + '00';
}
Alternative I try to set an event in this other way:
<input type="text" ng-model="myInputValue" ng-blur="onBlur()">
scope.onBlur = function() {
model.$viewValue = model.$viewValue + '00';
// or model.$setViewValue(model.$viewValue + '00';);
model.$render();
};
The model.$viewValue changes, myInputValue (in the HTML with {{myInputValue}}) changes but not the value showed in the input box... which is the problem? Thanks!
----------------UPDATE----------------
Probably the problem is because I have 2 ng-model (one in the HTML and one in the template): https://github.com/angular/angular.js/issues/9296
How can I do? Both model refer to the same model...
Formatters change how model values will appear in the view.
Parsers change how view values will be saved in the model.
//format text going to user (model to view)
ngModel.$formatters.push(function(value) {
return value.toUpperCase();
});
//format text from the user (view to model)
ngModel.$parsers.push(function(value) {
return value.toLowerCase();
});
Try using $parsers to change the view to your desired value.
I hope this will help you.
Update:
angular.module('components', []).directive('formatParse', function() {
return {
restrict: 'E',
require: 'ngModel',
scope: { model: "=ngModel" },
template: '<input type="text" data-ng-model="model"></input><button type="button" data-ng-click="clickedView()">SetView</button><button type"button" data-ng-click="clickedModel()">SetModel</button>',
link: function ($scope, el, attrs, ngModelCtrl) {
format = "MMM Do, YYYY H:mm";
console.log($scope.model);
console.log(ngModelCtrl);
$scope.clickedView = function () {
ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue);
};
$scope.clickedModel = function () {
$scope.model = 12; // Put here whatever you want
};
ngModelCtrl.$parsers.push(function (date) {
console.log("Parsing", date)
return date; // Put here the value you want to be in $scope.model
});
ngModelCtrl.$formatters.push(function (date) {
console.log("Formatting", date);
console.log($scope.model);
console.log(ngModelCtrl);
return +date * 2; // Put here what you want to be displayed when clicking setView (This will be in ngModelCtrl.$viewValue)
});
}
}
});
angular.module('someapp', ['components']);
Try using this code and tell if this helped to get the result you wanted.
If it does I suggest, to console.log the ngModelCtrl that you way you will understand more about the inner flow of angular.
In addition, just so you have some more information,
When you edit the input in the view the formatters function are fired to change the model accordingly.
If the value that has been entered is not valid you can return in your formatters function the ngModelCtrl.$viewValue to keep $scope.model with his old and true information.
When you change your scope variable (in your case $scope.model) the parsers functions will be fired to change the view value. (You don't need to use $render, you just need to decide when you want to change your $scope.model),
I suggest instead of using $setViewValue put the value you want in your scope variable and the parsers will act accordingly.
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
I've a long calculation form and want to prefill some values for displaying initial results. These initial values should live in the template to have these easily editable. It seems, the angular way is to use a directive which reads the input value fields and initializes the app with these values.
Here is one way to to set the model value from the input field:
<input name="card[description]" value="Visa-4242" ng-model="card.description" ng-initial>
Coffeescript:
app = angular.module 'forms', []
app.directive 'ngInitial', ->
restrict: 'A'
controller: ['$scope', '$element', '$attrs', '$parse', ($scope, $element, $attrs, $parse) ->
val = $attrs.sbInitial || $attrs.value
getter = $parse($attrs.ngModel)
setter = getter.assign
setter($scope, val)
]
Source: Angular js init ng-model from default values
Unfortunately this doesn't work in my app. The values are displayed but the results aren't calculated. I have to manual fill out the fields in the browser to start the calculation.
The input fields are watched in my controller, like so:
$scope.$watch(
'inputValues.permeatCapacity',
function (newValue, oldValue) {
if (newValue === oldValue) {
return;
}
permeatCapacity = $scope.inputValues.permeatCapacity;
permeatPerDay = permeatFactory.getPermeatPerDay(permeatCapacity);
$scope.permeatPerDay = $filter('number')(permeatPerDay, 2);
}
);
What's the best directive for this problem or is there a better way in Angular.js?
UPDATE
I've just found a really dirty way in the controller to update the input fields after initialization that make use of the bound calculations. It not only feels like an bad idea, the initial values also don't live in the template:
$timeout(function () {
$scope.inputValues.overallRecovery = 40;
$scope.inputValues.permeatCapacity = 7.2;
}, 0);
In comparison, this initial object in my controller fills out the fields but doesn't trigger the watchers (important for the bound calculations):
$scope.inputValues = {
overallRecovery: 40,
permeatCapacity: 7.2
};
But there is a better way to do this, isn't it?
Ok, here's what I found out. The condition:
if (newValue === oldValue) {
return;
}
in my watch methods blocked the initial calculation. I can now use a really simple directive for my usecase:
An input field in the template:
<input type="text" id="overall-recovery" value="40" ng-model="inputValues.overallRecovery" initial-value>
The directive:
angular.module('ksbApp')
.directive('initialValue', function ($compile) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
var initialValue = attrs.value;
ngModel.$setViewValue(initialValue);
}
};
});
In this case, the value "40" is pushed to the model during initialization.
Is there anything I could do better?
Not sure why you would need a directive for this, unless I dont understand the question fully. Spin up the model in scope, setting the values where on the controller, and then bind them using ngmodel.
in the controller:
scope.inputValues = {permeatCapacity: 1};
scope.calc = function(){
return permeatFactory(scope.inputValues.perMeatCapacity);
};
in the view:
<input ng-model="inputValues.permeatCapacity" type="text">
<button ng-click="calc()" />
In later versions of angular you can even have the calculation run AS the values in the model change using ng-change.
Using angularjs, if I bind the placeholder of an input to its model, the change event is fired when the document loads in IE. This does not appear to be correct and I'm not seeing this behavior in other browsers.
JS Fiddle
Html:
<div ng-app="angularjs-starter" data-ng-controller="MainCtrl">
<div data-ui-view="viewMain">
<input
placeholder="{{theValue}}"
data-ng-model="theValue"
data-ng-change="valueChanged(theValue)" />
</div>
Javascript:
var app = angular.module('angularjs-starter', []);
app.controller('MainCtrl', function($scope) {
$scope.valueChanged = function(theValue) {
alert("Value Change Called On Load in IE.");
};
});
It's possible to use the built-in ng-attr-placeholder directive as well.
ng-attr-placeholder="{{theValue}}"
I know this is old but just in case anyone else runs in to this I created a small directive that goes around putting a dynamic value in the placeholder and instead have it assign when it changes:
.directive('dynamicPlaceholder',
function() {
return {
restrict: 'A',
link: function ($scope, element, attrs) {
attrs.$observe('dynamicPlaceholder', function(value) {
element.attr('placeholder', value);
});
}
};
});
Then to use this all you need to do is use dynamic-placeholder instead of placeholder:
<input ng-model='someValue' dynamic-placeholder='{{someDynamicPlaceholder}}' />
Not sure what is causing the problem in IE though
fiddle here: http://jsfiddle.net/graphicsxp/QA4Fa/2/
I'm triying to create a directive for searching. Basically it's just a textbox that detects user input and after a 1 second delay, a search method is called.
It's not working yet and I'm having two issues.
First, why is the filterCriteria not updated in the span when user inputs text ?
Second, the watch on filterCriteria is triggered at page loading but not when text is entered in the textbox.
<div ng-app="myApp" ng-controller="myController">
<delayed-search ng-model="filterCriteria"></delayed-search>
<span>filter criteria is : {{filterCriteria}}</span>
</div>
angular.module('myApp', []).directive("delayedSearch", ['$timeout', function($timeout) {
return {
restrict: "E",
template: '<input type="text" />',
scope: {
filterCriteria : '='
},
link: function (scope, element, attrs) {
},
controller: function ($scope) {
var timer = false;
$scope.$watch('filterCriteria', function () {
if (timer) {
$timeout.cancel(timer);
}
timer = $timeout(function () {
alert('timeout expired');
}, 1000)
});
}
}
}]).controller('myController', function($scope){ });
You should NOT use a controller with a directive ( until you understand it ! ) .
A controller in a directive is meant for directive to directive communication (I wish they had named it something else!).
#Langdon got it right.. But here is another implementation of the same. Note that in both the answer's the controller is missing.
http://jsfiddle.net/QA4Fa/4/
First, why is the filterCriteria not updated in the span when user inputs text ?
Your scope is wrong, it should be scope: { ngModel : '=' },, and your template should be template: '<input type="text" ng-model="ngModel" />.
Second, the watch on filterCriteria is triggered at page loading but not when text is entered in the textbox.
Same as the first problem, you should be watching ngModel.
Also, you don't need the overhead of a controller for this, you can get away with just using the link function. Here's an updated fiddle: http://jsfiddle.net/QA4Fa/3/