16

edit: Based on the answer by @actor2019 I want to update my question to better explain the problem:

Using Angular UI-Router(v0.0.2), I've setup the app to properly navigate between main "pages"/state, while inheriting the base state.

Index.html:

<div ui-view></div>

base.html:

<!-- Header -->
<div>
    <!-- Header markup -->

    <!-- Search View -->
    <div ui-view="search"></div>
</div>

<!-- Page Content view -->
<div ui-view></div>

The issue is here in the app.js file. When I add the views parameter to the base state, everything stops working(100% blank page). Without that parameter, the page renders correctly, but I have no search view.

app.js:

$urlRouterProvider.otherwise('/');

//
// Now set up the states
$stateProvider
  .state('base', {
    abstract: true,
    templateUrl: 'views/base.html',
    views: {
      "search": {
        templateUrl: "views/search.html"
      }
    }
  })
  .state('base.home', {
    url: "/",
    templateUrl: "views/home.html"
  })
  .state('base.page2', {
    url: "/page2",
    templateUrl: "views/page2.html"
  });

How do I add views to this parent 'base' state?

enter image description here

UPDATE: The problem with @actor2019's answer here is that the search view gets reinitialized when the state changes. I'd like the views off the base level to persist through state changes.

Community
  • 1
  • 1
JT703
  • 1,311
  • 4
  • 17
  • 41

4 Answers4

24

The first obvious mistake:

You can't specify controller and template on the state while your using views. They are mutually exclusive...

This is because when there is no "views" but a controller and template on the state, UI-Router automatically creates the "views" property and pulls those properties to an "empty" view...

.state('base', {
  abstract: true,
  templateUrl: 'views/base.html', //Can't do this
  views: { // when this is there.
    "search": {
      templateUrl: "views/search.html"
    }
  }
})

Instead do:

.state('base', {
  abstract: true,
    views: {
      "": {
        templateUrl: 'views/base.html'
      },
      "search": {
        templateUrl: "views/search.html"
      }
    }
  })

Second problem:

How views targeting works with nested views etc. is not very logical, it may work well if you restrict your self to one view in one view all the way down, but ones you start working with multiple named views it all gets confusing... Add unnamed views on top and many people gets lost...

The way views work in UI-Router is the worst part of UI-Router...

Given you example I am not even entirely sure of the way to target the search view from your abstract parent state... Might be:

.state('base', {
  abstract: true,
    views: {
      "": {
        templateUrl: 'views/base.html'
      },
      "search@base": {
        templateUrl: "views/search.html"
      }
    }
  })

If it can even be made to work... Alternatively you can move the search view out of base.html, but I guess you added it in there for a reason.

The whole view concept is the biggest reason why I ended up writing https://github.com/dotJEM/angular-routing instead.

Jens
  • 3,353
  • 1
  • 23
  • 27
4

The Child state should be home.search instead of header.search. In your case, you may want to write some abstract state to hold the layout,

base.html

<div class="row-fluid">
    <div class="header">
        <div class="span3" ui-view="logo"></div>
        <div  class="span9" ui-view="menu"></div>
    </div>
</div>

<div class="row-fluid">
    <div class="content">
        <div class="span2" ui-view="sidebar"></div>
        <div  class="span10" ui-view="entry"></div>
    </div>
</div>

in app.js

$stateProvider
    .state('base',{
        abstract:true,
        url:'/',
        templateUrl: viewBase+'base.html'
    })
    .state('base.main',{
        url:'',
        views:{
            "logo":{
                templateUrl:viewBase+'main/logo.html'
            },
            "menu":{
                templateUrl:viewBase+'main/menu.html'
            },
            "sidebar":{
                templateUrl:viewBase+'main/sidebar.html'
            },
            "entry":{
                templateUrl: viewBase+'main/entry.html'
            }
        }})
