0

I'm trying to migrate from angular 1.2.28 to angular 1.3.16, however my code broke.

Angular 1.2.28 working: http://plnkr.co/edit/XfVakwA3Upm7Z2wosHCQ?p=preview

Angular 1.3.16 not working: http://plnkr.co/edit/4VxcHL0MHddobkmu9DMG?p=preview

JS

var app = angular.module('app', []);
app.run(function($rootScope, $timeout){
  $rootScope.loading = true;
  $timeout(function(){
    $rootScope.items = ['Angular', '1.3.16', 'doesnt work'];
    $rootScope.loading = false;
  }, 3000);
});

app.directive('refresh', function(){
  return {
    restrict: 'A',
    require: '^myDirective',
    link: function(scope, element, attrs, ctrl){
      if(scope.$last)
        ctrl.init();
    }
  };
});

app.directive('myDirective', function(){
  return {
    restrict: 'E',
    replace: true,
    transclude: true,
    template: '<div class="my-directive"><p>Height: {{myHeight}}</p> <div ng-transclude></div></div>',
    controller: function($scope, $element){
      this.init = init;

      function init(){
        $scope.myHeight = $('.my-directive').height();
      }
    }
  };
});

HTML

<!DOCTYPE html>
<html ng-app="app">

  <head>
    <script data-require="angular.js@1.3.16" data-semver="1.3.16" src="https://code.angularjs.org/1.3.16/angular.js"></script>
    <script data-require="jquery@1.11.0" data-semver="1.11.0" src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>

  <body>
    <h1>Angular 1.3.16</h1>
    <div ng-show="loading">Loading...</div>
    <my-directive ng-hide="loading">
      <div ng-repeat="item in items" refresh>
        <p>{{item}}</p>
      </div>
    </my-directive>
  </body>

</html>

The idea is to only run certain code when the inner html is outputted. Height is 0 in angular 1.3.16.

However, if I remove ng-hide="loading" from <my-directive ng-hide="loading"> in angular 1.3.16, height gets the appropriated value.

Any ideas how can I solve this?

Claies
  • 22,124
  • 4
  • 53
  • 77
JobaDiniz
  • 862
  • 1
  • 14
  • 32
  • 1
    Don't know why it doesn't work anymore, but I find this way of doing quite convoluted. Here's a modified example doing the same thing (and working): http://plnkr.co/edit/ULkWW8Ir75EJ0RfMHsau?p=preview. Notes: include jquery *before* angular, use ng-if instead of ng-hide to execute the directive only once loading is done, use the element instead of a class selector. – JB Nizet Jun 20 '15 at 14:44
  • Hi, $timeout is a bit of a hack, don't you say? You said *"I find this way of doing quite convoluted"*, so how can I improve the code I wrote? – JobaDiniz Jun 20 '15 at 19:21
  • Yes, it's a bit of a hack. But I find it much less of a hack than relying on an additional directive that calls a function of the first one if the scope happens to have a truthy $last property because the transcluded template happens to use ng-repeat. My code doesn't need any other directive, and will work whatever the transcluded template is. The timeout is there so that the height is computed after the directive has been rendered by the browser. – JB Nizet Jun 20 '15 at 19:25
  • So, do you know how can I rewrite this? Without $timeout and different than what I did? I'd really appreciate. – JobaDiniz Jun 20 '15 at 19:27
  • If I knew how to avoid the $timeout hack, I would avoid it. It's the cleanest solution I know of unfortunately. This is also what is suggested in http://stackoverflow.com/questions/11125078/is-there-a-post-render-callback-for-angular-js-directive. – JB Nizet Jun 20 '15 at 19:31
  • Well... I just tested my sample using `ng-if="!loading"` and it worked. So it is `ng-hide` the issue here. Your sample also doesn't work with `ng-hide`. Why is that? I don't want to use `ng-if` in this case. – JobaDiniz Jun 20 '15 at 19:35
  • ng-if is an intrinsic part of the solution. It makes sure the directive is executed only after loading becomes false. If it executes before, then the height will be computed immediately, while the element is still hidden, and the height will thus be 0. If you use ng-hide/ng-show, you'll have to watch the value of loading, and only compute the height after is becomes false: http://plnkr.co/edit/3oVDSUvGo9kOIUkQA54l?p=preview – JB Nizet Jun 20 '15 at 19:45
  • @JBNizet, make an answer so I can mark it. I'll go with `ng-if`and `$timeout`. Thanks. – JobaDiniz Jun 23 '15 at 20:26

2 Answers2

1

var app = angular.module('app', []);
app.run(function($rootScope, $timeout) {
  $rootScope.loading = true;
  $timeout(function() {
    $rootScope.items = ['Angular', '1.3.16', ' work'];
    $rootScope.loading = false;
  }, 1000);
});


app.directive('myDirective', function($timeout) {
  return {
    restrict: 'E',
    replace: true,
    transclude: true,
    template: '<div class="my-directive"><p>Height: {{myHeight}}</p> <div ng-transclude></div></div>',
    link: function($scope, $element) {

      $element.on('DOMAttrModified DOMSubtreeModified', init);

      function init() {
        $scope.$apply(function() {
          $scope.myHeight = $element.height();
        });
      }
    }
  };
});
<!DOCTYPE html>
<html ng-app="app">

<head>
  <script data-require="jquery@1.11.0" data-semver="1.11.0" src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
  <script data-require="angular.js@1.3.16" data-semver="1.3.16" src="https://code.angularjs.org/1.3.16/angular.js"></script>
  <link rel="stylesheet" href="style.css" />
  <script src="script.js"></script>
</head>

<body>
  <h1>Angular 1.3.16</h1>
  <div ng-show="loading">Loading...</div>
  <my-directive ng-hide="loading">
    <div ng-repeat="item in items" refresh>
      <p>{{item}}</p>
    </div>
  </my-directive>
</body>

</html>

You have to set the height in the correct angular directive phase/lifecycle. You should set the hight in the link or even postlink phase. Usually the two phases are the same if you don't use prelink This is when all the content has already been rendered. See angular $compile or google for angular post link

The controller is for the logic and the link is for html/dom manipulations.

EDIT: You can bind 'DOMAttrModified DOMSubtreeModified` events to trigger changes.

Michael
  • 3,085
  • 1
  • 17
  • 15
  • Hi, I always didn't like this `refresh directive`, but I don't know how can I write it better. Whenever I write a transclude directive and when its content has a `ng-repeat`, I always have to write some `refresh directive` just like I did in order to know when transclude content finished rendering. And honestly, I've never find such examples.. do you have one? – JobaDiniz Jun 20 '15 at 19:26
1

Inject $timeout into your directive and put the init code block in $timeout(function(){ ... }) like this:

app.directive('myDirective', function($timeout){
  return {
    restrict: 'E',
    replace: true,
    transclude: true,
    template: '<div class="my-directive"><p><b>Height: {{myHeight}}</b></p> <div ng-transclude></div></div>',
    controller: function($scope, $element){
      this.init = init;

      function init(){
        $timeout(function(){
          $scope.myHeight = $('.my-directive').height();
        });
      }
    }
  };
});
Onur Gazioğlu
  • 501
  • 2
  • 12