I have a custom directive:
.directive('myDirective', function() {
return {
scope: {ngModel:'='},
link: function(scope, element) {
element.bind("keyup", function(event) {
scope.ngModel=0;
scope.$apply();
});
}
}
});
This works as planned, setting the variables to 0 on keyup, but it doesn't reflect the changes on the input themselves. Also when initialized, the values of the model are not in the input. Here is an example:
http://jsfiddle.net/prXm3/
What am I missing?
You need to put a watcher to populate the data since the directive creates an isolated scope.
angular.module('test', []).directive('myDirective', function () {
return {
scope: {
ngModel: '='
},
link: function (scope, element, attrs) {
scope.$watch('ngModel', function (val) {
element.val(scope.ngModel);
});
element.bind("keyup", function (event) {
scope.ngModel = 0;
scope.$apply();
element.val(0); //set the value in the dom as well.
});
}
}
});
Or, you can change the template to
<input type="text" ng-model="$parent.testModel.inputA" my-directive>
the data will be populated thought it will break your logic to do the event binding.
So it is easier to use the watcher instead.
Working Demo
Related
So this is mainly to guarantee backwards compatibility. I need to add additional functionality on blur to a large number of html elements without having to edit the controller methods called by the ng-blur attribute of those elements.
I wrote a directive to do this, but the problem is the directive method seems to always finish executing after the ng-blur methods in the controller. I need the directive method to finish executing before the ng-blur method in the controller begins.
Here is the basic format of the code:
<input test-directive ng-model="testValue" ng-blur="ngBlurTest(testValue)">
Directive:
angular.module('testDirectives', [])
.directive('testDirective', [ function() {
return {
restrict: 'A',
require: 'ngModel',
scope: {
ngModel: '=',
},
link: function (scope, element, attrs, ngModelCtrl, test) {
element.bind('blur', function() {
console.log('This needs to appear first')
}
});
}
};
}])
Controller (Method)
$scope.ngBlurTest = function(value) {
console.log('TEST FROM CONTROLLER: ' + value);
}
Thanks for the help!
I'm not sure if it's possible, but try setting priority of the directive to a really high number, for example:
.directive('testDirective', [ function() {
return {
priority: 1000000,
restrict: 'A',
...
Also try using addEventListener, but don't forget to clear it:
var listener = function(event) {...};
element[0].addEventListener('blur', listener);
...
scope.$on('$destroy', function() {
element[0].removeEventListener('blur', listener);
});
Also try setting useCapture parameter of addEventListener to true:
element[0].addEventListener('blur', listener, true);
Basically, I created a directive that passes a promise to the link function from ng-click and and detects when the promise is done so that I can attach a class to it.
Example:
.directive('myDirective', function($parse) {
return {
restrict: 'A',
scope: {
ng-click: '&'
},
link: function(scope) {
var d = $parse(scope.ngClick);
element.on('click', function(event) {
d().then(function() {
element.addClass(attrs.myDirective);
});
});
}
};
});
<element ng-click="promise();" my-directive="class"></element>
//controller function
$scope.promise = function() {
return promise().then(function() {});
}
It is doing what I want except that the controller function is getting called three times. I would really like to just use require: '^ngClick' here but since the ngClick directive does not have any controllers, I can't do that. Can anyone point me in the right direction? Thanks!
Added event.preventDefault() to the event.on('click') function in the link of my directive:
element.on('click', function(event) {
event.preventDefault();
d().then(function() {
element.addClass(attrs.myDirective);
});
});
I have the following markup:
<div class="controller" ng-controller="mainController">
<input type="text" ng-model="value">
<div class="matches"
positions="{{client.positions | filter:value}}"
select="selectPosition(pos)">
<div class="match"
ng-repeat="match in matches"
ng-click="select({pos: match})"
ng-bind="match.name">
Then, inside my matches directive I have
app.directive('matches', function()
{
return {
scope: {
select: '&'
},
link: function(scope, element, attrs)
{
scope.matches = [];
attrs.$observe('positions', function(value)
{
scope.matches = angular.fromJson(value);
scope.$apply();
})
}
}
}
When I do this, I can console log scope.matches, and it does change with the value from my input. However, the last div .match doesn't render anything! If I remove scope: {...} and replace it with scope: true, then it does render the result, but I want to use the & evaluation to execute a function within my main controller.
What do i do?
Use scope.$watch instead, you can watch the attribute select whenever changes are made from that attribute.
app.directive('matches', function()
{
return {
scope: true,
link: function(scope, element, attrs)
{
scope.matches = [];
scope.$watch(attrs.select, function(value) {
scope.matches = angular.fromJson(value);
});
}
}
}
UPDATE: Likewise, if you define select itself as a scope attribute, you must use the = notation(use the & notation only, if you intend to use it as a callback in a template defined in the directive), and use scope.$watch(), not attr.$observe(). Since attr.$observe() is only used for interpolation changes {{}}, while $watch is used for the changes of the scope property itself.
app.directive('matches', function()
{
return {
scope: {select: '='},
link: function(scope, element, attrs)
{
scope.matches = [];
scope.$watch('select', function(value) {
scope.matches = angular.fromJson(value);
});
}
}
}
The AngularJS Documentation states:
$observe(key, fn);
Observes an interpolated attribute.
The observer function will be invoked once during the next $digest
following compilation. The observer is then invoked whenever the
interpolated value changes.
Not scope properties defined as such in your problem which is defining scope in the directive definition.
If you don't need the isolated scope, you could use $parse instead of the & evaluatation like this:
var selectFn = $parse(attrs.select);
scope.select = function (obj) {
selectFn(scope, obj);
};
Example Plunker: http://plnkr.co/edit/QZy6TQChAw5fEXYtw8wt?p=preview
But if you prefer the isolated scope, you have to transclude children elements and correctly assign the scope of your directive like this:
app.directive('matches', function($parse) {
return {
restrict: 'C',
scope: {
select: '&',
},
transclude: true,
link: function(scope, element, attrs, ctrl, transcludeFn) {
transcludeFn(scope, function (clone) {
element.append(clone);
});
scope.matches = [];
attrs.$observe('positions', function(value) {
scope.matches = angular.fromJson(value);
});
}
}
});
Example Plunker: http://plnkr.co/edit/9SPhTG08uUd440nBxGju?p=preview
I'm trying to understand directives, and I'm having problems with two way data binding.
My directive will be used to submit a form when "enter" is pressed in a textarea.
I found a solution in another SO thread (see the code below in the scope definition of the directive), but I don't like it because it means that if I change the model name, I need to change the directive as well..
--> Here is the problem in codepen.io
Here is the html part:
<div ng-app="testApp" ng-controller="MyController">
<textarea ng-model="foo" enter-submit="submit()"></textarea><br/>
Binding: {{foo}}
</div>
Here is the javascript part:
var app = angular.module('testApp', []);
function MyController($scope) {
$scope.foo = "bar"
$scope.submit = function() {
console.log("Submitting form");
}
}
app.directive('enterSubmit', function () {
return {
restrict: 'A',
scope: {
submitFn: '&enterSubmit',
foo: '=ngModel' // <------------------- dont't like this solution
},
link: function (scope, elem, attrs) {
elem.bind('keydown', function(event) {
var code = event.keyCode || event.which;
if (code === 13) {
if (!event.shiftKey) {
event.preventDefault();
scope.submitFn();
}
}
});
}
}
});
Thanks for your help !
When multiple directives are used on an element, normally you don't want any of them to use an isolate scope, since that forces all of them to use the isolate scope. In particular, isolate scopes should not be used with ng-model – see Can I use ng-model with isolated scope?.
I suggest creating no new scope (the default, i.e., scope: false):
app.directive('enterSubmit', function () {
return {
restrict: 'A',
//scope: {
// submitFn: '&enterSubmit',
// foo: '=ngModel' // <------------------- dont't like this solution
//},
link: function (scope, elem, attrs) {
elem.bind('keydown', function(event) {
var code = event.keyCode || event.which;
if (code === 13) {
if (!event.shiftKey) {
event.preventDefault();
scope.$apply(attrs.enterSubmit);
}
}
});
}
}
});
Is it possible to "watch" for ui changes on the directive?
something like that:
.directive('vValidation', function() {
return function(scope, element, attrs) {
element.$watch(function() {
if (this.hasClass('someClass')) console.log('someClass added');
});
}
})
Yes. You can use attr.$observe if you use interpolation at the attribute.
But if this is not an interpolated attribute and you expect it to be changed from somewhere else in the application (what is extremely not recommended, read Common Pitfalls), than you can $watch a function return:
scope.$watch(function() {
return element.attr('class');
}, function(newValue){
// do stuff with newValue
});
Anyway, its probably that the best approach for you would be change the code that changes the element class. Which moment does it get changed?
attrs.$observe('class', function(val){});
You can also watch variable in the controller.
This code automatically hides notification bar after some other module displays the feedback message.
HTML:
<notification-bar
data-showbar='vm.notification.show'>
<p> {{ vm.notification.message }} </p>
</notification-bar>
DIRECTIVE:
var directive = {
restrict: 'E',
replace: true,
transclude: true,
scope: {
showbar: '=showbar',
},
templateUrl: '/app/views/partials/notification.html',
controller: function ($scope, $element, $attrs) {
$scope.$watch('showbar', function (newValue, oldValue) {
//console.log('showbar changed:', newValue);
hide_element();
}, true);
function hide_element() {
$timeout(function () {
$scope.showbar = false;
}, 3000);
}
}
};
DIRECTIVE TEMPLATE:
<div class="notification-bar" data-ng-show="showbar"><div>
<div class="menucloud-notification-content"></div>