Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set default child view with Angular UI Router

Let's say I have the following 3 Angular UI Router states:

$stateProvider
    .state('adminCompanies', {
        abstract: true,
        url: "/admin/companies",
        template: '<div ui-view class="viewContainer" autoscroll="false" />'
    })
    .state('adminCompanies.list', {
        url: "",
        templateUrl: 'app/admin/companies/companies.list.html',
        controller: 'AdminCompaniesController'
    })
    .state('adminCompanies.detail', {
        url: "/:companyId",
        templateUrl: 'app/admin/companies/companies.detail.html',
        resolve: {
            company: function(Model, $stateParams) {
                return Model.get("/admin/companies", $stateParams.companyId);
            }
        },
        controller: 'AdminCompanyDetailController'
    })

If the adminCompanies is transitioned to directly, how can I tell Angular UI Router to go to the adminCompanies.list state instead?

Ideally, what I'd like to is something like:

$stateProvider.when('adminCompanies', 'adminCompanies.list');

In my code, I then want $state.go('adminCompanies') to be equivalent to $state.go('adminCompanies.list')

like image 624
egervari Avatar asked Feb 23 '14 21:02

egervari


3 Answers

As your application is currently set up, you should not need any explicit logic for this. When a sub-state has an empty url property, it becomes active at the same time its parent state becomes active. Any template for the sub-state is inserted into the ui-view directive provided by the parent state's template. The Angular UI sample application addresses this type of architecture: when the contacts state is navigated to, its template provides the navigation layout as well as a distinct ui-view for its sub-states; because the contacts.list state has a url property of "", it is automatically loaded when its parent state, contacts is navigated to. This is documented in the application's comments:

Using an empty url means that this child state will become active when its parent's url is navigated to. Urls of child states are automatically appended to the urls of their parent. So this state's url is '/contacts' (because '/contacts' + '').

Now, assuming for one reason or another that you never wanted your parent adminCompanies state to become active. In this case, you can make that state an abstract state like so:

$stateProvider
    .state('adminCompanies', {
        abstract: true,
        url: "/admin/companies",
        template: '<div ui-view class="viewContainer" autoscroll="false" />'
    })
    .state('adminCompanies.list', {
        url: "/list",
        templateUrl: 'app/admin/companies/companies.list.html',
        controller: 'AdminCompaniesController'
    })
    .state('adminCompanies.detail', {
        url: "/:companyId",
        templateUrl: 'app/admin/companies/companies.detail.html',
        resolve: {
            company: function(Model, $stateParams) {
                return Model.get("/admin/companies", $stateParams.companyId);
            }
        },
        controller: 'AdminCompanyDetailController'
    })

Note the addition of the abstract: true addition on the 3rd line and the explicit state url of /list for adminCompanies.list

This tells Angular-UI that it should treat this state as if doesn't exist for URL navigation purposes. It will still become active when you navigate to sub-states though, so it will still use the URL you provide to prefix the URL of sub-states. But if you try navigating to it from another state, it will act as if the state doesn't exist. For this reason, you will also need to add handling for unmatched URLs using $urlRouteProvider.

A basic use of $urlRouteProvider might look like this:

$urlRouterProvider.otherwise("/")

That just redirects all calls to non-existent states to the state with the URL of "/". This assumes you have one. However, because someone might be trying to navigate to the list view but they are navigating to /admin/companies directly, you should probably have a handler like this:

$urlRouterProvider.when("/admin/companies", "/admin/companies/list");

Which one you use depends on the architecture of your application, but it seems that for your current needs you shouldn't have to do anything. If your specifications change, the abstract state might come in handy.

like image 97
David Antaramian Avatar answered Nov 18 '22 19:11

David Antaramian


You can also use ui-router-default - a module where default child states are defined in the parent state by setting abstract: ".defaultChild".

This has the benefit of being able to use ui-sref="parent" and $state.go("parent"); (which was important for me when dynamically creating a navbar).

NB: Both this and David's solution only work if the parent state is abstract - i.e. the parent cannot be active without a child being active - which should be the case when we want to have a "default" child view.

(See also the ui-router team's relevant discussion here.)

like image 29
StevieP Avatar answered Nov 18 '22 20:11

StevieP


In version 1.0.0alpha0 they finally make it possible! Not child for abstract states, new $transitionsProvider, in which you could define onBefore hook. You can change the behaviour depends on state options.

For example you can change "abstract" param behaviour:

 $transitionsProvider.onBefore({
    to: state => !!state.abstract
  }, ($transition$, $state) => {
    if (angular.isString($transition.to().abstract)) {
      return $state.target($transition.to().abstract);
    }
  });

and use it like so:

  abstract: 'abstract2.foo',  //use not boolean but exact child state

code example

was taken from: ui-router: Default child for abstract state

like image 1
Stepan Suvorov Avatar answered Nov 18 '22 19:11

Stepan Suvorov