Monday, May 8, 2017

Weird ngIf + $compile behavior

Leave a Comment

I'm trying to build a directive that would do the following:

  • add another directive to the element (ngSwipeRight for example)
  • add some custom behavior to the new directive.

An example would be: mySwipeBack that would add ngSwipeRight and when the user swipes over the element I would do history.back().

I tried like this:

.directive('swipe', function($compile){   return {     restrict: 'A',     compile: function(el){         // I removed all the actual logic for demo purposes         // here I would add ng-swipe-right plus a handler         el.removeAttr('swipe');         var fn = $compile(el);         return function (scope) {           fn(scope);         };    }  } }); 

But I ran into an issue with the following markup:

<div ng-if='true'>   <h1 swipe>OUTSIDE     <div ng-if="true">INSIDE</div>   </h1> </div> 

The "INSIDE" text doesn't get rendered. You can see the behavior in this jsbin: http://jsbin.com/tokofevuga/edit?html,js,output

If I remove the first ng-if, it works as intended.

Does anyone know what the reason is behind this - and if I can make it work?

Or if someone has another idea of how to achieve what I described above?

3 Answers

Answers 1

    return function (scope) {       $compile(el)(scope);     }; 

Work for me ... And the point is that in your code - you compile element immediately in directive compile, while here function is returned from compile, that will be executed somewhen later.

Answers 2

Since ng-if create its own scope, I think you just compile the element too early.

In the below code snippet, I moved compile part to link, and bind the element with the current scope of the directive itself.

var app = angular.module('jsbin', []);    app.controller('DemoCtrl', function() {    });    app.directive('swipe', function($compile) {    return {      restrict: 'A',      link: function($scope, el) {        el.removeAttr('swipe');        $compile(el)($scope);      }    }  });
<!DOCTYPE html>  <html>    <head>    <meta charset="utf-8">    <title>Angular JS</title>  </head>    <body ng-app="jsbin">    <div ng-controller="DemoCtrl">      <div ng-if='true'>        <h1 swipe>OUTSIDE          <div ng-if="true">INSIDE</div>        </h1>      </div>    </div>    <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.0/angular.js"></script>  </body>    </html>

Answers 3

Consider this. ngIf removes the whole element and replaces it with the comment. The expression is then watched for. The subsequent changes trigger the element from between DOM to REMOVED.

Now the problem is, you have placed the block inside the compile phase. The code for whole ng-if gets replaced by a single comment.

Regarding solution, there can be many. It all depends upon what goes well with the structure and needs if your app.

I propose two:

1) Move the logic from 'compile' to 'link' phase.

link: function(el){     el.removeAttr('swipe');     var fn = $compile(el);     return function (scope) {       fn(scope);     };  }, 

2) You will need to use transclusion.

app.directive('swipe', function($compile){  return {   restrict: 'A',   compile: function(el){     el.removeAttr('swipe');     var fn = $compile(el);     return function (scope) {       fn(scope);         };    },     transclude: true,     template: "<div ng-transclude></div>"  } }); 

The following forks of your sample might guide you:

http://jsbin.com/zecujonoqi/1/edit?html,js,output

http://jsbin.com/mubodevofa/1/edit?html,js,output

Hope it helps.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment