Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested parameterized routes with ui-router in AngularJS

I believe I'm on the right track with these terms, "nested parameterized routes", but haven't found what I'm looking for yet.

My objective is to create intuitive routes for my API, something like the following example:

/api/v1/project/list
/api/v1/project/1/item/list
/api/v1/project/1/item/1/edit
/api/v1/project/2/item/3/delete

It's relatively easy and clear how to setup project states, but not the item states within each project.

{
  state: 'project'
  config: {
    url:'/project'
  }
},
{
  state: 'project.list'
  config: {
    url: '/list'
  }
},
{
  state: 'project.detail'
  config: {
    url: '/:project_id'
  }
}

It's not clear to me where to go from there so that items are relative or nested within projects.


2 Answers

I'll assume you have a REST api (based on your example containing /api/v1) which you want to expose/parallel as a UI. I'll assume you want to allow the user to drill down some hierarchical data model.


Choices!

There are many ways you could organize your states, for this drill-down list/details pattern. None is the "correct" way, but some are probably better than others. I will highlight two approaches that I've used:

Sibling states for list and details

One approach is to keep the "item list" states and "item details" states as siblings. This is what you did with project.list and project.details. This approach can be seen in the UI-Router Extras Demos source code.

When taking this approach

  • you must take care to move the user from the list state to the detail state when drilling down.
  • This approach has the benefit of easy-to-understand nesting of UI-Views. The ui-view for the detail view replaces the ui-view for the list view, when drilling down, because you are navigating to a sibling state.
  • Your choice whether or not the detail for an entity also retrieves the list of sub-entities (does the detail for a project also show the items list for that product?)

States:

  • projectlist // template plugs into parent ui-view
  • projectdetail // template plugs into parent ui-view, replacing projectlist
  • projectdetail.itemslist // template plugs into parent ui-view (@projectdetail)
  • projectdetail.itemdetail // template plugs into parent ui-view (@projectdetail), replacing itemslist

Details state as a substate of List state

Another approach is to make the detail state a child of the list state. This is organized similar to your REST routes.

When taking this approach

  • States hierarchy closely resembles the REST routes being exposed
  • Drilling down is simple and intuitive
  • You must manage the visual display of list/detail.
    • When drilling down from list state to the details substate, you probably want to hide the list.
    • We use named views, and absolute naming in order to replace the parent list state's template with the template for the the detail state. This is called "view targetting".

States:

  • top // theoretical parent state
  • top.projects // lists projects. Plugs into parent ui-view (@top)
  • top.projects.project // details for project. Its named view targets the grandparent ui-view (@top), replacing the template from top.projects list state.
  • top.projects.project.items // lists items. Plugs into parent ui-view (@top.projects.project)
  • top.projects.project.items.item // details for item. Its named view targets the grandparent ui-view (@top.projects.project), replacing the template from top.projects.project.items list state.

Here's an example of using named view targeting to accomplish the second approach:

$stateProvider.state('top', {
  url: '/',
  template: '<ui-view/>',
});

$stateProvider.state('top.projects', {
  url: '/projects',
  resolve: {
    projects: function(ProjectsRoute) { 
      return ProjectsRoute.getProjects(); 
    }
  },
  controller: function($scope, projects) { $scope.projects = projects; },
  template: '<li ng-repeat="project in projects">   <ui-view/>'
});

$stateProvider.state('top.projects.project', {
  url: '/:projectid',
  resolve: {
    project: function(ProjectsRoute, $stateParams) { 
      return ProjectsRoute.getProject($stateParams.projectid);
    }
  }
  views: {
    '@top': {
      controller: function($scope, project) { $scope.project = project; },
      template: 'Project details: {{ project.name }}   <a ui-sref=".items">view items</a>  <ui-view/>'
    }
});

$stateProvider.state('top.projects.project.items', {
  url: '/projects',
  resolve: { 
    items: function(ItemsRoute, project) { 
      return ItemsRoute.getItemsForProject(project.id); 
    }
  },
  controller: function($scope, items) { $scope.items = items; },
  template: '<li ng-repeat="item in items">   <ui-view/>'
});

$stateProvider.state('top.projects.project.items.item', {
  url: '/:itemid',
  resolve: { 
    item: (ItemsRoute, $stateParams) { 
      return ItemsRoute.getItem($stateParams.itemid); 
    }
  },
  views: {
    '@top.projects.project': {
      controller: function($scope, item) { $scope.item = item; },
      template: 'Item details: {{ item.name }}'
    }
});
like image 92
Chris T Avatar answered Jan 21 '26 06:01

Chris T


I checked the Github Wiki, the Abstract States is enough.

like image 35
micfan Avatar answered Jan 21 '26 06:01

micfan