Why do I have to use "{{title}}" with '@' and "title" with '='?
@ binds a local/directive scope property to the evaluated value of the DOM attribute. If you use title=title1
or title="title1"
, the value of DOM attribute "title" is simply the string title1
. If you use title="{{title}}"
, the value of the DOM attribute "title" is the interpolated value of {{title}}
, hence the string will be whatever parent scope property "title" is currently set to. Since attribute values are always strings, you will always end up with a string value for this property in the directive's scope when using @.
= binds a local/directive scope property to a parent scope property. So with =, you use the parent model/scope property name as the value of the DOM attribute. You can't use {{}}
s with =.
With @, you can do things like title="{{title}} and then some"
-- {{title}} is interpolated, then the string "and them some" is concatenated with it. The final concatenated string is what the local/directive scope property gets. (You can't do this with =, only @.)
With @, you will need to use attr.$observe('title', function(value) { ... })
if you need to use the value in your link(ing) function. E.g., if(scope.title == "...")
won't work like you expect. Note that this means you can only access this attribute asynchronously.
You don't need to use $observe() if you are only using the value in a template. E.g., template: '<div>{{title}}</div>'
.
With =, you don't need to use $observe.
Can I also access the parent scope directly, without decorating my element with an attribute?
Yes, but only if you don't use an isolate scope. Remove this line from your directive
scope: { ... }
and then your directive will not create a new scope. It will use the parent scope. You can then access all of the parent scope properties directly.
The documentation says "Often it's desirable to pass data from the isolated scope via an expression and to the parent scope", but that seems to work fine with bidirectional binding too. Why would the expression route be better?
Yes, bidirectional binding allows the local/directive scope and the parent scope to share data. "Expression binding" allows the directive to call an expression (or function) defined by a DOM attribute -- and you can also pass data as arguments to the expression or function. So, if you don't need to share data with the parent -- you just want to call a function defined in the parent scope -- you can use the & syntax.
See also
- When a Modal is opened, set focus on a predefined <input> inside this Modal.
Define a directive and have it $watch a property/trigger so it knows when to focus the element:
Name: <input type="text" focus-me="shouldBeOpen">
app.directive('focusMe', ['$timeout', '$parse', function ($timeout, $parse) {
return {
//scope: true, // optionally create a child scope
link: function (scope, element, attrs) {
var model = $parse(attrs.focusMe);
scope.$watch(model, function (value) {
console.log('value=', value);
if (value === true) {
$timeout(function () {
element[0].focus();
});
}
});
// to address @blesh's comment, set attribute value to 'false'
// on blur event:
element.bind('blur', function () {
console.log('blur');
scope.$apply(model.assign(scope, false));
});
}
};
}]);
Plunker
The $timeout seems to be needed to give the modal time to render.
'2.' Everytime <input> becomes visible (e.g. by clicking some button), set focus on it.
Create a directive essentially like the one above. Watch some scope property, and when it becomes true (set it in your ng-click handler), execute element[0].focus()
. Depending on your use case, you may or may not need a $timeout for this one:
<button class="btn" ng-click="showForm=true; focusInput=true">show form and
focus input</button>
<div ng-show="showForm">
<input type="text" ng-model="myInput" focus-me="focusInput"> {{ myInput }}
<button class="btn" ng-click="showForm=false">hide form</button>
</div>
app.directive('focusMe', function($timeout) {
return {
link: function(scope, element, attrs) {
scope.$watch(attrs.focusMe, function(value) {
if(value === true) {
console.log('value=',value);
//$timeout(function() {
element[0].focus();
scope[attrs.focusMe] = false;
//});
}
});
}
};
});
Plunker
Update 7/2013: I've seen a few people use my original isolate scope directives and then have problems with embedded input fields (i.e., an input field in the modal). A directive with no new scope (or possibly a new child scope) should alleviate some of the pain. So above I updated the answer to not use isolate scopes. Below is the original answer:
Original answer for 1., using an isolate scope:
Name: <input type="text" focus-me="{{shouldBeOpen}}">
app.directive('focusMe', function($timeout) {
return {
scope: { trigger: '@focusMe' },
link: function(scope, element) {
scope.$watch('trigger', function(value) {
if(value === "true") {
$timeout(function() {
element[0].focus();
});
}
});
}
};
});
Plunker.
Original answer for 2., using an isolate scope:
<button class="btn" ng-click="showForm=true; focusInput=true">show form and
focus input</button>
<div ng-show="showForm">
<input type="text" focus-me="focusInput">
<button class="btn" ng-click="showForm=false">hide form</button>
</div>
app.directive('focusMe', function($timeout) {
return {
scope: { trigger: '=focusMe' },
link: function(scope, element) {
scope.$watch('trigger', function(value) {
if(value === true) {
//console.log('trigger',value);
//$timeout(function() {
element[0].focus();
scope.trigger = false;
//});
}
});
}
};
});
Plunker.
Since we need to reset the trigger/focusInput property in the directive, '=' is used for two-way databinding. In the first directive, '@' was sufficient. Also note that when using '@' we compare the trigger value to "true" since @ always results in a string.
Best Answer
When you make changes to some data-bound variable and you need to wait for the corresponding rendering to happen, you need to use the famous
setTimeout
0 ( alternately in Angular the$timeout
service ).Here is a plunkr that shows how to do this.
http://plnkr.co/edit/N9P7XHzQqJbQsnqNHDLm?p=preview