SD.
SD.

Reputation: 9549

Modal window with Angular (UI-Router / Directive)

I have spent some time now looking into a generic way of controlling a modal window with AngularJS and none of the proposed options are anywhere near 'good' solution.

The Directive Solution

I found this demo, however the downside of that is you have to manually manage and store the state of the modal and cross-scope update it:

scope.$parent[attrs.visible] = true;

Also if you had to add more functionality like actually adding an item with a popup that would involve even more ugly code on the parent page scope.

The UI-Router Solution

This is the official guide on how to use modals with ui router.

This however is using ui.bootstrap.modal

My question is, is there any simple and elegant solution to what is quite frankly a very simple problem...

Something like this for example:

.state('popup', {
        url: '/item/add/',
        views: {
            'popupView': {
                templateUrl: "/myPopup.html",
                controller: "myPopupController"
            }
        },
        type: 'modal'
    })

And things like close, redirect, submit, all are handled in the myPopupController.

I am not seeking an explanation of why the examples are above are as they are, or how I should be using them. I am simply looking if anyone has come up with a better solution.

Upvotes: 7

Views: 1872

Answers (3)

user1364910
user1364910

Reputation:

I've been working for some time now on a modal/dialog plugin for ui-router (utilising it and ui-router-extras).

I'm in the process of porting some in house code to an open source project, and it'd be a huge benefit (certainly so for me) if you would be open to giving it a try.

We are currently using the in house version in our production application and it is working quite well. My goal is to make the open source version even better in terms of consumer API and performance.

The configuration phase

var app = angular.module('mod', ['angular.ui.router.modal', /** ui-router + ui-router-extras **/]);

app.config(function ($uiRouterModalProvider) {
  $uiRouterModalProvider.config({
    viewName: 'my_modal_view_name',
    templateUrl: '/path/to/my_modal_wrapper.html',
    rootState: 'name_of_my_apps_root_state',
    controller: 'my_modal_wrapper_controller',
    resolve: {
      common: function ($http) {
        return $http.get(apiUrl + '/common_modal_settings');
      }
    }
  });
});

Let's go through the different parts:

viewName

This is the name of the ui-view that your modal template will live in. It is required that you have a container in your page with the ui-view attribute pointing to a view of $uiRouterModalProvider.viewName that lives beside your regular ui-view for your regular content (example will be shown later).

The package provides a directive that does this for you

templateUrl

This is the base of all of your modals. Say you wanted a container with certain properties, and a $scope that is independent of the inner contents of the modal at any given point in time.

rootState

This is the name of the root state in your application. It is important that this matches that of the state that holds your root ui-view element and the modal ui-view element.

controller

The name of your common modal controller. Can also be a function (though I would always recommend a named controller that can be unit tested in isolation).

resolve

The common resolve block for each modal. This is interdependent of each modal states own resolve block. The API and behaviour regarding resolves is still in flux.


The registration-of-states phase

Now that the base behaviour is configured, this is how you would register a modal state:

.modalState('home.mySuperModal', {
  url: '/my-super-modal',
  templateUrl: 'superModal.html',
  controller: 'SuperModalController'
});

The .modalState function will register a state and reference the common settings set in the modalProvider and convert the given state definition into the final state object, looking like so:

.state('home.mySuperModal', {
  url: '/my-super-modal',
  views: {
    'modal_view_name@root_state': {
      templateUrl: 'modal_wrapper.html',
      controller: 'ModalWrapperController'
    }
  },
  $$originalState: {
    templateUrl: 'superModal.html',
    controller: 'SuperModalController'
  }
});

The markup

<!-- The outermost ui-view element -->
<html>
  <body ng-app="app">
    <div ui-view></div>
  </body>
</html>

<!-- The content for the root state of your application {living inside the outermost ui-view directive} -->
<div>
  <ui-view></ui-view>
  <ui-modal-view></ui-modal-view>
</div>

Now the container for our modal wrapper is set up. But what about the inner contents?

This is where another directive comes into play; uiModalFill. This needs to be present in your wrapping modal template. As such:

<!-- wrapping modal template -->
<div id="modal_wrapper">
  <h1>Some heading. I'm always present!</h1>

  <hr>

  <ui-modal-fill></ui-modal-fill>
</div>

Conclusion

Some final notes before (if) you feel like giving this a go:

  • A lot of the desired functionality has not yet been ported/implemented. It's a work in progress as it stands.
  • Some of the API's may very well change in the near future, including but not limited to the resolve property and the way a $state.resolve lives together with the common modal.resolve.
  • Some of the boilerplate requirements are due for a change. The API should be made easy to use (yet flexible), but right now there's too much details that the consumer must fill in.
  • The documentation is virtually non existent. The best way to figure out what is actually going on behind the scenes is to look into the source (sorry...).

So, if I haven't managed to scare you off from giving this a go - head on over to GitHub and grab angular-ui-router-modal and give it a go. I'm sort of always available on GitHub and/or on SO if you need any help.

Or if you would just like to pester me about writing some documentation. I would understand that, fully.

Upvotes: 4

jakeforaker
jakeforaker

Reputation: 1657

Have you looked into AngularStrap http://mgcrea.github.io/angular-strap/#/modals ?

You can pass in a templateUrl in which you can bind a controller to your modal instance, therefore giving you full control (by setting ng-controller="ModalCtrl" on the top level element of the modal template).

You can programatically fire the modal via ng-click or set attributes in your html using the bs-modal directive.

http://plnkr.co/edit/h9BwU0dFTTdw59b6oTWv?p=preview

Upvotes: 0

DanEEStar
DanEEStar

Reputation: 6270

Well showing a directive is not something that is nicely done in only declarative way via directives. Also a solution via router/state handling is not something I would do personally.

My approach would be to handle modals with a Angular Service. With a service you can imperatively open your modals.

You already mentioned the ui.bootstrap.modal which in my opinion is a pretty good solution. But it is also very flexible.

I created a simple dialogService with a method openConfirmDialog which has its own controller:

(function() {
    'use strict';

    angular.module('app').factory('dialogService', dialogService);

    function dialogService($modal) {
        return {
            openConfirmDialog: openConfirmDialog
        };

        function openConfirmDialog(texts, props) {
            texts = texts || {};
            texts = angular.extend({
                title: '',
                okText: 'Yes',
                cancelText: 'No',
                bodyText: ''
            }, texts);

            props = props || {};
            props = angular.extend({
                okClass: 'btn-primary',
                cancelClass: 'btn-default'
            }, props);

            return $modal.open({
                templateUrl: 'dialogconfirm.html',
                controller: ConfirmController,
                controllerAs: 'vm',
                resolve: {
                    texts: function() {
                        return texts;
                    },
                    props: function() {
                        return props;
                    }
                }
            }).result;
        }

        function ConfirmController($modalInstance, texts, props) {
            var vm = this;
            vm.texts = texts;
            vm.props = props;

            vm.ok = function() {
                $modalInstance.close();
            };

            vm.cancel = function() {
                $modalInstance.dismiss();
            };
        }
    }
}());

This can be used very easily directly in my controllers:

  vm.openDialog = function() {
    dialogService.openConfirmDialog({
      bodyText: 'Do you really want to close me'
    });
  };

Here is a working Plunker

With this solution I don't have to pollute my controller and/or HTML-view with code for the model. The modal code is very well encapsulated.

Upvotes: 0

Related Questions