Skip to content
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

feat($state): "independent" (child) state #1282

Closed

Conversation

gugiserman
Copy link

If state parent's defined, "independent" property can be set and won't render nor keep it's parent's view(s)/templates (as long it's own ui-view(s) is outside of it's parent's, of course). But inherits everything else, as usual.

@gugiserman gugiserman force-pushed the feature/independentChild branch 2 times, most recently from e3f1736 to e6ad562 Compare August 21, 2014 17:06
"independent" states must have defined parents (besides abstract "root")

Prevent "independent" child state from rendering parent's template, but inherits everything as usual

Forces state reload (render parent's views) when coming back from independent child state
@gugiserman gugiserman changed the title Feature/independent child feat($state): "independent" (child) state Aug 25, 2014
@nateabele
Copy link
Contributor

I don't totally understand the use case / intent of this. Can you put up a plunkr with a minimal example? (I realize you updated the sample, but that doesn't really make the need clear).

@gugiserman
Copy link
Author

@nateabele Hello there,

I put up a gh-page for you: http://gugiserman.github.io/ui-router-independent-demo/
repo: https://github.com/gugiserman/ui-router-independent-demo

Just the obvious, really. Since I don't have any more time today, perhaps even for the rest of this week, to work on this, I couldn't reproduce the exact (far more complex) problem we had here in the company I'm currently working at.

The point is to prevent repeating URLs and Resolves in states that should follow this kind of hierarchy (maybe for REST purposes, whatever), but simply are not designed to share the same screen/layout, and manually hiding/showing based on listeners to $stateChanges just feels dirty (to me).

Feels not convincing enough, man :( But hey, thanks for your attention anyways!

@nateabele
Copy link
Contributor

@gugiserman Okay, your follow-up description actually helps a lot, thanks for that. I'll try to review the code and your example further in the next day or two.

@gugiserman
Copy link
Author

(Should be a) Parent state:

$stateProvider
.state 'orders',
  url: '/orders',
  controller: 'OrdersController'
  resolve:
    orders: (OrderService) -> OrderService.getOrders()

_(Should be a) "Child state" - Before: (If you set it's parent, you have to deal with both views being rendered in the same screen or manually hiding each other based on $state.current and change events)_

.state 'order',
  url: '/orders/:id'
  controller: 'OrderController'
  resolve:
    order: (OrderService, $stateParams) ->
      OrderService.getOrder($stateParams.id)

_Actually child state - After:_

.state 'order',
  parent: 'orders'
  independent: true
  url: '/:id',
  controller: 'OrderController'
  resolve:
    detail: ($stateParams, orders) ->
      return order for order in orders when order.id is $stateParams.id

I mean, even for 'DRY' reasons, besides the fact that these state's views were not supposed to share the screen or something like that.

@nateabele Well, sorry for bothering again, thanks man!

PS I do believe there's a better way of handling this, so we can discard the need to name the view of the "independent child" state.

@christopherthielen
Copy link
Contributor

Interesting, I came across what I think is this same scenario at work and wrote a directive.

I put this directive on an element I want hidden (or shown) when a nested state is activated. I wrote this because I am sharing templates between states, so something like ng-hide="$state.includes()" is impossible. This directive doesn't really show/hide, rather it toggles a class defined in the attribute value.

@gugiserman Is this the same use case as you are describing?

<div class="panel  callout" ui-nested-state-active="ng-hide">
  <h1> This content hidden using ng-hide class when a child state is active</h1>
</div>

var directiveFactory = function (options) {
  return function ($state, $interpolate) {
    return {
      restrict: "A",
      link: function (scope, elem, attrs) {
        var inheritedData = elem.inheritedData("$uiView");
        var clazz = $interpolate(attrs[options.directiveName], false)(scope);

        function update(evt, toState, toParams) {
          var thisStateActive = inheritedData.state.self.name === toState.name;
          var activateWhenNested = options.activateWhenNested;
          if ((activateWhenNested && !thisStateActive) || (!activateWhenNested && thisStateActive)) {
            elem.addClass(clazz);
          } else {
            elem.removeClass(clazz);
          }
        }

        scope.$on("$stateChangeSuccess", update);
        update(null, $state.current, null);
      }
    };
  };
};

var nestedStateDirective = directiveFactory({ directiveName: 'uiNestedStateActive', activateWhenNested: true});
angular.module('patientportal').directive('uiNestedStateActive', nestedStateDirective);

var thisStateDirective = directiveFactory({ directiveName: 'uiThisStateActive', activateWhenNested: false});
angular.module('patientportal').directive('uiThisStateActive', thisStateDirective);

@gugiserman
Copy link
Author

@christopherthielen Yeah! Just the behavior I was looking for, man. Nice.

I'm currently switching (with ng-show) the elements in the worst possible way, I guess. Which is something like ng-show="$state.current.name == 'exact_name'" and named views for the specific case.

But it became a need for other projects and other states/pages really fast, and it would turn into a monster :p

Your directive is a pretty sweet solution, tho. I will probably do something really similar for now. Thanks!

I hated to trigger a state reload (in my fork) when returning from it's child but couldn't find a way that wouldn't be aggressive.

Another thought I had is that if trying to access directly the child state, there would be no need to append/render it's parent's elements into the DOM at first, nor watch for it's bindings in the template, but I do have to inherit data.

@ProLoser
Copy link
Member

I don't think this is a good solution. The entire level of complication is just so that you can re-occupy the same <ui-view> that a parent occupies, except there is already a way to do this:

$stateProvider
.state 'orders',
  url: '/orders',
  controller: 'OrdersController'
  resolve:
    orders: (OrderService) -> OrderService.getOrders()
.state 'orders.order',
  url: '/:id',
  views:
    '@^' # or the equivalent like '@orders' or '@dashboard'
      controller: 'OrderController'
      templateUrl: 'order.html'
  resolve:
    order: (orders, $stateParams) -> orders[$stateParams.id]

Alternatively, you can simply create a list child and a view child and use the orders state to purely resolve data (which is even easier with #1235):

$stateProvider
.state 'orders',
  url: '/orders',
  abstract: true 
  template: '<ui-view>'
  resolve:
    orders: (OrderService) -> OrderService.getOrders()
.state 'orders.list',
  controller: 'OrdersController'
.state 'orders.view',
  url: '/:id',
  controller: 'OrderController'
  templateUrl: 'order.html'
  resolve:
    order: (orders, $stateParams) -> orders[$stateParams.id]

@ProLoser
Copy link
Member

@gugiserman @christopherthielen I feel you guys are not leveraging view overriding properly by resorting to display toggling. There is massive power and versatility to view overriding that makes it easier to refactor later.

@christopherthielen
Copy link
Contributor

@ProLoser you're absolutely right. Now I feel stupid. :)

@ProLoser
Copy link
Member

@christopherthielen cool now that I have second-hand confirmation I'm going to close this. I would instead like to see abstract defaults implemented which I think will solve far more use-cases in the long run!

@ProLoser ProLoser closed this Sep 20, 2014
@ProLoser
Copy link
Member

@christopherthielen can you confirm if it's possible to use relative paths for view definitions? Like is '@^': a valid view key? And if not, that might be the one other feature I'd like to see implemented (but this again is very non-crucial as simply knowing the name of the 'orders' parent state would make this go away).

@christopherthielen
Copy link
Contributor

@ProLoser you can't do that right now, afaik. Also, I'll have to check how I used those directives. I might be toggling only one or two ui elements, I don't recall offhand.

@ProLoser
Copy link
Member

@christopherthielen honestly there are so many far more important things to do I'm just going to forget about it.

@gugiserman
Copy link
Author

@ProLoser thanks, man!

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

Successfully merging this pull request may close these issues.

None yet

4 participants