Daimz Daimz - 5 months ago 176
AngularJS Question

Using ui-router with Bootstrap-ui modal

I know this has been covered many times and most articles refer to this bit of code: http://stackoverflow.com/a/21213422/1031184

But I just don't get it. I don't find that to be very clear at all. I also found this http://jsfiddle.net/sloot/ceWqw/3/ which was actually great, very helpful except this doesn't add the url and allow for me to use the back button to close the modal.




Edit: This is what I need help with.

So let me try explain what I am trying to achieve. I have a form to add a new item, and I have a link 'add new item'. I would like when I click 'add new item' a modal pops up with the form I have created 'add-item.html'. This is a new state so the url changes to /add-item.
I can fill out the form and then choose to save or close. Close, closes the modal :p (how odd) . But I can also click back to close the modal as well and return to the previous page(state).

I don't need help with Close at this point as I am still struggling with actually getting the modal working.




This is my code as it stands:

Navigation Controller: (is this even the correct place to put the modal functions?)

angular.module('cbuiRouterApp')
.controller('NavbarCtrl', function ($scope, $location, Auth, $modal) {
$scope.menu = [{
'title': 'Home',
'link': '/'
}];

$scope.open = function(){

// open modal whithout changing url
$modal.open({
templateUrl: 'components/new-item/new-item.html'
});

// I need to open popup via $state.go or something like this
$scope.close = function(result){
$modal.close(result);
};
};

$scope.isCollapsed = true;
$scope.isLoggedIn = Auth.isLoggedIn;
$scope.isAdmin = Auth.isAdmin;
$scope.getCurrentUser = Auth.getCurrentUser;

$scope.logout = function() {
Auth.logout();
$location.path('/login');
};

$scope.isActive = function(route) {
return route === $location.path();
};
});


This is how I am activating the modal:

li(ng-show='isLoggedIn()', ng-class='{active: isActive("/new-item")}')
a(href='javascript: void 0;', ng-click='open()')
| New Item


new-item.html:

<div class="modal-header">
<h3 class="modal-title">I'm a modal!</h3>
</div>
<div class="modal-body">
<ul>
<li ng-repeat="item in items"><a ng-click="selected.item = item">{{ item }}</a></li>
</ul>Selected:<b>{{ selected.item }}</b>
</div>
<div class="modal-footer">
<button ng-click="ok()" class="btn btn-primary">OK</button>
<button ng-click="close()" class="btn btn-primary">OK</button>
</div>


Also whilst this does open a modal it doesn't close it as I couldn't work that out.

Answer

It's intuitive to think of a modal as the view component of a state. Take a state definition with a view template, a controller and maybe some resolves. Each of those features also applies to the definition of a modal. Go a step further and link state entry to opening the modal and state exit to closing the modal, and if you can encapsulate all of the plumbing then you have a mechanism that can be used just like a state with ui-sref or $state.go for entry and the back button or more modal-specific triggers for exit.

I've studied this fairly extensively, and my approach was to create a modal state provider that could be used analogously to $stateProvider when configuring a module to define states that were bound to modals. At the time, I was specifically interested in unifying control over modal dismissal through state and modal events which gets more complicated than what you're asking for, so here is a simplified example.

The key is making the modal the responsibility of the state and using hooks that modal provides to keep the state in sync with independent interactions that modal supports through the scope or its UI.

.provider('modalState', function($stateProvider) {
    var provider = this;
    this.$get = function() {
        return provider;
    }
    this.state = function(stateName, options) {
        var modalInstance;
        $stateProvider.state(stateName, {
            url: options.url,
            onEnter: function($modal, $state) {
                modalInstance = $modal.open(options);
                modalInstance.result['finally'](function() {
                    modalInstance = null;
                    if ($state.$current.name === stateName) {
                        $state.go('^');
                    }
                });
            },
            onExit: function() {
                if (modalInstance) {
                    modalInstance.close();
                }
            }
        });
    };
})

State entry launches the modal. State exit closes it. The modal might close on its own (ex: via backdrop click), so you have to observe that and update the state.

The benefit of this approach is that your app continues to interact mainly with states and state-related concepts. If you later decide to turn the modal into a conventional view or vice-versa, then very little code needs to change.