Skip to content

Multiple parents for a state #384

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
ProLoser opened this issue Sep 4, 2013 · 28 comments
Closed

Multiple parents for a state #384

ProLoser opened this issue Sep 4, 2013 · 28 comments
Labels

Comments

@ProLoser
Copy link
Member

ProLoser commented Sep 4, 2013

This is more of an RFC.

Lets say I have a child state I want to use in multiple areas of my app. Or perhaps it's more like a mixin, etc. How would you go about doing this? Should I define multiple parent states, or what?

@jannis-seemann
Copy link

you could create a directive for that?

@laurelnaiad
Copy link

I guess with todays ui-router I would reuse templates and controllers in each place... and put my own service on top that knows how to define the same state specs in multiple places. Having different parents will have an impact on the context (e.g. inherited state params) one way or the other so they really arent the same states even if theyre only spec'ed out once.

@ProLoser
Copy link
Member Author

ProLoser commented Sep 5, 2013

It actually is a directive now, but it's like a modal with a form in it. I thought it would be cool if you could use a state change to trigger the modal.

@nateabele
Copy link
Contributor

This is for the login form thing? I don't think adding an extra child to every possible state makes sense here. The approach I would take is to listen for $stateChangeStart, and pass the event off to a service that checks if the user needs to log in, and triggers the dialog accordingly.

@laurelnaiad
Copy link

listen for$stateChangeStart, and pass the event off to a service that checks if the user needs to log in, and triggers the dialog accordingly

ditto, except I might have the service that does the checking be thing that does the listening. No reason that a service can't listen to $rootScope events. I actually create one service to wrap all the state events and to deal with managing transitions (I call it transitions) -- it does things like helping the app avoid trying to supercede transitions accidentally. It emits its own events to the rootScope. Another service manages session-releated stuff -- it listens to events from transitions and decides whether to interject itself to prompt a login.

@ProLoser
Copy link
Member Author

ProLoser commented Sep 5, 2013

Lets say it's a notes modal to add notes to this object I'm on.
On Sep 5, 2013 2:42 PM, "Stu Salsbury" [email protected] wrote:

listen for$stateChangeStart, and pass the event off to a service that
checks if the user needs to log in, and triggers the dialog accordingly

ditto, except I would have the service that does the checking be thing
that does the listening. No reason that a service can't listen to
$rootScope events


Reply to this email directly or view it on GitHubhttps://github.com//issues/384#issuecomment-23904054
.

@laurelnaiad
Copy link

Well, there's an argument to be made for keeping modal's out of states altogether... or there's the $dialog sample technique, which uses onEnter to launch a modal. https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-open-a-dialogmodal-at-a-certain-state -- which still doesn't have the modal as its own state, but uses a state to get it going.

Thinking out loud: seems like it would be great to have a built-in, easy to use way of transferring state to a modal -- such a state would be top-level, thus not needing a container template in which to live.

@nateabele
Copy link
Contributor

So, this is something that I brought up in the initial design phase, and something that @ksperling has referred to indirectly, with respect to redesigning UrlMatcher to operate in a tree structure. But what you're suggesting requires (a) a reentrant URL parser, and (b) either a 'polymorphic' state, that can attach to any other state arbitrarily, or some notion of having multiple inter-related state trees, where you have a state active in each tree.

But I have no idea what that would look like.

@laurelnaiad
Copy link

I think independent state trees would be great -- because it would also be a helpful step toward portal-like/component support.

Would it be possible and useful to support independent state trees that are so independent that they are not related to each other (except by virtue of the application code gluing them together)?

@ProLoser
Copy link
Member Author

Actually I think I created a redundant issue with #953 however it would be a far simpler approach. If a sate could define an array of children states (and multiple states could specify the same child) it would be perfect. Each instance would be isolated from each other however.

@hexadeciman
Copy link

I'm having this exact same issue and the possible solution you talked about would be perfect !

@cScarlson
Copy link

To reiterate a little...

There may be times when you have a two parent routes, say, /new & /:id but either way we want the child states of /new to be called when referencing /:id...

/new/childA
&
/:id/childA

Perhaps a "parents" array in the config will do -- OR -- a wildcard in the parent-namespace:

.state('{instance}', ...) // handles both, /new -- AND -- /:id
.state('{instance}.childA', ...)

... So that routing to any "/childA" manifests the childA state -- even if that is the only component in the route; in other terms:

.state('{REGARDLESS}.childA', ...)

I know there are other ways to get around this -- for instance -- just using a wildcard in the { url: '...' }, but in many cases this has not been the MOST-desired effect as you may want to explicate certain routes.

... However, I've been designing frameworks for a while -- but I'm still a little new to ui-router; hope I'm not speaking out of my hat.

@ProLoser ProLoser reopened this Sep 24, 2014
@christopherthielen
Copy link
Contributor

@ProLoser I believe @nateabele has some thoughts on re-using state trees, to be tackled after the v1.0.0 rewrite

@guikubivan
Copy link

FWIW, this solution worked for me since I only had two parent states that should both be able to access a child state: #1323 (comment)

@pocesar
Copy link

pocesar commented May 10, 2015

