Join the Stack Overflow Community
Stack Overflow is a community of 6.5 million programmers, just like you, helping each other.
Join them; it only takes a minute:
Sign up

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.

share|improve this question
    
Are you able to close your modal window when clicking close? I guess no. right? – micronyks Jul 12 '14 at 13:38
    
No, I can't get it to close. I can only open it and even then not only does it not close but it isn't functioning with the state and url Change so things like active links won't work. – Daimz Jul 12 '14 at 13:46
    
I have made a Plunk with my latest attempt to solve this but it''s not working. plnkr.co/edit/k514Nc25zfr0amtnxXDu?p=preview – Daimz Jul 12 '14 at 14:24
    
I just saw your plunker I've to say I didnt get anything from that , its totaly wrong, You've defined a controller inside your open-modal function Brother it's better you explain your actual problem first,Believe me , a good question worth 100 bad answers – xe4me Jul 12 '14 at 15:05
up vote 42 down vote accepted

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.

share|improve this answer
    
That was sooooo helpful! Thanks so much, and it all makes sense too which is even better. I did run into one more problem tho. This works if I am on state main but if i go to another state about I get this Could not resolve '.add' from state 'about' I need a way to allow this to work on top of any state About, Main, Contact etc as it is accessible from the main navigation. – Daimz Jul 15 '14 at 11:48
    
The dot prefix is for relative navigation to a child state. If add is a sibling of about, then you would need an expression like ^.add to get to add from about. Take a look at the documentation for $state.go and see if that helps. – Nathan Williams Jul 15 '14 at 14:21
    
I had a read through, but I still can't get it working. I forked your plnkr and made a few changes to illustrate what I am doing. plnkr.co/edit/eupjB1i0djFGLcaGCD8j?p=preview Perhaps I am putting the '^' in the wrong place but I did also try put it in $state.go('^.add') and that didn't work either. – Daimz Jul 15 '14 at 21:52
1  
Relative expressions are used for navigation ($state.go). When declaring a state, you either have to use the fully-qualified name or reference a parent state. So ^.modal1 is not valid when declaring a state. Here's a working example. Every state has to exist on its own. You can reuse the templates and controllers, but there has to be a state declaration for main.modal1 and about.modal1. – Nathan Williams Jul 16 '14 at 14:14
1  
You can use the $stateNotFound event to define states on the fly. Each state must have its own definition, but you can reuse templates and controllers. The hierarchical nature of state definitions ensures that the names and urls are unique even if the content is the same. This example shows the basic idea. (Note that due to the way it's setup, it wouldn't work for bootstrap from a deep/direct link to modal2, but that's something you can address per the needs of your own implementation.) – Nathan Williams Jul 18 '14 at 16:24

Here is a provider that improves @nathan-williams solution by passing resolve section down to the controller:

.provider('modalState', ['$stateProvider', function($stateProvider) {
  var provider = this;

  this.$get = function() {
    return provider;
  }

  this.state = function(stateName, options) {
    var modalInstance;

    options.onEnter = onEnter;
    options.onExit = onExit;
    if (!options.resolve) options.resolve = [];

    var resolveKeys = angular.isArray(options.resolve) ? options.resolve : Object.keys(options.resolve);
    $stateProvider.state(stateName, omit(options, ['template', 'templateUrl', 'controller', 'controllerAs']));

    onEnter.$inject = ['$uibModal', '$state', '$timeout'].concat(resolveKeys);
    function onEnter($modal, $state, $timeout) {
      options.resolve = {};

      for (var i = onEnter.$inject.length - resolveKeys.length; i < onEnter.$inject.length; i++) {
        (function(key, val) {
          options.resolve[key] = function() { return val }
        })(onEnter.$inject[i], arguments[i]);
      }

      $timeout(function() { // to let populate $stateParams
        modalInstance = $modal.open(options);
        modalInstance.result.finally(function() {
          $timeout(function() { // to let populate $state.$current
            if ($state.$current.name === stateName)
              $state.go(options.parent || '^');
          });
        });
      });
    }

    function onExit() {
      if (modalInstance)
        modalInstance.close();
    }

    return provider;
  }
}]);

function omit(object, forbidenKeys) {
  var prunedObject = {};
  for (var key in object)
    if (forbidenKeys.indexOf(key) === -1)
      prunedObject[key] = object[key];
  return prunedObject;
}

then use it like that:

.config(['modalStateProvider', function(modalStateProvider) {
  modalStateProvider
    .state('...', {
      url: '...',
      templateUrl: '...',
      controller: '...',
      resolve: {
        ...
      }
    })
}]);
share|improve this answer
    
This is the right solution. – Damiox Aug 9 at 17:40
    
Thanks! this is the right solution! – Paul Preibisch Sep 21 at 23:53
    
Could you take a look at stackoverflow.com/questions/40767450/… please – Abhijit Mazumder Nov 25 at 6:22
    
From UI-Bootstrap 1.3.3, It is not necessary any more ! "$resolve" is available in template and his members can be injected in controller :) – TeChn4K Dec 8 at 16:02

I answered a similar question, and provided an example here:

Modal window with custom URL in AngularJS

Has a complete working HTML and a link to plunker.

share|improve this answer

The $modal itself doesn't have a close() funcftion , I mean If you console.log($modal) , You can see that there is just an open() function.

Closing the modal relies on $modalInstance object , that you can use in your modalController.

So This : $modal.close(result) is not actually a function!

Notice : console.log($modal); ==>> result :

          Object { open: a.$get</k.open() }
           // see ? just open ! , no close !

There is some way to solve this , one way is :

First you must define a controller in your modal like this :

   $modal.open({
      templateUrl: 'components/new-item/new-item.html',
      controller:"MyModalController"
    });

And then , Later on , :

    app.controller('MyModalController',function($scope,$modalInstance){
      $scope.closeMyModal = function(){
       $modalInstance.close(result);
        }
       // Notice that, This $scope is a seperate scope from your NavbarCtrl,
       // If you want to have that scope here you must resolve it

   });
share|improve this answer
    
I meantioned above that I tried a new method to get this working as shown in this plunk plnkr.co/edit/k514Nc25zfr0amtnxXDu?p=preview but the reason I bring this up it that method relies upon resolve as well. I had a look to try see what resolve was actually for but it didn't make sense, would you mind elaborating on how it works and why I have to resolve it to have the NavbarCtrl in there? – Daimz Jul 12 '14 at 14:27
    
I still dont get your problem :( 1- do you want to know what is the resolve and how it works ? 2- closeing modal is your problem ? – xe4me Jul 12 '14 at 14:56
    
@xe4me, This is right. there are also second method that you can use if you don't want to use $modalInstance. second way is, var myModal=$modal.open({ templateUrl: 'myHtml.html'}); myModal.close(); <-----this method would also work when you click close button. – micronyks Jul 12 '14 at 14:58
    
@Daimz. You need to be more specific when you ask any question. Bcos there are many ways to solve particular problem. But firstly you need to be specific what you what n how? We don't get your problem identified. so it becomes problematic for us to help you. – micronyks Jul 12 '14 at 15:02
    
@micronyks Yes , ofcourse , I wanted to write that too but I remembered in the official angular-ui-bootstrap they've explained it well , So I thought he maybe have seen that approach already ! But thanks – xe4me Jul 12 '14 at 15:02

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.