ZachB
ZachB

Reputation: 15366

Nested states in UI-Router providing both multiple views and scope inheritance

I have deeply nested routes, like

/customers
/customers/:customerId
/customers/:customerId/orders
/customers/:customerId/orders/:orderId

and, I have multiple named views: a header, a sidebar and main content.

// customers.html
<div ui-view="header">
<div ui-view="sidebar">
<div ui-view="content">

I'm trying to get a few requirements to work together:

  1. customer should be defined in all routes nested under /customers/:customerId (i.e. a scope should be created with a controller that defines customer)
  2. there should be a default set of views for all routes nested under /customers/:customerId and all three of the views should have access to customer as defined by the parent.

The first requirement I can achieve with:

$stateProvider
    .state("customers", {
        abstract: true,
        url: "/customers/:customerId",
        templateUrl: "customers.html",
        controller: "CustomersController as vm"
    })
    .state("customers.details", {
        url: "",
        views: {header: ..., sidebar: ..., content: ...}
    })
    .state("customers.orders", {
        url: "/orders",
        views: {header: ..., sidebar: ..., content: ...} // these have to be defined again
    })

Note that customers.orders needs all three views defined again. That's not terrible, but I'm wondering if there's a better way. The only way I can figure is to have another level of state, but this seems worse because the state names include something that is not relevant to the state and is just a technical artifact ("default"):

$stateProvider
    .state("customers", {
        // as above
    })
    .state("customers.default", {
        abstract: true,
        views: {header: ..., sidebar: ..., content: ...}
    })
    .state("customers.default.details", {
        url: "",
        views: {content: ...} // don't have to define all three views!
    })

Note that in my application I cannot flip the order and have something like app, app.customers and app.customers.details, where app provides the three default views and app.customers provides the controller. The real application has defaults that I want to kick in at various depths of routing.

Upvotes: 0

Views: 106

Answers (1)

Anid Monsur
Anid Monsur

Reputation: 4538

Here is a potential solution. I'm assuming that in your root html file, you have an unnamed ui-view. Let's say that this is your customer.html file:

<div ui-view="header">
<div ui-view="sidebar">
<div ui-view>
<div ui-view="content">

Your state config can be this:

$stateProvider
    .state("customers", {
        abstract: true,
        url: "/customers/:customerId",
        views: {
            '': { // Populate root ui-view with customers.html
                templateUrl: "customers.html",
                controller: "CustomersController as vm"
            },
            'header@customers': { // Populate header ui-view in customers.html
                ...
            },
            'content@customers': {
                ...
            },
            'sidebar@customers': {
                ...
            }
        }
    })
    .state("customers.details", {
        url: "",
        views: {
            '@customers': { // Populate unnamed ui-view in customers.html
                ...
            }
        }
    })
    .state("customers.orders", {
        url: "/orders",
        views: {
            '@customers': {
                    ...
            }
        }
    })

This allows you to have the header, sidebar, and content ui-views remain constant for children of the customers state. Children can override each view individually if needed. Because all these views are nested, they would have access to the CustomersController as vm.

Upvotes: 1

Related Questions