Can I use ng-model with isolated scope?

Replacing scope: true with scope: { datetime1: '=ngModel'} in your first fiddle seems to work fine -- fiddle. Unfortunately, the link to your "example" fiddle is broken, so I'm not sure what you tried there.

So, it would seem that ngModelController can be used with an isolate scope.

Here's a smaller fiddle that uses ng-model in the HTML/view, an isolate scope, and $setViewValue in the link function: fiddle.

Update: I just discovered something rather interesting: if the isolate scope property is given a different name -- e.g., say dt1 instead of datetime1 -- scope: { dt1: '=ngModel'} -- it no longer works! I'm guessing that when we require: 'ngModel', the ngModelController uses the name in the HTML/view (i.e., the ng-model attribute value) to create a property on the isolate scope. So if we specify the same name in the object hash, all is well. But if we specify a different name, that new scope property (e.g., dt1) is not associated with the ngModelController we required.

Here's an updated fiddle.


Make your directive run at a higher priority than ngModel and correct the model binding for your isolated scope. I chose a priority of '100' which is the same level as the input directive, after high priority template manipulations like ngRepeat but before the default of 0, which is what ngModel uses.

Here's example code:

myDirective = function() {
  return {
    compile: function(tElement, tAttrs, transclude) {
      // Correct ngModel for isolate scope
      if (tAttrs.ngModel) {
        tAttrs.$set('model', tAttrs.ngModel, false);
        tAttrs.$set('ngModel', 'model', false);
      }

      return {
        post: function(scope, iElement, iAttrs, controller) {
          // Optionally hook up formatters and parsers
          controller.$formatters.push(function(value) {
             // ...
          })

          // Render
          return controller.$render = function() {
            if (!controller.$viewValue) {
              return;
            }
            angular.extend(scope, controller.$viewValue);
          };
        }
      };
    },
    priority: 100,
    require: '^ngModel',
    scope: {
      model: '='
    },
  };
}

During compilation, the directive checks whether the ngModel attribute is present. This check works on the normalized value using Angular's Attributes. If the attribute is present, it is replaced with 'model' (not 'ngModel'), which is the name data-bound into our isolate. However, we must also create an attribute so that Angular can perform the data binding for us. Both attributes can be (at your option) modified with a false parameter which leaves the DOM unchanged.


I think I had the same problem, and I found partial yet usable solution.

So, the problem has several parts:

  1. your custom directive wants some private properties, i.e. isolated scope
  2. DOM node can have only one scope, all directives share it
  3. ngModel="something" binds to "something" in that shared (isolated) scope, and this is the actual problem

So, my first step was to rewrite my directive to use scope:true instead of scope:{...} (actually, that was a requirement, because I wanted to use some global scope properties within my directive's transcluded content): things like attrs.$observe(), $scope.$parent.$watch(), etc. helped.

Then in compile() I re-bound ngModel to parent scope's property: attrs.$set('ngModel', '$parent.' + attrs.ngModel, false). And that's all.

Here is my directive, with non-essential code stripped:

angular.module('App', []).directive('dir', function () {
    return {
        /* This one is important: */
        scope:true,
        compile:function (element, attrs, transclude) {
            /* The trick is here: */
            if (attrs.ngModel) {
                attrs.$set('ngModel', '$parent.' + attrs.ngModel, false);
            }

            return function ($scope, element, attrs, ngModel) {
                // link function body
            };
        }
    };
});