AngularUI Router: multiple states with same url pattern

Problem

Hey all I'm running into what I thought would be a common routing problem, but I'm unable to figure out a solution. Basically my page has two states, basic and advanced, and I want the URL patterns to be the same for both states but only load the template for the current state at the time (which is transitioned to from within a controller)

config(function ($stateProvider) {

  $stateProvider.state('basic', {
    url: '/:post',
    templateUrl: function (stateParams) {
      return 'post-' + stateParams.post + '-tmpl.html';
    }
  });

  $stateProvider.state('advanced', {
    url: '/:post',
    templateUrl: function (stateParams) {
      return 'post-' + stateParams.post + '-advanced-tmpl.html';
    }
  });
})

controller('myCtrl', function ($state) {
  //
  // In this case, I would expect only the template from
  // the advanced state to load, but both templates are trying
  // to load.
  $state.transitionTo('advanced', {post: 2});
}

I assume that navigating to the matched pattern loads the given state which is why when it matches, both templates attempt to load. Is there some way to accomplish the same url pattern but with different templates based only on the current state?

Problem courtesy of: ThinkingInBits

Solution

Assuming that you cannot have two states with the same URL, why don't you go along and merge the two states into one? Ui-router already allows you to have a custom function to return the template. You just need to add another, hidden custom parameter (let's call it advanced) to the state declaration:

$stateProvider.state('basicOrAdvanced', {
  url: '/:post',
  templateUrl: function (stateParams) {
    if (stateParams.advanced) {
      return 'post-' + stateParams.post + '-advanced-tmpl.html';
    } else {
      return 'post-' + stateParams.post + '-tmpl.html';
    }
  },
  params: {
    advanced: False
  }
});

And then you call it with:

$state.transitionTo('basicOrAdvanced', {post: 2, advanced: True})

For another possible solution with nested states and a common controller, see issues #217 and #1096 on ui-router's github page.

The solution presented there creates a third state whose controller does the dispatching work, whereas the two states you want to land in (basic and advanced) have empty URLs:

$stateProvider.state('basicOrAdvanced', {
  url: '/:post',
  controller: function($state, $stateParams) {
    if($stateParams.advanced) {
        $state.go('advanced', {post: $stateParams.post});
    } else {
        $state.go('basic', {post: $stateParams.post});
    }
  },
  params: {
    advanced: False
  }
}

This solution is better if your states differ in more aspects than the simple templateUrl (e.g. if they have completely separate controllers, etc.)


Also see another question on StackOverflow about a similar issue.

Solution courtesy of: DzinX

Discussion

This is very useful in AngularJS,

You Can Specify Dynamic Route for Multiple State with same url pattern

My Code is As follow that can be useful for you,

Module Intialization,

var app = angular.module("koops_app", ['ui.router','ui.bootstrap']);    //here you have to define all your required module for your project that you should inject...

Further,

app.config(function ($locationProvider, $httpProvider, $stateProvider, $urlRouterProvider, $wampProvider) {


    // When No Routing i.e Default Routing
    $urlRouterProvider.when('', '/dashboard');


    // 404
    $stateProvider.state("404", {
        url: "/404",
        templateUrl: 'template/404.html',
    });


    // When Only One Argument i.e. Only Module Name
    $stateProvider.state("default", {
        url: "/:section",
        templateUrl: 'views/View-to-be-load.html',  //can use $stateParams.section for dynamic
        reload: true,
        resolve: {
            loadController: ['$q', '$stateParams', '$state',
                        function ($q, $stateParams, $state) {
                            var deferred = $q.defer();
                            deferred.resolve();
                            return deferred.promise;
                        }
                    ]
        },
        controllerProvider: function ($stateParams) {
            return 'controllerName';
        }
    });



        // When Two Argument i.e. Module/Controller
        $stateProvider.state("default/title", {
            url: "/:section/:title",
            templateUrl: 'views/View-to-be-load.html',  //can use $stateParams.section for dynamic
            reload: true,
            resolve: {
                loadController: ['$q', '$stateParams', '$state',
                            function ($q, $stateParams, $state) {
                                var deferred = $q.defer();
                                deferred.resolve();
                                return deferred.promise;
                            }
                        ]
            },
            controllerProvider: function ($stateParams) {
                return 'controllerName';
            }
        });



        // When Three Arguments i.e. Module/Controller/id
        $stateProvider.state("default/title/id", {
            url: "/:section/:title/:id",
            templateUrl: 'views/View-to-be-load.html',  //can use $stateParams.section for dynamic
            reload: true,
            resolve: {
                loadController: ['$q', '$stateParams', '$state',
                            function ($q, $stateParams, $state) {
                                var deferred = $q.defer();
                                deferred.resolve();
                                return deferred.promise;
                            }
                        ]
            },
            controllerProvider: function ($stateParams) {
                return 'controllerName';
            }
    });



        // Otherwise
        $urlRouterProvider.otherwise("/404");

May be this is helpful for you... Enjoy...

Discussion courtesy of: Sagar Naliyapara

You need to use nested states

config(function ($stateProvider) {


  $stateProvider.state('post', {
    url: '/:post',
    templateUrl: function (stateParams) {
      return 'post-' + stateParams.post + '-advanced-tmpl.html';
    }
  });

   $stateProvider.state('post.basic', {
    url: '/:post',
    templateUrl: function (stateParams) {
      return 'post-' + stateParams.post + '-tmpl.html';
    }
  });

})

controller('myCtrl', function ($state, $timeout) {
  // go to advanced
  $state.transitionTo('post', {post: 2});
  // after 500ms switch to basic
  $timeout(function() {
      $state.transitionTo('post.basic', {post: 2});
  }, 500)
}
Discussion courtesy of: vittore

Why would you want to have two different views mapped on the same url. Did you imagined the problem when the user would want to add a bookmark on one of the states? How would you then know which one was if they have exactly the same url?

I would suggest defining two different states like:

  • /:post - for the simple view
  • /:post/advanced - for the advanced view

This can be done with defining /:post as abstract state and two different states for the simple and advanced view. For convenience simple view will act as default. If you like the idea I can provide you an example solution.

The other option would be to add url parameter for example:

  • /:post - for the simple view
  • /:post?view=advanced

I hope that this answer will help you. At least by seeing the things from different angle ;).

Discussion courtesy of: S.Klechkovski

This recipe can be found in it's original form on Stack Over Flow.