14

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?

5
  • I'm having the same problem. I thought that UI router was based on states, and not the URL, but this proves the opposite Commented Sep 22, 2015 at 8:44
  • did you find a solution for this? Commented Sep 22, 2015 at 8:53
  • I've noticed that when clicking a 2nd state in this configuration ( your advanced ) implemented by a ui-sref a second time loads the correct template. ( the browser address bar updates as it should the first time ) Commented Sep 22, 2015 at 8:55
  • @SamVloeberghs Check this question stackoverflow.com/questions/30900111/… . The solution in the answer depends on the onEnter define in the first state with same pattern. So if you have suppose 10 states with the same pattern , then in the onEnter you handle all the cases , when to redirect to which state, But this require some differentiating feature of each state , In that solution I used hidden parameters but we can use cookies or localStorage as well. Also yes it works with ui-sref , check run.plnkr.co/plunks/wRqwPr/# Commented Sep 22, 2015 at 15:12
  • Looks like a repeat of this question - stackoverflow.com/questions/23344055/… Commented Sep 22, 2015 at 16:47

4 Answers 4

19
+75

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.

Sign up to request clarification or add additional context in comments.

12 Comments

Will this work with ui-sref as well? I get your point in providing a solution, but I want to avoid your approach. What if it's not only the template associated with it that might change, but also the data or other attributes on the ui-router state associated with it?
I for example have a use case where ":post" is a dynamic value in an multilanguage context. So I want a state with name 'about' and url ':page' to be able to have an url /about-english and /about-dutch. There are other similar states which have different data attributes ( and others ) bound to those specific states
About the multilanguage part, isn't it the opposite problem? I.e. one state that has two different URLs?
no it's the original problem: different states with the same url pattern :) contact(/:page) -> /contact-nl or /contact-en about(/:page) -> /about-nl or /about-en The multilanguage part is just a context, not a change to the problem.
when the contact state is defined before the about state, and you visit the about state ( using ui-sref or state.go), the contact state is loaded, because it matches the same URL pattern, before the about state
|
3

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...

4 Comments

This does not answer the question. Your URL patterns are different with each state definition. My use case would be if you were using "/:section" as your url pattern in multiple states.
I can see where your answer uses the same URL under two or more different $stateProvider.state( ) entries ?
Yes it uses same url under different state but with same patern @CodeUniquely
For the parameter value for url, do not use /foo/{bar:string} - use /foo/:bar instead. Otherwise, it won't work
-1

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)
}

5 Comments

Thanks, but when I make this change I'm getting "Uncaught object" in the console.
@ThinkingInBits updated answer, I basically copied it from you, your param name in the uri and what you were actually passing should match.
oops, that was just a typo. They do match in my code.
just adding the dot notation to the second state breaks it.
@vittore, this does not fix his problem. The advanced is not a child state of basic but more of a sibling I believe.
-1

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 ;).

2 Comments

it's about a url pattern, not a url
Thank you for your insight. We have specific requirements and logic for these route names. A user can't simply bookmark one of these urls... In fact, they will be redirected to a separate view/controller altogether if they try to navigate here directly via the location bar. I did, at the time, however, need the ability to differ controllers and views of the same URL pattern.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.