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 @.

Tags: ,

About ninjapro

It is better to feel by yourself about me

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: