David Wolever
David Wolever

Reputation: 154454

Different layouts in nested Angular states with ui-router?

My application has four logical states:

pages.list      (lists pages)
pages.create    (create a new page)
pages.item      (show a single page)
pages.item.edit (edit a single page)

The states pages.list and pages.item share one base template (a traditional three column layout), but the states pages.create and pages.item.edit have a totally different template (a full-page editor).

What's the best way to express this with ui-router?

Upvotes: 3

Views: 2026

Answers (2)

b0nyb0y
b0nyb0y

Reputation: 1446

I would say that using abstract states is the way to go. In documentation, they clearly outline the benefits and some of them fit your use case pretty nicely (emphasis mine):

Some examples of how you might use an abstract state are:

  • To prepend a url to all child state urls.
  • To insert a template with its own ui-view(s) that its child states will populate.
    • Optionally assign a controller to the template. The controller must pair to a template.
    • Additionally, inherit $scope objects down to children, just understand that this happens via the view hierarchy, not the state hierarchy.
  • To provide resolved dependencies via resolve for use by child states.
  • To provide inherited custom data via data for use by child states or an event listener.
  • To run an onEnter or onExit function that may modify the application in someway.
  • Any combination of the above.

With that understanding, your app can be structured as follows:


STEP 1

Create two templates that your pages can use. Let's call them three_columns.html and one_column.html. For 3-column layout, make sure you name your ui-view accordingly:

<!-- three_columns snippet -->
...
<body>
    <div ui-view="left"></div>
    <div ui-view="middle"></div>
    <div ui-view="right"></div>
</body>

Then, one-column template:

<!-- one_column snippet -->
...
<body>
    <div ui-view></div>
</body>

STEP 2

Declare two abstract states. Let's called them oneCol and threeCols. Note that since both of them declare url: '/pages', all inheriting child states will have /pages automatically prepended to their own URLs.

$stateProvider
  .state('oneCol', {
    abstract: true,
    url: '/pages',
    templateUrl: 'layouts/one_column.html'
}).state('threeCols', {
    abstract: true,
    url: '/pages',
    templateUrl: 'layouts/three_columns.html'
});

STEP 3

Declare your states which extend abstract states above. Suppose we call them list, create, view, and edit. Here, threeCols.list and threeCols.view will have to declare 3 templateUrls, one for each named ui-view. On the other hand, oneCol.create and oneCol.edit will declare their unnamed templateUrl normally. The convention I usually use is to create a folder specific to each state, since it's good for organizing sub-view templates:

$stateProvider
  .state('threeCols.list', {
    url: '/list',
    controller: 'ListCtrl',
    views: {
        'left': {
            templateUrl: 'modules/list/leftbar.html'
        },
        'middle': {
            templateUrl: 'modules/list/content.html'
        },
        'right': {
            templateUrl: 'modules/list/rightbar.html'
        }
    }
}).state('oneCol.create', {
    url: '/create',
    controller: 'CreateCtrl',
    templateUrl: 'modules/create/content.html'
}).state('threeCols.view', {
    url: '/view',
    controller: 'ViewCtrl',
    views: {
        'left': {
            templateUrl: 'modules/view/leftbar.html'
        },
        'middle': {
            templateUrl: 'modules/view/content.html'
        },
        'right': {
            templateUrl: 'modules/view/rightbar.html'
        }
    }
}).state('oneCol.edit', {
    url: '/edit',
    controller: 'EditCtrl',
    templateUrl: 'modules/edit/content.html'
});

With this, now your URLs will look like what you expected: /pages/list, /pages/create, /pages/view, /pages/edit. You can also create and reuse templates as much as you like.

Upvotes: 5

Matt Way
Matt Way

Reputation: 33141

I usually define my routes based on view, not really based on the model. For example, given your simple explanation above, I would use the following:

$stateProvider
    .state('pages.list', { url: '/list', controller: 'ListCtrl', templateUrl: 'threecol.html' })
    .state('pages.create', { url: '/create', controller: 'CreateCtrl', templateUrl: 'create.html' })
    .state('pages.item', { url: '/:itemId', controller: 'ItemCtrl', templateUrl: 'threecol.html' })
    .state('pages.edit', { url: '/:itemId/edit', controller: 'EditCtrl', templateUrl: 'edit.html' });

Essentially then you would use threecol.html to define your common three column layout. create.html and edit.html would be your full pages.

If you wanted a more abstract approach defined around the layout types, you could also create abstract parent states to define the layout itself. For example you could have:

threecol.list
threecol.item
fullpage.create
fullpage.edit

If you made threecol and fullpage abstract states, you could define the commonly used layout markup, and just insert the appropriate <div ui-view=""></div> where desired. That way you could use different content template files for list and item for example.

Upvotes: 0

Related Questions