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.