Stateful modals with Angular UI router

July 03, 2015Marc Perrin-Pelletier3 min read

Stateful modals with Angular UI router

Modals are very useful to capture user focus, thus enhancing user experience.
Their use was largely popularized by Twitter Bootstrap and now by its Angular-equivalent: Angular UI Bootstrap.

This article assumes you are familiar with the Angular UI router and Angular. Code samples are written in Coffeescript and Jade.

Creating a modal with UI Bootstrap

First you need to set up the action trigger. In our case, we'll use a simple button.

# home/states/main/view.jade (View from which the modal is launched)
//...
button.btn.btn-default(ng-click="launchModal()")
//...

launchModal is called by the ng-click directive and triggers the modal. If you need to pass arguments to your modal, you can add a resolve attribute, as shown below.

# home/states/main/controller.coffee  (Controller of the view from which the modal is launched)
angular.module 'home-module'
  .controller 'HomeController', ($scope) ->
    $scope.foo = 'bar'
    $scope.launchModal = ->
      modalInstance = $modal.open
        animation: true
        templateUrl: 'home/modals/mymodal/view.html'
        size: 'lg'
        controller: 'MyModalCtrl'
        resolve:
          myVar: ->
            $scope.foo

      modalInstance.result.then (anotherVar) ->
        $scope.anotherVar = anotherVar

Notice myVar is then available in the Modal controller by adding it as a dependency.

# home/modals/mymodal/controller.coffee (Modal controller)
angular.module 'home-module'
  .controller 'MyModalCtrl', ($scope, $modalInstance, myVar) ->
    $scope.myVar = myVar
    $scope.ok = ->
      // ...
      $modalInstance.close $scope.anotherVar
    $scope.cancel = ->
      $modalInstance.dismiss 'cancel'

The Modal view :

# home/modals/mymodal/view.jade (Modal view)
.modal-header
  h3.modal-title
    span This is the modal title.
    span.pull-right
      i.fa.fa-remove.cursor(ng-click="cancel()")
.modal-body
  p
    This is the modal body. `myVar` is available here : {{ myVar }}
.modal-footer
  button.btn.btn-primary(ng-click="ok()")
  button.btn.btn-link(ng-click="cancel()")

If need be, you may return a variable on modal closure, in our case anotherVar. This variable is passed down to the modal promise.

# home/states/main/controller.coffee  (Controller of the view from which the modal is launched)
angular.module 'home-module'
  .controller 'HomeController', ($scope) ->
    $scope.foo = 'bar'
    $scope.launchModal = ->
      modalInstance = $modal.open
        animation: true
        ...

      modalInstance.result.then (anotherVar) ->
          console.log 'Promise has resolved'
          $scope.anotherVar = anotherVar
        , ->
          console.log 'Promise was rejected'

Making it stateful

A great way to improve the ergonomy of your application is to make some modals stateful: if your modal represents a key step in your application - login, subscribe, view my cart, etc-, as opposed to an alert or confirmation modal, then it should have its own url.

This is made possible by Angular UI Router, by linking your modal to a state with onEnter:

# home/module.coffee
angular.module 'home', [...]
.config ($stateProvider) ->

  $stateProvider
    .state 'home',
      url '/home'
      ...

    .state 'home.properties',
      url: '/properties/:foo'
      onEnter: ($modal, $state, $stateParams) ->
        modalInstance = $modal.open
          animation: false
          templateUrl: 'home/modals/mymodal/view.html'
          controller: 'MyModalCtrl'
          size: 'lg'
          resolve:
            myVar: ->
              $stateParams.foo

        modalInstance.result.finally ->
          $state.go '^'

The state home.properties is a child state of home. It will load its template in its parent's ui-view, as demonstrated below. Moreover the modal is triggered by a ui-sref attribute, as you would do with a link. Finally, $state.go '^' redirects you to the parent state when the modal promise is resolved.

# home/states/main/view.jade (View from which the modal is launched)
//...
button.btn.btn-default(ui-sref="home.properties({foo: 'bar'})")
//...
.ui-view

Conclusion

That's all folks! If you want to see a live example of stateful modals, you can check out Trello.

Marc Perrin-Pelletier

Marc Perrin-Pelletier

Web Developer at Theodo