Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoid using $timeout in Angular UI Modal

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;
});
like image 564
ps0604 Avatar asked Oct 18 '17 01:10

ps0604


2 Answers

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

like image 165
Maxim Shoustin Avatar answered Sep 22 '22 09:09

Maxim Shoustin


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;
});
like image 32
michal.jakubeczy Avatar answered Sep 22 '22 09:09

michal.jakubeczy