Sunday, July 26, 2015

Modal windows available by URL binding in AngularJS with ui-bootstrap and ui-router

It's well known how ui-bootstrap supports modal windows. These modals don't have any URL and cannot be accessed by URL, though. This is very good, because they should stay as the main view subview only, don't mess with the browser history and first and foremost they shouldn't kill underlying main view controller, what would reset its current state (like filters applied, pagination, etc). This would be a problem using URL-based modals, especially with ui-router which seems to be a standard for now.

On the other hand it's good to have a URL-bound modals for one usage. When you send to people notification emails with links to perform the requested action. I mean such emails like "Attention, you have to do something in the application. You can do it with this link.". In typical scenario this action is already available in the application, usually on modal window. You just want to send this modal link to the user, to trigger requested action, and if you can't bind your modal to the URL, you can only send him some more or less awkward message, like "Use this link to login to the application, and then ... dig yourself to find where you can do it.".

Here is a little ui-router hack to achieve both things - normal, unobtrusive modals, and URL bindings.

NOTE, that this is not about having modals with ui-router URL address. There're many implementations on stackoverflow, and also example in ui-router FAQ. But these solutions have a very big flaw - their modals kill underlying main view controller, and when you're back, you're really back (like with the back button) on the main view with new controller instance and reset whole state, what is unnacceptable for real world applications. Maybe next time I'll think a little about how to achieve this point without this problem, but now it's about having normal bootstrap modals also bound to URL addresses in ui-router.

Firstly, you need to define in your main HTML the location where the modal will be rendered, if it's URL-bound. Let's make it simple as modal.html (all codes are inserted at the bottom of this post). You can define surrounding view as main view, and use it for the modal target (when the modal window is rendered) or the whole application layout target (for views other than modal windows) by appropriate router substates configuration and CSS.

Afterwards, let's define simple module.js with modal content, one URL parameter and resolve function that provides the resolved object to the modal controller, from the server side. At this point your modal is exposed at /modal/{ID} URL, and you may use it in your notification emails.

Now, let's support our modal as the regular ui-boostrap modal for other application views. This is done in modal.service.js by simple hack and moving parameters from ui-router configuration to ui-bootstrap $modal instance. In any view now, instead of calling $modal, you can call modalService(viewName, stateParams) to open your modal window previously defined in ui-router configuration, and at the same time you have this modal available by URL.

Source code