Access ngModel value in directive
Angular built-in directives are useful and sufficient for most of basic use. However, to leverage the power provided by the framework, writing custom directive is inevitable. In this post, I will introduce ways to access ngModel in directive to create more robust components in application.
Before going ahead, just revise knowledge we have. This is non-trivial to understand why an approach working in this case but not in another one.
Understand ngModel
First of all, we need to remind how to use ngModel
and what happening when using it. Normally, ngModel
will be added into input
tag and followed by an expression that indicates property in scope.
<input type='text' ng-model='myModel'>
In above example, myModel
is the expression that Angular will evaluate, then bind input
tag to this property in the scope. If myModel
is not found in the scope, Angular will implicitly create and add into the scope.
Understand matching attributes
Matching attributes is the way to pass data to directive using attributes in the element. For data, there are two ways: @
and =
. Their explanation can be simplify as:
* @
: mono-directional binding, interpolation.
* =
: bi-directional binding, parse.
What? Don’t worry, I will explain latter via examples, it is easier to understand.
Great, till now we have enough foundation to continue. There, however, is one more thing we need, that is scope types in directive.
As we know already, there are three types of scope in directive: no scope, inherited scope from parent scope and ultimately isolated scope. If you are not familiar with this, reference Angular’s guide first. Why do we need to concern scope of directive? It will affect how we access ngModel value in directive. Let’s go through each type and see which ways we can use.
No scope at all
Directive uses its parent’s scope instead of creating a new one for itself. Thus, it can see all scope properties, of course, model value as well. Because of this, the most straightforward way is to directly access scope properties.
// No scope app.directive('d1', function() { return { restrict: 'E', link: function($scope, $element, $attr) { angular.element($element).append("Hello "); angular.element($element).append($scope[$attr.ngModel]); } }; });
<d1 ng-model='hello'></d1>
Inherited scope from parent
This is the same as before, no difference.
// Inherited scope app.directive('d2', function() { return { restrict: 'E', scope: true, link: function($scope, $element, $attr) { angular.element($element).append("Hello "); angular.element($element).append($scope[$attr.ngModel]); } }; });
Isolated scope
Unlike previous cases, we can’t get model value directly from neither parent scope nor directive’s. This is because the scope is completely isolated from the rest of the world, it is the only one and stands alone. What can we do in this case?
// Isolated scope app.directive('d3', function() { return { restrict: 'E', scope: { modelValue: '=ngModel' }, link: function($scope, $element, $attr) { angular.element($element).append("Hello "); angular.element($element).append($scope.modelValue); } }; });
<d3 ng-model='hello'></d3>
By using matching attributes, we take advantage of Angular expression evaluation. What that means? Angular will evaluate hello
, then bind hello
property in the scope to which the element belongs to modelValue
property in the isolated scope. From now, every change in hello
or modelValue
will be updated and reflect each other.
Note: We must use =
instead of @
. Why? As said before, @
is for mono-directional binding, interpolation. See the following example:
// Isolated scope app.directive('d4', function() { return { restrict: 'E', scope: { modelValue: '@ngModel' }, link: function($scope, $element, $attr) { angular.element($element).append("Hello "); angular.element($element).append($scope.modelValue); } }; });
<d4 ng-model='hello'></d4>
If using @
, instead of parsing hello
, it will be interpolated. Therefore $scope.modelValue
will be hello
literally, not the value of hello
property as we expect.
Here is all of above examples.
Conclusion
If directive doesn’t own scope or inherits from its parent scope, just access via scope. Ultimately we need workaround in isolated scope by using matching attributes. And don’t forget to use =
instead of @
.