I have the following directive:
mod.directive('mdCheckbox', function(){
var link = function(scope, element, attr, ngModel){
var inp = element.find('input');
scope.$watch(function(){
return ngModel.$viewValue;
}, function(newVal){
console.log('newVal: ' + newVal);
if(newVal){
inp.attr('checked', 'checked');
}else{
inp.removeAttr('checked');
}
});
scope.clickT = function(evt){
// ** why the need for stopPropagation ?
evt.stopPropagation();
console.log('click called');
if(inp.is(':checked')){
ngModel.$setViewValue(false);
console.log('set to false');
}else{
ngModel.$setViewValue(true);
console.log('set to true');
}
};
};
var directive = {
restrict: 'EA',
require: 'ngModel',
scope: {
text: '#mdText'
},
template: '<div ng-click="clickT($event)" class="checkbox"><input type="checkbox"/>' +
'<span>{{text}}</span></div>',
replace: true,
link: link
};
return directive;
});
and the html :
<md-checkbox md-text="Is Awesome" ng-model="user.isAwesome"></md-checkbox>
Now the clickT handler is called twice, and I have to stop propagation on it. But it's not clear to me why that happens. And the second problem is that even though this seems like it works, after a couple of clicks it stops working - the value doesn't change true/false anymore.
Here's a plunkr for testing
Thanks.
You shouldn't change the DOM manually because you can use data binding for that. But I won't go into that since it isn't the answer to question you asked.
Problem in your example is code that toggles Checking/Unchecking checkbox. It's not an attribute, it's a property(it can be true/false, not contain a value).
The checked attribute doesn't update the checked property after initial load. The attribute is in fact related to defaultChecked property.
Change it to:
if (newVal) {
inp.prop('checked', true);
} else {
inp.prop('checked', false);
}
Working Plnkr
Also, you can remove stopPropagation call.
There was something weird about the way you were adding and removing the checked attribute. Keep it simple and just set the input to true or false.
Working Demo
scope.clickT = function(){
console.log('click called');
if(inp){
ngModel.$setViewValue(false);
console.log('set to false');
}else{
ngModel.$setViewValue(true);
console.log('set to true');
}
};
It works if you query ngModel instead of input.
if (ngModel.$viewValue) ...
Although I'm not sure what this directive buys you...
Related
I have a type of input mask using an angular directive. I'm using a formatters and the blur event to format the model value for display, and I'm using parsers and the focus event to remove the formatting when the user edits the textbox.
I'm getting strange behaviour in Internet Explorer where if you use the Tab key to lose focus, the parser event is (incorrectly) firing so the model value is being updated incorrectly.
Is this an angular bug? Or is there something I'm doing wrong?
Here is a fiddle: https://jsfiddle.net/capesean/htorwgs5/3/
Note that in IE, with your console window open, you will see the events logging out.
Also, testing this on an earlier Angular version, seems to work fine:
https://jsfiddle.net/htorwgs5/4/
The directive code is:
.directive("test", function () {
return {
restrict: "A",
require: 'ngModel',
link: function (scope, element, attr, ngModel) {
// for DOM -> model validation
ngModel.$parsers.unshift(function (value) {
console.log("parser");
ngModel.$setValidity('test', true);
return +value;
});
ngModel.$formatters.unshift(function (value) {
console.log("formatter");
ngModel.$setValidity('test', true);
return (value === undefined ? "" : value) + "!";
});
element.val(scope.minutes);
element.bind("blur", function () {
scope.$apply(function () {
console.log("blur");
element.val((scope.minutes === undefined ? "" : scope.minutes) + "#");
});
});
element.bind("focus", function () {
scope.$apply(function () {
console.log("focus");
element.val(scope.minutes);
});
});
}
};
})
This is known behaviour. I've posted a bug report here:
https://github.com/angular/angular.js/issues/14987
The solution was to use $timeout to delay the setting of the element value, as suggested in the reply to the bug report.
I need to update my model after is has loaded its data. So i tried to write a directive which can do that. I didn't thought it would be this hard :(
I first tried a filter, which should be more simple, but got this error.
Error: [ngModel:nonassign] Expression 'editpage.url | addUrl' is non-assignable.
So now i try the directive way. This is my html code in the view:
<input ng-model="editpage.url" add-url type="text" class="light_txtbox" readonly>
And this is my directive:
app.directive('addUrl', [function() {
return {
restrict: 'A',
require: '?ngModel',
replace: true,
link: function(scope, element, attrs, ngModel) {
if (!ngModel) return;
// what to do next?
}
};
}]);
In the "what to do next" part i tried a watch like this:
scope.$watch(attrs.ngModel, function(site) {
if (typeof site !== undefined) {
ngModel.$setViewValue('www.mysite.com/' + site);
ngModel.$render();
}
});
But of course now the model is updated and "hey, i am changed so update again!" and again and again...
I only need the update to take place once. I think i need another approach, but can not figure out what to do.
You can unregister a $watch
var unregister = scope.$watch(attrs.ngModel, function() {
if (shouldStopWatching) {
unregister();
}
});
where shouldStopWatching is whatever condition you need (i.e. stop on second call of callback etc)
I have the following directive:
app.directive('scMultiselect', [function() {
return {
link: function(scope, element, attrs) {
element = $(element[0]);
element.multiselect({
enableFiltering: true,
// Replicate the native functionality on the elements so
// that Angular can handle the changes for us
onChange: function(optionElement, checked) {
optionElement.prop('selected', false);
if (checked)
optionElement.prop('selected', true);
element.change();
}
});
scope.$watch(function () {
return element[0].length;
}, function () {
element.multiselect('rebuild');
});
scope.$watch(attrs.ngModel, function() {
element.multiselect('refresh');
});
}
};
}]);
And the following element in my partial:
<select
id="level_teachers"
class="multiselect col-sm-10"
multiple="multiple"
ng-model="level.teachers"
ng-options="teacher.id as teacher.name for teacher in teachers"
sc-multiselect>
</select>
The bootstrap-multiselect control initializes and displays correctly, however when I select entries in it, my model (level.teachers) remains empty.
Had same problem and this worked for me :
First you add ngModel as 4th parameter of link function. Its very useful - more about it here: https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
Then you basically have to add/delete 'by hand' the option in onChange method to/from your ngModel.
ngModel.$setViewValue() updates the value, ngModel.$render and scope.$apply() are making it visible and spread new model further :)
If you have only single selection then its much easier - less code because of no array control - just use $setViewValue(), $render() and $apply().
app.directive('scMultiselect', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
element = $(element[0]);
element.multiselect({
enableFiltering: true,
onChange: function(optionElement, checked) {
optionElement.prop('selected', false);
var modelValue = ngModel.$modelValue; // current model value - array of selected items
var optionText = optionElement[0].text; // text of current option
var optionIndex = modelValue.indexOf(optionText);
if (checked) {
if ( optionIndex == -1) { // current option value is not in model - add it
modelValue.push(optionText)
}
optionElement.prop('selected', true);
} else if ( optionIndex > -1 ) { // if it is - delete it
modelValue.splice(optionIndex,1);
}
ngModel.$setViewValue(modelValue);
ngModel.$render();
scope.$apply();
}
});
scope.$watch(element[0].length, function () {
element.multiselect('rebuild');
});
}
};
});
Hope it will work for you too :)
IT's probably because Bootstrap components aren't built in a way that allow them to be used by Angularjs. They don't really have a way to update themselves after you instantiate them, and more importantly they don't participate in Angularjs's update process. Anyway the good news is someone has taken the initiative to rewrite those bootstrap components in Angularjs so we can use them.
http://angular-ui.github.io/bootstrap/
If you are using Bootstrap 3.x then get the latest. If you're stuck on 2.3.x v0.8 is the last version that supported 2.3.x
I made a directive designed to be attached to an element using the ngModel directive. If the model's value matches something the value should then set to the previous value. In my example I'm looking for "foo", and setting it back to the previous if that's what's typed in.
My unit tests passed fine on this because they're only looking at the model value. However in practice the DOM isn't updated when the "put back" triggers. Our best guess here is that setting old == new prevents a dirty check from happening. I stepped through the $setViewValue method and it appears to be doing what it ought to. However it won't update the DOM (and what you see in the browser) until I explicitly call ngModel.$render() after setting the new value. It works fine, but I just want to see if there's a more appropriate way of doing this.
Code is below, here's a fiddle with the same.
angular.module('myDirective', [])
.directive('myDirective', function () {
return {
restrict: 'A',
terminal: true,
require: "?ngModel",
link: function (scope, element, attrs, ngModel) {
scope.$watch(attrs.ngModel, function (newValue, oldValue) {
//ngModel.$setViewValue(newValue + "!");
if (newValue == "foo")
{
ngModel.$setViewValue(oldValue);
/*
I Need this render call in order to update the input box; is that OK?
My best guess is that setting new = old prevents a dirty check which would trigger $render()
*/
ngModel.$render();
}
});
}
};
});
function x($scope) {
$scope.test = 'value here';
}
Our best guess here is that setting old == new prevents a dirty check from happening
A watcher listener is only called when the value of the expression it's listening to changes. But since you changed the model back to its previous value, it won't get called again because it's like the value hasn't changed at all. But, be careful: changing the value of a property inside a watcher monitoring that same property can lead to an infinite loop.
However it won't update the DOM (and what you see in the browser) until I explicitly call ngModel.$render() after setting the new value.
That's correct. $setViewValue sets the model value as if it was updated by the view, but you need to call $render to effectively render the view based on the (new) model value. Check out this discussion for more information.
Finally, I think you should approach your problem a different way. You could use the $parsers property of NgModelController to validate the user input, instead of using a watcher:
link: function (scope, element, attrs, ngModel) {
if (!ngModel) return;
ngModel.$parsers.unshift(function(viewValue) {
if(viewValue === 'foo') {
var currentValue = ngModel.$modelValue;
ngModel.$setViewValue(currentValue);
ngModel.$render();
return currentValue;
}
else
return viewValue;
});
}
I changed your jsFiddle script to use the code above.
angular.module('myDirective', [])
.directive('myDirective', function () {
return {
restrict: 'A',
terminal: true,
require: "?ngModel",
link: function (scope, element, attrs, ngModel) {
if (!ngModel) return;
ngModel.$parsers.unshift(function(viewValue) {
if(viewValue === 'foo') {
var currentValue = ngModel.$modelValue;
ngModel.$setViewValue(currentValue);
ngModel.$render();
return currentValue;
}
else
return viewValue;
});
}
};
});
function x($scope) {
$scope.test = 'value here';
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<h1>Foo Fighter</h1>
I hate "foo", just try and type it in the box.
<div ng-app="myDirective" ng-controller="x">
<input type="text" ng-model="test" my-directive>
<br />
model: {{test}}
</div>
I am newbie with AngularJs and ,
I am tring to make a form project with $resource ,
I don't have anyideas to get multiple checkbox value and set it
to model , but there is an error show for my checkbox's value is empty from
service ?
here is my code
<dd ng-repeat="user in User.user" >
<input name="userName[user.userId][]" type="checkbox" ng-model="newProgram.program.managerId"
value="{{user.userId}}" check="model" />
{{user.userUsername}}
</dd>
angular.module('elnApp')
.directive('check', function () {
return {
require: 'ngModel',
scope: { model: '=check', value: '#' },
link: function(scope, elm, attr, ngModelCtrl, $filter){
elm.bind('click', function() {
scope.$watch('newProgram.program.managerId', function (value){
if(value == true){
var checkValue = scope.value;
var brands = [];
brands.push(checkValue);
ngModelCtrl.$setViewValue(brands);
scope.$apply();
}
});
});
}
}
});
I am so confused about that , anyone ideas ?
There may be other problems with your code than the one I'm going to point out, but one mistake I noticed is that you shouldn't be using elm.bind('click', ...) in your link function. The way your code is written, the watch won't be set up until after the element is clicked, but you really want the watch set up right from the beginning.
Just run scope.$watch from inside your link function, with no elm.bind, and you should see some improvement. In other words, your link function should look like:
link: function(scope, elm, attr, ngModelCtrl, $filter) {
scope.$watch('newProgram.program.managerId', function (value) {
if (value == true) {
var checkValue = scope.value;
var brands = [];
brands.push(checkValue);
ngModelCtrl.$setViewValue(brands);
scope.$apply();
}
});
});
I don't fully understand the rest of what you want to do ("get form value to service" really isn't very clear to me -- seeing an example on jsfiddle or plunker would be much easier to understand), so this is all the help I can give you. I hope it's useful.