Trying to be more Angular with a modal dialog

Angular has been in the news a bit of late. The announcement of Angular 2.0, scheduled for release at the end of 2015, has got many people frothing at the mouth either for or against the proposed changes to the framework.

For the record I’m quite relaxed about it, partly because I’ve only been using Angular for a few months. I’m also a former (and still occasional) Flash Developer so I know what it’s like to see a chosen technology die from a thousand cuts. I don’t think this will happen to Angular, irrespective of the more performance-based newer frameworks like React/Flux, and also native ES6 web components.

The recent flame wars have also prompted some good posts on Angular optimisation, and made me google some older ones. Here are two posts I’ve been reading and re-reading:

  • https://www.airpair.com/angularjs/posts/angularjs-performance-large-applications
  • http://nathanleclaire.com/blog/2014/04/19/5-angularjs-antipatterns-and-pitfalls/

With all that in mind, here is one of my early attempts at an Angular directive - a humble modal dialog. I’m trying to be more Angular here. Which means I’m trying to be more declarative in a way that works with the Angular framework.

This article assumes some knowledge of Angular scopes. I would also recommend reading the blog posts linked above.

Modal directive

The things I wanted from this modal were as follows:

  • One concrete instance to serve all modal dialog requirements from anywhere in the app
  • The ability to configure any number of modal messages from one place
  • Be able to send a target id with an OK click event (e.g. to confirm you want to delete a specific item)
  • To have an optional cancel button
  • To be able to style the modal however I wanted

Firstly here is a fiddle of the modal directive demo:

And to better understand the code clone this repo: https://github.com/wmyers/angular-modal-demo

Using a service as a bus to update bindings

How am I being more Angular? Well I’ll concentrate on one thing in this post - using a service as a pub/sub bus. This is a more declarative approach. Rather than using $emit from a given nested scope to tell the modal in the $rootScope to show itself, I am simply binding the ModalService directly to both the scope of the ui requesting a modal and the scope of the modal directive itself. See a simple example of this here.

Services are singletons so both bindings point to the same object. Also I am binding the whole ModalService object rather than a primitive type property of the service, so the binding works correctly. See “1. Not having a dot in your ng-model” here.

In this snippet ModalService is injected into the modal directive:

angular.module('modalComp')
.directive('modal', function($rootScope) {
  return {
    restrict: 'EA',
    scope: {},
    transclude: true,
    controller: function($scope, ModalService){ //injecting ModalService into the controller
      //putting the data/service in the $scope directly, Angular will handle changes for you.
      $scope.service = ModalService;
      //service api for modalContent child directives
      this.getService = function(){
        return $scope.service;
      };
    },
    link: function(scope, element, attrs) {
      scope.okButtonClick = function(){
        //broadcast ok event from root with clone of service.config
        $rootScope.$broadcast(
          'modalOKClickEvent',
          _.clone(scope.service.config));

          //reset values in the service
          scope.service.reset();
        };

      }
    ...

And here ModalService is injected into the main modalDemo app module (which contains the UI to trigger the modal dialog - normally this would be in a more nested scope):

angular.module('modalDemo', ['modalComp'])
.controller('modalDemoCtrl',
[
'$scope',
'ModalService',
function (
  $scope,
  ModalService // ModalService injected
) {

  //config attached to modal OK button click events
  $scope.modalOKConfig = null;

  $scope.launchModal = function(contentId, targetId, cancellable){
    $scope.modalOKConfig = null;
    targetId = targetId || 0;
    cancellable = cancellable || false;
    var config = { contentId:contentId, targetId:targetId, cancellable:cancellable };
    ModalService.show(config); //calling show function in ModalService with a config that gets passed back by the OK click event
  };
  ...

N.B. I’m still using a $broadcast from the $rootScope to propagate the modal OK click event, but I’m not sending an $emit up the scope hierarchy when I want to trigger the modal.

Written on December 23, 2014