maow
  • 1,387
  • 11
  • 20
  • 1
    so, anything in the "views" cannot be a parent? It has to be the tip of the hierarchy branch? last child? – JT703 Aug 28 '13 at 13:30
  • 1
    you can define `ui-view` in the views, let's say in `main/logo.html` but you have to use `base.main` as parent and then derive ot from 'base.main' like `.state('base.main.NewChildState') {views:{'NewSubView@base.main':...}}` – maow Aug 28 '13 at 14:06
3

According to the ui-router documentation, when the application is in a particular state—when a state is "active"—all of its ancestor states are implicitly active as well. So, for example, when the "contacts.list" state is active, the "contacts" state is implicitly active as well, because it's the parent state to "contacts.list". Child states will load their templates into their parent's ui-view. I'd reccomend looking over the section of their documentation entitled Nested States & Views to gain a fuller understanding of how to do this.

In the code you have provided us here, the parent state of the search template is home, while

.state('header.search', {
    templateUrl: "views/search.html",
    controller: "SearchCtrl"
})

implies that the parent state of the search template should be header in order for the view to get loaded correctly. So, I believe the following changes to your app.js will fix your issue.

app.js

$stateProvider
  .state('home', {
    url: "/",
    views: {
      '': {
        templateUrl: "views/mainContent.html",
        controller: "MainCtrl"
      },
      'header': {
        templateUrl: "views/header.html"
      },
      'footer': {
        templateUrl: "views/footer.html"
      },
    }
  })
  .state('home.search', {
    views: {
      'search': {
        templateUrl: "views/search.html",
        controller: "SearchCtrl"
      }
  })
  .state('anotherPage', {
    url: "/anotherPage",
    templateUrl: "views/anotherPage.html"
  });
drdalton
  • 271
  • 3
  • 8
  • (+1) One question Dr. Dalton, is `home.search` automatically invoked because it has no `url` and maps its `views` to those within whichever state is *active*? And if we had more than one '`ui-view="search"`' throughout our app, we could restrict `home.search` to only views in `home` using the normal `view@state` syntax (assuming it was acually `home.other.state.search` and one or more of the states between had a "`search`" view)...? – Cody Apr 15 '16 at 16:54
2

This works for me.

$stateProvider
    .state('base', {
      abstract: true,
      url:'/',
      templateUrl: 'views/base.html'
    })
    .state('base.home', {
      url: "",
      views: {
      "search@base": {
        templateUrl: "views/searchOfHome.html"
      }
      //content@base, contentOfHome.html
    }
    })
    .state('base.page2', {
      url: "page2",
      views: {
      "search@base": {
        templateUrl: "views/searchOfPage2.html"
      }
      //content@base, contentOfPage2.html
    });

If 'base' is the root state, you don't need the '@base'

maow
  • 1,387
  • 11
  • 20
  • 1
    but in this case the views (search) aren't inherited. it seems like there should be a more proper way. – JT703 Aug 29 '13 at 12:15
  • inherit from a view? I don't get your point, I think the idea is to inherit from a state and overwrite the view in the parent state. – maow Aug 29 '13 at 14:43
  • I want to inherit from a state with views. I want the views from the inherited state to be persistent across all states. I shouldn't have to have a different search view for each state. It's all the same view. I want it to be inherited with the base state. – JT703 Aug 29 '13 at 15:14
  • The main problem here is that when you change states, the "search" view is re-initialized. If that view was defined at the parent level, I would assume that it would persist through state changes. Hope that makes sense. – JT703 Aug 29 '13 at 15:49
  • 1
    If you want "it persist through all state changes", why not just hard-code it as plain html in the base html. Btw, if you don't overwrite the view in the parent state, it may not `re-initialized`. Not sure. you may want to add some code in the `onEnter` of the view to check it out. – maow Aug 30 '13 at 06:50
  • 1
    That is an option, it's just not as clean as I would like. – JT703 Aug 30 '13 at 13:50