How can I persist sibling ui-views when changing state?
Asked Answered
D

1

1

I have 2 ui-view directives in my application. One contains subnavigation and the other is the actual page content. My subnavigation is a tree structure, and when you click on an end node (or a leaf) should be the only time the content view gets updated. Whenever you click non-leaf nodes in the subnavigation, I would like the content view to stay the same while the subnavigation view changes.

What is happening is whenever I switch to a state that does not define one of the views, the view gets cleared out. I want it to stay how it was previous to me changing state. Has anyone accomplished this?

enter image description here

Code:

<!--NOTE THAT THESE ARE SIBLING ELEMENTS, NOT NESTED -->
<div id="subNav" ui-view="subNav"></div>
<div id="content" ui-view="content"></div>

Here is my route setup. Note that State1 only should update the subnav view and State2 should only update the content view.

$stateProvider
  .state('State1', {
    url: '/State1',
    views: {
      "subnav": { 
         templateUrl: "views/subnav.html",
         controller: "SubNavController"
      }
    }
  })
  .state('State2', {
    url: '/State2',
    views: {
      "content": { 
         template: "<p>State 2</p>"
      }
    }
  });

Here is a plnkr of what is is currently doing: http://plnkr.co/edit/TF7x5spB8zFLQPzrgZc9?p=preview

Decca answered 12/9, 2014 at 19:30 Comment(0)
D
4

I would say, that the way to go here is really very clear:

Multiple Named Views

Nested States & Nested Views

I am using this technique, in very similar scenario: left column is a list, right (large area) is a place for a detail. There is an example

The state def would be:

$stateProvider
    .state('index', {
        url: '/',
        views: {
          '@' : {
            templateUrl: 'layout.html',
            controller: 'IndexCtrl'
          },
          'top@index' : { templateUrl: 'tpl.top.html',},
          'left@index' : { templateUrl: 'tpl.left.html',},
          'main@index' : { templateUrl: 'tpl.main.html',},
        },
      })
    .state('index.list', {
        url: '/list',
        templateUrl: 'list.html',
        controller: 'ListCtrl'
      })
    .state('index.list.detail', {
        url: '/:id',
        views: {
          'detail@index' : {
            templateUrl: 'detail.html',
            controller: 'DetailCtrl'
          },
        },
      })

So in our case, it could be like this:

  1. the parent state would have a template for both states (layout) and also will inject the navbar
  2. the child will just inject the view into main area

the Parent state:

.state('State1', {
    url: '/State1',
    views: {
      "bodyArea" { template: "body.thml"},
      "subnav@State1": { 
         templateUrl: "views/subnav.html",
         controller: "SubNavController"
      }
    }
})

So what we can see, the template for both states, the layout is defined on the State1 as a view placed in "bodyArea".

The other view (original) is injected into that template, via absolute name "subnav@State1". I.e. 2 views defined for one parent state...

The Child state:

.state('State2', {
    parent: 'State1', // a child needs parent
    url: '/State2',
    views: {
      "content": { 
         template: "<p>State 2</p>"
      }
    }
})

Here, we just say, that State1 is parent of State2. That means, that the "content" target/anchor (ui-view="content") must be part of the views defined in State1. The best place here would the "body.html"...

EXTEND: based on comments and this plunker with some issues, I created its updated version. Navigation to Main1 is broken (to be able to compare), but Main2 and Main3 are working.

  • Main2 is working because it has the similar def as index state
  • Main3 is on the other hand child of the index state

The absolute and relative naming in action should be clear from this snippet:

the index:

$stateProvider
  .state('index', {
    url: '/',
    views: {
      '@': {
        templateUrl: 'layout.html'
      },
      'mainNav@index': {
        template: '<a ui-sref="Main1">Main1</a><br />'
                + '<a ui-sref="Main2">Main2</a><br />'
                + '<a ui-sref="Main3">Main3</a>'
      },
      'subNav@index' : {
        template: '<p>This is the sub navigation</p>'
      }, 
      'content@index': {
        template: '<p>This is the content</p>'
      }
    }
  })

the Main1 with issues

  .state('Main1', {
    url: '/Main1',
    views: {
      /*'mainNav': {

      },*/
      'subNav': {
        template: '<a ui-sref="Sub1">Sub1</a><a ui-sref="Sub2">Sub2</a>'
      },
      'content': {
        template: '<p>This is the content in Main1</p>'
      }
    }
  })

working states

  .state('Main2', {
    url: '/Main2',
    views: {
      '': {
        templateUrl: 'layout.html'
      },
      'mainNav@Main2': {
        template: '<a ui-sref="Main1">Main1</a><br />'
                + '<a ui-sref="Main2">Main2</a><br />'
                + '<a ui-sref="Main3">Main3</a>'
      },
      'subNav@Main2': {
        template: '<a ui-sref="Sub1">Sub for Main2</a>'
      },
      'content@Main2': {
        template: '<p>This is the content in Main2</p>'
      }
    }
  })
  .state('Main3', {
    parent: 'index',  // PARENT does the trick
    url: '/Main3',
    views: {
      'subNav': { // PARENT contains the anchor/target - relative name is enough
        template: '<a ui-sref="Sub1">Sub for Main3</a>'
      },
      'content': {
        template: '<p>This is the content in Main3</p>'
      }
    }
  })
Dialectologist answered 13/9, 2014 at 2:27 Comment(6)
Thanks for your answer Radim. I have been trying to figure out how the syntax for the view names (like detail@index) works and I haven't been able to figure it out yet. I am putting together a Plunkr now and I'll link it here in a few.Decca
Here is what I am having a problem with. Notice whenever you click 'Main1' everything disappears. I have tried using the @ syntax in different ways but can't quitee figure out how to get this to work. plnkr.co/edit/TF7x5spB8zFLQPzrgZc9?p=previewDecca
Thanks Radim, it is clearer now and I was able to get Main1 to work after looking at your other examples. Is there any way to keep one of the views unaltered whenever you switch from one state to another? So whenever I switch from Main2 to Main3, can I keep the content page the same until I click on something in the subnav section?Decca
Yes, all that is possible, here you go: plnkr.co/edit/Dz6ag35QZxxIj40xjy3m?p=preview. The content is defined in index for both mains (3,4) only if the submenu is selected - content is changed. Enjoy ui-routerRoundabout
@Decca well your only number 2.982.298.123.302.123 to have troubles with the view model... Which was a huge motivator in creating dotjem.github.io/angular-routing... ;)Zebrawood
I tried to achieve the mentioned behavior of keeping the content persistent, and only changing it when selecting an entry in the submenu. It seems like you guys solved that problem, but the plunkr doesn't show that behavior.Urbina

© 2022 - 2024 — McMap. All rights reserved.