Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I use ng-model with isolated scope?

Tags:

I am creating simple ui-datetime directive. It splits javascript Date object into _date, _hours and _minutes parts. _date uses jquery ui datepicker, _hours and _minutes - number inputs.

angular.module("ExperimentsModule", [])     .directive("uiDatetime", function () {     return {         restrict: 'EA',         replace: true,         template: '<div class="ui-datetime">' +             '<input type="text" ng-model="_date" class="date">' +             '<input type="number" ng-model="_hours" min="0" max="23" class="hours">' +             '<input type="number" ng-model="_minutes" min="0" max="59" class="minutes">' +             '<br />Child datetime1: {{datetime1}}' +             '</div>',         require: 'ngModel',         scope: true,         link: function (scope, element, attrs, ngModelCtrl) {             var elDate = element.find('input.date');              ngModelCtrl.$render = function () {                 var date = new Date(ngModelCtrl.$viewValue);                 var fillNull = function (num) {                     if (num < 10) return '0' + num;                     return num;                 };                 scope._date = fillNull(date.getDate()) + '.' + fillNull(date.getMonth() + 1) + '.' + date.getFullYear();                 scope._hours = date.getHours();                 scope._minutes = date.getMinutes();             };              elDate.datepicker({                 dateFormat: 'dd.mm.yy',                 onSelect: function (value, picker) {                     scope._date = value;                     scope.$apply();                 }             });              var watchExpr = function () {                 var res = scope.$eval('_date').split('.');                 if (res.length == 3) return new Date(res[2], res[1] - 1, res[0], scope.$eval('_hours'), scope.$eval('_minutes'));                 return 0;             };             scope.$watch(watchExpr, function (newValue) {                 ngModelCtrl.$setViewValue(newValue);             }, true);         }     }; });  function TestController($scope) {     $scope.datetime1 = new Date(); } 

jsfiddle

On github: https://github.com/andreev-artem/angular_experiments/tree/master/ui-datetime

As far as I understand - best practice when you create a new component is to use isolated scope.

When I tried to use isolated scope - nothing works. ngModel.$viewValue === undefined.

When I tried to use new scope (my example, not so good variant imho) - ngModel uses value on newly created scope.

Of course I can create directive with isolated scope and work with ngModel value through "=expression" (example). But I think that working with ngModelController is a better practice.

My questions:

  1. Can I use ngModelController with isolated scope?
  2. If it is not possible which solution is better for creating such component?
like image 576
Artem Andreev Avatar asked Aug 10 '12 06:08

Artem Andreev


Video Answer


2 Answers

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.

like image 90
Mark Rajcok Avatar answered Oct 21 '22 08:10

Mark Rajcok


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.

like image 20
tilgovi Avatar answered Oct 21 '22 07:10

tilgovi