5

In this plunk I have an Angular UI Modal wrapped in a directive. From the controller, I call a method to open the modal, but to do so I need to use $timeout, otherwise, the DOM hasn't finished rendering the directive.

This seems to work, however, what would happen if whatever needs to be completed hasn't finished after the $timeout expires? The $timeout may work in a development environment but may fail in production. Is it a bad practice to use $timeout? How to avoid using it in this example?

HTML

<div modal control="modalCtl"></div>

Javascript

var app = angular.module('app', ['ui.bootstrap']);

app.controller('myCtl', function($scope,$timeout) {
    $scope.modalCtl = {};       
    $timeout(function(){
        $scope.modalCtl.openModal();
    },100);         
})
.directive('modal', function ($uibModal) {
    var directive = {};
    directive.restrict = 'EA';
    directive.scope = {    
        control: '='
    };
    directive.link = function (scope, element, attrs) {
        scope.control = scope.control || {};            
        scope.control.openModal = function() {
            scope.modalInstance = $uibModal.open({
                template: '<button ng-click="close()">Close</button>',
                scope: scope
            })
        };
        scope.close = function () {
            scope.modalInstance.close();
        };
    };
    return directive;
});
4
  • 1
    you should be ok with the timeout. I would decrease the time to 0, tho, since timeout will send your call to the bottom of the event loop. For best practice, I would create an init function in your controller where you can place your timeout (which would be better wrapped within it's own descriptive function) Commented Oct 18, 2017 at 3:05
  • I generally use service instead of directive in such a case. Your directive only job is calling $uibModal service and this is not a suitable job for directive. If you want to proxy just use service not directive... Commented Oct 18, 2017 at 5:20
  • @EduardoLaHozMiranda Can you give an example of the init function?
    – ps0604
    Commented Oct 19, 2017 at 13:04
  • in the bottom of your ctrl you would write: function init () { //call function that starts timeout here } and then at the very last line of your ctrl just trigger init(); Commented Oct 22, 2017 at 19:34

3 Answers 3

7
+150

To avoid using $timeout the directive can notify controller when everything is ready. Take a look:

.directive('modal', function ($uibModal) {      
    var directive = {};    
    directive.restrict = 'EA';    
    directive.scope = {    
            control: '=',
            onReady: '&'  // <-- bind `onReady` with  `onModalReady`            
        };

    directive.link = function (scope, element, attrs) {

      scope.control = scope.control || {};

      scope.control.openModal = function() {
          scope.modalInstance = $uibModal.open({
              template: '<button ng-click="close()">Close</button>',
              scope: scope
            })                
        };

        scope.close = function () {
            scope.modalInstance.close();
        };

      scope.onReady(); // <-- notify controller
    };    
    return directive;    
});

Out HTML:

 <div modal on-ready="onModalReady()" control="modalCtl"></div>

Our controller:

 $scope.onModalReady = function(){
   $scope.modalCtl.openModal();
 }

Changed Plunker


About comment @Eduardo La Hoz Miranda

you should be ok with the timeout. I would decrease the time to 0, tho, since timeout will send your call to the bottom of the event loop.

Generally when we initialize $timeout with 0 milliseconds or with no argument as:

 $timeout(function(){
   $scope.modalCtl.openModal();
 }); 

We delay $scope.modalCtl.openModal() to run before next digest cycle a.e. last in queue. So in this case directive link will run 1st from beginning to to the end and only after you will enter to $timeout.

The $timeout may work in a development environment but may fail in production.

On Production you have the same code. It should work. I believe the problem is in something else. If you are not confident with $timeout use above mentioned way I posted.

Your Logged Plunker

8
  • when you call scope.onReady(); why does that mean that the directive is ready? how do you know that the DOM was already loaded?
    – ps0604
    Commented Oct 23, 2017 at 16:47
  • @ps0604 link function, a.e. postLink, runs after the template is put in. So you fine here. Commented Oct 23, 2017 at 17:20
  • worked for me. What if I have another complex object (such a jQuery object wrapped in a directive) in the modal itself? should I chain the onReady calls?
    – ps0604
    Commented Oct 23, 2017 at 21:28
  • @ps0604 jQuery has its own callbacks when element is ready. Keep in mind, since jQuery is 3d party, to notify Angular we should wrap scope.onReady(); with $timeout 0 ms or $apply to trigger digest cycle. Commented Oct 24, 2017 at 4:25
  • sorry but this code looks awful, such interactions are bad. plnkr.co/edit/uBsosFwA95vUesjcxrtp?p=preview Commented Oct 26, 2017 at 19:27
1

When link function of directive is finished, it can emit a message that it's ready.

And controller listens to this message and displays modal when received.

Code:

var app = angular.module('app', ['ui.bootstrap']);

app.controller('myCtl', function($scope,$timeout) {
  $scope.modalCtl = {};
    $scope.$on("hey", function() {
    $scope.modalCtl.openModal();
  });
})
.directive('modal', function ($uibModal) {

  var directive = {};

  directive.restrict = 'EA';

  directive.scope = {    
    control: '='
  };

  directive.link = function (scope, element, attrs) {

  scope.control = scope.control || {};

  scope.control.openModal = function() {
    scope.modalInstance = $uibModal.open({
      template: '<button ng-click="close()">Close</button>',
      scope: scope
  })

  };

  scope.close = function () {
        scope.modalInstance.close();
  };

  scope.$emit("hey");
};

return directive;
});
0

Using timeout for any arbitrary waits on code executing is generally bad. As you state in your question, depending on the overall context of the page you are loading, you have no guarantee that the directive will be ready at the time your controller runs.

It seems like you have too many levels of abstraction here. Something is rendering a div that when it is fully rendered, shows a modal.

Wouldn't it make more sense to just have the thing that is rendering the div create and show the modal instead ?

1
  • The problem is that I cannot tell when a directive finished loading; for example if the directive wraps a JQuery object the DOM is available only after $timeout.
    – ps0604
    Commented Oct 23, 2017 at 16:25

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.