6

I'm trying to create a state which acts like a popup, i.e it doesn't clear the current state, it just pops over it without completely destroying the current state (So that the user can gain access to it by dismissing the popup).

Heavily simplified, my applications routes is something like the following:

angular.module('test', ['ui.router'])
  .config(['$stateProvider', '$urlRouterProvider',
    function($stateProvider, $urlRouterProvider) {
      $stateProvider
        .state('login', {
          url: '/login',
          template: '<button><a ui-sref="authenticated.home">Login</a></button>'
        })
        .state('authenticated', {
          url: '/authenticated',
          template: '<p>We are now authenticated</p>' +
            '<a ui-sref="authenticated.home">home</a>' +
            '<a ui-sref="authenticated.statistics">statistics</a>' +
            '<a ui-sref="authenticated.popup">Popup!</a>' +
            '<div ui-view></div>' +
            '<div ui-view="popup"></div>'
        })
        .state('authenticated.home', {
          url: '^/home',
          template: '<p>We are in home. <br><input></p>'
        })
        .state('authenticated.statistics', {
          url: '^/statistics',
          template: '<p>We are in statistics. <br><input></p>'
        })
        .state('authenticated.popup', {
          url: '^/popup',
          views: {
            popup: {
              template: '<div id="popup"><p>popup is up</p>' +
                '<a ui-sref="authenticated.home">close</a>' +
                '</div>'
            }
          }
        });
      $urlRouterProvider.otherwise('/login');
    }
  ]);
a {
  margin-right: 20px;
  text-decoration: none;
}
#popup {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 100%;
  background: #000;
  color: #fff;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.18/angular-ui-router.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.js"></script>
<div ng-app="test">
  <div ui-view>
  </div>
</div>

  • User is presented with a login screen
  • Once logged in, user is redirected to authenticated.home state. The authenticated parent state holds a navigation menu and <ui-view> to attach subviews
  • User can use this navigation to navigate around the application to other routes like authenticated.statistics, authenticated.popup.

The problem is that, when I simply move to the popup state, even though I have specified popup view inside it's views object, it clears the other ui-view (makes sense though, because we're no longer in a state that matches it).

One solution I can think of is to use something like ui-router-extras to go back to previous state, the problem with this is that any changes the user might have been making in the previous states will be lost.

Another solution will be to have the template of popup in the authenticated states template and show/hide it. But the problem with this is that, the popup should be a bookmark-able state, which loads data from server based on state params.

Is there a better approach to create a state that acts like a popup over current state? maybe by changing the template structure or using something like abstract-states that I haven't thought of?

2
  • Any reason why sticky states from ui-router-extras won't work? They have an example here and as far as i can tell there's no loss of info while using the modal. Commented May 1, 2016 at 10:29
  • @AhmedWagdi Thanks, that looks promising. Will check it out. Maybe you can post an answer Commented May 1, 2016 at 10:48

3 Answers 3

5
+50

The sticky states add on from ui-router-extras should be what you're looking for. Gives you the ability to create sticky/parallel states so it should allow you to create a popup state without affecting the original state you were in.

I haven't experimented enough with it to know all the details but the main idea is to move all you main states under a root app state and set sticky to true:

$stateProvider.state('app', {
      url: '',
      views: {
        'app': {
          templateUrl: 'app.html',
          controller: 'mainCtrl',
        }
      },
      sticky: true
  });

  $stateProvider.state('app.account', {
      url: '/account',
      templateUrl: 'account.html'
  });

  $stateProvider.state('app.account.stuff', {
      url: '/stuff',
      template: "<h3>Here's my stuff:</h3><ul><li>stuff 1</li><li>stuff 2</li><li>stuff 3</li></ul>"
  });

After that add your modal state as a sibling (so not as a child of the root app state)

$stateProvider.state('modal', {
      url: '/modal',
      views: {
        'modal': {
          templateUrl: 'modal.html'
        }
      }
  });

I took the example provided in the docs and made a few edits to add controllers and simplify it: http://plnkr.co/edit/4JGdIcDUQs0Za4fBaLj1?p=preview

Sign up to request clarification or add additional context in comments.

Comments

2

sounds like something abstract states could fix.

changing your "authenticated" state to an abstract parent and attaching a controller to it can allow you to axx data being transformed by child states. Also you could just make a separate controller for your data and drop an ng-controller on the body tag.

If you're still having probs implementing, how about you use LocalStorage to persist the data you want?

Update2 Found a post and changed the plnk on updating ng-model as you type, but i see why OP wants a popup.

Edit Plnkr Shared State Example

I'll try to find a good example, but here's some pseudo code for now.

.state('authenticated', {
      abstract: true,
      url: '/authenticated',
      controller: 'AuthStateController',
      template: '<p>We are now authenticated</p>' +
        '<a ui-sref="authenticated.home">home</a>' +
        '<a ui-sref="authenticated.statistics">statistics</a>' +
        '<a ui-sref="authenticated.popup">Popup!</a>' +
        '<div ui-view></div>' +
        '<div ui-view="popup"></div>'
    })

I believe with or without {abstract: true} your popup state should have access to data inside of 'AuthStateController'. The resolve stuff may be an overkill solution, but might give you an idea of how to use the controllers data more efficiently.. if all else fails .. build a service to handle the data.

.state('authenticated.popup', {
      url: '^/popup',
      views: {
        popup: {
          resolve: {
            ctrl: 'AuthStateController',
            data: function(ctrl) {
              return ctrl.data_for_popup;
            }
          }
          template: '<div id="popup"><p>popup is up</p>' +
            '<a ui-sref="authenticated.home">close</a>' +
            '</div>'
        }
      }
    })

5 Comments

"changing your "authenticated" state to an abstract parent and attaching a controller to it can allow you to axx data being transformed by child states" - I'm not sure what you mean by "allow you to axx data". Could you show an example based on the demo in question? I already have controllers for each state. The problem is that the controller is re-initialized when coming back to that state. And I asked this question for a "popup like" state hoping to avoid having to handle storing the data myself
thanks for merger suggestion.. as long as data you need is stored in AuthStateController, it will be present through all child states of "authenticated"
Ah... I see, you're suggesting to have a shared parent controller with all the data for all child states and use prototypical inheritance... That' a nice idea but unfortunately the child states has their own controllers and their own data that is input by user, fetched from server etc... This data won't be present in the shared parent :/
i was able to come up with an example of how this works with children owning their own controllers. Plnkr Shared State Example
In the plunker if I type something in textbox, go to popup and come back, textbox is empty, which is what I meant by losing data, and secondly there won't be any menu in the popup state. app has to return to previous state when it is closed, which means previous state's DOM shouldn't be cleared. This is the behavior of a "popup", it's pops over existing content without destroying it.
0

Probably what you are looking for is a different on your main page. You need to keep 2 ui-view on your index.html or your base template.

And then while loading the states do something like:

 $stateProvider.state('stateNAme', {
                url: '/name',
                views: {
                    "main": {
                        controller: '1Controller',
                        templateUrl: 'tpl1.html'
                    },
                    "sidebar@": {
                        controller: '2Controller',
                        templateUrl: 'tpl2.html'
                    }
                }

Read here about the multiple named views

1 Comment

How will this achieve anything mentioned in the question..?! I'm using named views in the question itself.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.