the only possible way of doing this at the moment, is to do things "off" angular, like dynamically creating a module. I created a reusable "login" module that could be used in admin, buy and user profile pages by wrapping the angular.module inside an init function like this:

export function init(namespace: string){
  angular.module('Login', [
    'ui.router',
    'User',
    'Resources'
  ]).config(['$stateProvider', function($stateProvider) {
      $stateProvider.state('login.register', { 
           views:{
                'login': {
                    templateUrl: '/templates/shared/login-register.html'
                }
           }
      });

      $stateProvider.state('login.do', { 
           views:{
                'login': {
                    templateUrl: '/templates/shared/login-do.html'
                }
           }
      });

      $stateProvider.state('login.forgot', { 
           views:{
                'login': {
                    templateUrl: '/templates/shared/login-forgot.html'
                }
           }
      });

      $stateProvider.state('login', { 
         // this act as an abstract, the states are always set to either login.forgot, login.do or login.register
         parent: namespace, // this is the trick
         templateUrl: '/templates/shared/login.html', // has a <div ui-view="login"></div> along with markup
         controller: Controllers.Login,
         controllerAs: 'login'
      });
  }]);
}

this way I can still use "login.do" and "login.forgot" without having to resource to namespaced state names, like "buy.login.do" or "admin.login.forgot". constructing the module is straight forward:

import Login = require('../shared/login');

Login.init('buy');
//Login.init('admin');
//Login.init('user');

angular.module('Base', [
   'Login'
]).config(['$stateProvider', ($stateProvider) => {
  $stateProvider.state('buy', {
     // the parent
  });
}]);

@mfoody
Copy link

mfoody commented May 21, 2015

@pocesar That's a nice solution. I think you might be able to improve it by creating the state inside a constant function. That function can then be injected into the config calls. For example,

angular.module('myApp').constant('loginDialog', function ($stateProvider, parent) {
  $stateProvider.state('login', {
    parent: parent,
    url: '/login'
  });
})
.config(['$stateProvider', 'loginDialog', function($stateProvider, loginDialog) {
  loginDialog($stateProvider, 'buy');
  loginDialog($stateProivder, 'admin');
  loginDialog($stateProvider, 'user');
}]);

@ProLoser
Copy link
Member Author

Does that work? That generates non-unique state names. You essentially have multiple states in your application called login (even though they are located at buy.login and admin.login). I would think this would cause state resolution bugs.

@mfoody
Copy link

mfoody commented May 21, 2015

Good point. Concatenating the parent into the state name should solve that.

angular.module('myApp').constant('loginDialog', function ($stateProvider, parent) {
  $stateProvider.state(parent + '.login', {
    url: '/login'
  });
})
.config(['$stateProvider', 'loginDialog', function($stateProvider, loginDialog) {
  loginDialog($stateProvider, 'buy');
  loginDialog($stateProivder, 'admin');
  loginDialog($stateProvider, 'user');
}]);

@pocesar
Copy link

pocesar commented May 21, 2015

@ProLoser the point was to be able to use ui-sref="login.do" regardless if I'm in admin, user or buy page. I can also do $state.go('login.register') when the current state is user.main.new without breaking the state or the flow, since I needed slightly different configurable versions of the login screen. I never used the Login.init in more than one bootstrap at the same time. Being able to reuse the state config is a win for me, even more pronounced when you have multiple views in one page (the login state is also used at the top "logged in" menu). Each admin, user and buy page have their own bootstrap, so that's a non-issue for me

@ProLoser
Copy link
Member Author

@pocesar I actually am not so sure that makes sense after I thought about it. If you only have 1 module running at a time, why throw it underneath x module? Why not just make it a top-level state?

@pocesar
Copy link

pocesar commented May 21, 2015

@ProLoser inheritance plus nested views unrelated to the login, but can still use login related stuff

@ProLoser
Copy link
Member Author

Honestly that is starting to sound like a directive or something. I'd be curious to see your setup. Login is sort of a weak example of this design pattern honestly.

@moander
Copy link

moander commented Jun 26, 2015

+1 (modal states)

@eddiemonge
Copy link
Contributor

@ProLoser can this be closed?

@ProLoser
Copy link
Member Author

@eddiemonge uh i don't know, up to you if you're leading the project. I am not super present. I can always reopen if the latest version isn't sufficient.

@altsanz
Copy link

altsanz commented Feb 17, 2016

Hi, I have a user case that maybe rellevant to this scenario:

We have a group of products that are editable with a specific state.
I would like to access to that state from /products/:productId and from /categoryA/:productId, so when user finishes editing that product I can just do $state.go('^') and keep user navigation experience consistent.

Actually I'm working on how this can be achieved, but @mfoody solutions seems the best. I'm just learning how ui-router works, so if there's an alternative and easier solution to this, please tell me. :)

@louiealmeda
Copy link

It would be great if we have a state that can be appended to any state, haven't thought much though on its implication and detail. But I think it would greatly help

@nhim175
Copy link

nhim175 commented Jun 14, 2016

https://christopherthielen.github.io/ui-router-extras/example/stickymodal/index.html#/people/managers

Check this ui-router-extras project, I bet what you want is called sticky state

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests