velochy
velochy

Reputation: 403

Angular transclude and scopes

I am trying to generate a directive for click-to-edit input fields. Since I have a variety of different types of input fields that it needs to work with, I wanted to make it an attribute type directive that just transcludes the input field itself.

However, the problem is that when using a scope parameter to the directive description (for ipDisable), things stop working as they should (try commenting in line 44 in jsFiddle js part). Presumably, it is therefore a scope error, but I really have no idea where to begin debugging it, and any help would be appreciated.

jsFiddle: http://jsfiddle.net/HbYwX/3/

HTML:

<input inplace type="string" ip-disable=false name="username" ng-model="uname">

JS:

myApp.directive('inplace', function($compile) {

var compile = function(tElem,tAttrib,transclude) {

  var whole = $('<div ng-scope></div>');

  var editable = $('<div class="editable-transclude" ng-hide="static">'+
                  '<a ng-click="changeStatic()" ng-show="!static && !disable()">'+
                  '&ltsave&gt</a></div>');

  whole.append(editable).append('<span class="disabledText" ng-show="static">{{ngModel.$viewValue}}</span>' +
            '<a ng-click="changeStatic()" ng-show="static && !disable()">'+
            '&ltedit&gt</a>');

  tElem.replaceWith(whole);

  transclude(tElem,function(clone) {
    clone.removeAttr('inplace');
    editable.prepend(clone);
  });  

  return function(scope, element, attrs) {  

    var input_element = $($(element).find('input')[0]);
    scope.name = input_element.name;
    scope.ngModel = element.controller('ngModel');

    scope.static = true;

    scope.changeStatic = function() {
      if (scope.static) {
        scope.static = false;
      } else if (!scope.ngModel.$error[scope.name]){
        scope.static = true;
      }
    };
  };
};

return {
  transclude: 'element',
  scope: { disable: '&ipDisable' },
  restrict: 'A',
  compile: compile
};  
});

Upvotes: 0

Views: 215

Answers (2)

velochy
velochy

Reputation: 403

Ed's proposed solution solved a part of the problem. However, there was another one that probably threw me off in the first place:

The template was compiled into the parent scope as opposed to being attached to the new directive scope. To fix that, I needed to manually compile the created template in the linker function, where I could bind it to the proper scope.

The working solution is: http://jsfiddle.net/HbYwX/5/

 myApp.directive('inplace', function($compile) {

var compile = function(tElem,tAttrib,transclude) {

  var whole = $('<div ng-scope></div>');

    var editable = $('<div class="editable-transclude" ng-hide="static">'+
                  '<a ng-click="changeStatic()" ng-show="!static && ipDisable()">'+
                  '&ltsave&gt</a></div>');

  transclude(tElem,function(clone) {
    clone.removeAttr('inplace');
    clone.attr('ng-model','model');
    editable.prepend(clone);
  });

  whole.append(editable).append('<span class="disabledText" ng-show="static">{{model}}</span>' +
            '<a ng-click="changeStatic()" ng-show="static && !ipDisable()">'+
            '&ltedit&gt</a>');


  return function(scope, element, attrs) {

    element.replaceWith($compile(whole)(scope));

    scope.name = attrs.name;

    scope.static = true;

    scope.changeStatic = function() {
      if (scope.static) {
        scope.static = false;
      } else {
        scope.static = true;
        if (scope.name) scope.$emit('inplace-edit',scope.name);
      }
    };
  };
};

return {
  transclude: 'element',
  scope: { ipDisable: '&', model: '=' },
  restrict: 'A',
  compile: compile
};  
});

(which might be useful to anyone looking for something similar and is under MIT licence i.e. do what you wish with it).

Upvotes: 1

Ed_
Ed_

Reputation: 19098

This is because you're moving the input element inside an element that has an isolate scope, so it can no-longer interact with the scope outside of it. As such, the uname you have bound to will not be on the same scope as the one you're feeding into the ng-model of the input.

You've got a couple of options - the first is not to create the isolate scope at all - you can still access ipDisable through attrs in your link function.

The other (better) solution, is to add ngModel to the isolate scope as well (scope: { disable: '&ipDisable', ngModel:'='},), and update the value of the input yourself using the ngModelController, when the input is changed.

Upvotes: 1

Related Questions