How to open a modal from any state using UI-Router
This post is a follow-up to a question that was asked on the AngularJS Poland Facebook group. One of the members wanted to open a modal window from anywhere in the app using UI-Router. At first this seemed simple enough. This is a common problem – so common that it has a section in the UI-Router FAQ.
Using a modal child state
The most simplified example goes like this:
In the above plunker we have two states defined – a parent state for the view (index
) and a child state for the modal (index.terms
). The child state doesn’t have a template nor controller as this isn’t an actually usable state. The magic happens because UI-Router executes the onEnter
callback. This callback has the dependency injection system build in, and can open the modal dialog using the $modal
service.
This works fine, unfortunately, this didn’t solve the initial problem – opening the modal window from anywhere in the app. In the above example, the modal state is a child of the index
state, and opening it from any state other than index
will cause UI-Router to switch to index
. To solve this problem we need to take a different approach.
Using the $stateChangeStart
event
This approach involves defining a placeholder state for the modal (terms
in the above plunker) which is a top-level state. Next we need to listen to the $stateChangeStart
event. UI-Router broadcasts this event from the $rootScope
using Angulars event system every time a state is about to change. Among the arguments passed to the event listeners this event passes the toState
object. We can use this to check which state is to be displayed, display the modal dialog using the $modal
service, and cancel the state transition using event.preventDefault()
. I would recommend listening to $stateChangeStart
in the run
block of the module – this is far more elegant than using an application-wide controller encapsulating the entire app.
The above example solves the problem of displaying the modal from anywhere in the application. Some users expressed that this solution has some down sides, like unnaturally separating the modal display logic from the router configuration block. I couldn’t think of an elegant solution to this problem. If you see one, drop a line in the comments.