Skip to content

Instantly share code, notes, and snippets.

@zxbodya
Last active August 29, 2015 14:12
Show Gist options
  • Save zxbodya/25da6f157f0fc9ea723b to your computer and use it in GitHub Desktop.
Save zxbodya/25da6f157f0fc9ea723b to your computer and use it in GitHub Desktop.
How to implement modals in angular

How to implement modal in angular

Disclaimer: Originally this was answer on stackoverflow... Question was removed, so - answer was not published. But it is good as an example how to do modals or something like in angular

At first, take a look at ui-bootstrap modal implementation, it is a good example how modals can be implemented in angular.

Recently, I have some experience implementing my own modals for states in ui-router, my implementation was inspired by ui-bootstrap modal.

Main part is modal service(js file below), it is responsible for:

  1. template loading
  2. resolving and injection parameters for modal controller
  3. creating scope, displaying modal (add compiled template into DOM, applying some styles required to properly display it...)
  4. closing modal (removing modal from DOM, destroying scope...)

This service can be used without ui-router - it just displays modal.

Example of use:

app.controller('MainCtrl', function($scope, modal) {
  var counter = 0;
  $scope.openModal = function(){
    modal({
      template:'<div class="modal"> Hello from modal <button ng-click="close()">x</button></div>',
      resolve:{...},
      controller:function($scope, $modalInstance, ...){
         $scope.close = function(){
           $modalInstance.close('name from modal #'+number);
         }
      }
    }).then(function(modalInstance){
      modalInstance.open().then(function(v){
        // do something when modal was closed
      });
    });
  }
});

template:

<div ng-controller="MainCtrl">
  <button ng-click="openModal()">Open Modal</button>
</div>

Demo on plunker

To use this modal service with ui-router state, you can onEnter and onExit callbacks and resolve for state:

$stateProvider.state('modal', {
  url: '/modal',
  resolve: {
    modalInstance: function (modal) {
      return modal({
        templateUrl: 'views/modal.html',
        controller: 'ModalCtrl'
      });
    }
  },
  onEnter: function (modalInstance) {
    modalInstance.open();
  },
  onExit: function (modalInstance) {
    modalInstance.close();
  }
})
app.factory('modal', function($document, $compile, $rootScope, $controller, $q, $http, $templateCache, $injector) {
var body = $document.find('body');
var container = angular.element($document[0].getElementById('modal-root'));
var modalCounter = 0;
/**
* @param options.template
* @param options.templateUrl
* @param options.controller
* @param options.scope
*
* @returns {Promise}
*/
function createModal(options) {
if (!options.template && !options.templateUrl) {
throw new Error('Either templateUrl or template options should be specified');
}
function getTemplate() {
return options.template ? $q.when(options.template) :
$http.get(options.templateUrl, {
cache: $templateCache
}).then(function(result) {
return result.data;
});
}
function getResolves(resolves) {
var promisesArr = [];
angular.forEach(resolves, function(value) {
if (angular.isFunction(value) ||
(angular.isArray(value) && angular.isFunction(value[value.length - 1]))) {
promisesArr.push($q.when($injector.invoke(value)));
} else {
promisesArr.push($q.when(value));
}
});
return promisesArr;
}
return $q.all(
[getTemplate()].concat(getResolves(options.resolve || {}))
).then(function(tplAndVars) {
return {
html: tplAndVars[0],
open: function() {
this.deffered = $q.defer();
this.element = angular.element('<div/>').append(this.html);
this.scope = (options.scope || $rootScope).$new();
if (options.controller) {
var ctrlLocals = {};
ctrlLocals.$scope = this.scope;
ctrlLocals.$modalInstance = this;
var i = 1;
angular.forEach(options.resolve, function(value, key) {
ctrlLocals[key] = tplAndVars[i++];
});
this.ctrlInstance = $controller(options.controller, ctrlLocals);
}
$compile(this.element)(this.scope);
if (modalCounter === 0) {
body.addClass('modal-opened');
}
container.append(this.element);
modalCounter += 1;
return this.deffered.promise;
},
close: function(value) {
this.element.remove();
this.scope.$destroy();
modalCounter -= 1;
if (modalCounter === 0) {
body.removeClass('modal-opened');
}
this.deffered.resolve(value);
},
ctrlInstance: null
};
});
}
return createModal;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment