UI Router RedirectTo after Promise from ResolveFn

Issue

I am using the Angular 2+ (targeting Angular 5) version of UI Router, but if anyone has resources for this same problem using the Angular 1 version, I would be happy to see–but I know that the redirectTo property was not an original feature of earlier versions of UI Router.

I have a parent component that’s template has “tabs” that are each named <ui-view>s.

Parent Component Template:

<ui-view name="child1" *ngIf="isCurrentView()"></ui-view>
<ui-view name="child2" *ngIf="isCurrentView()"></ui-view>
...

With this, you can see that there is nothing in the parent template except for these “tabs”. So I want the ui-view named ‘child1’ to be the default view, and I want it active all the time from the parent view, until the user navigates to a different route to turn on the other ui-view.

Assume that the isCurrentView() function works and returns a boolean for whichever view should be visible. There should be only one view visible at a time. I’m including this info just for clarity.

The states are set up in this way. You notice in the parentComponent state that there are multiple resolveFn. These all call APIs and are asynchronous, and we cannot navigate to any of the “tabs” until these are completed. This setup ensures that is the case. Both child1 and child2 components rely on having these responses.

{
    name: "parentComponent",
    url: "/parent",
    component: ParentComponent,
    resolve: [
        {
            token: "apiDetail1",
            deps: [MyAPIService],
            resolveFn: (myAPIService) => { return myAPIService.getApiDetail1(); }
        },
        {
            token: "apiDetail2",
            deps: [MyAPIService],
            resolveFn: (myAPIService) => { return myAPIService.getApiDetail2(); }
        }
    ]
},
{
    name: "parentComponent.child1",
    url: "/child1",
    views: {
        child1: { component: Child1Component }
    }
},
{
    name: "parentComponent.child2",
    url: "/child2",
    views: {
        child2: { component: Child2Component } 
    }
}

I want to reroute to the state named parentComponent.child1 as soon as all the resolve functions are complete–otherwise the necessary data won’t be available. But I don’t know how to do that.

I know that UI Router has a ‘redirectTo’ property that I can then pass in my target state, like this:

redirectTo: "parentComponent.child1"

However, redirectTo completes at the beginning of the route activation. I know that I can pass in an async service and have the redirect wait for this async service to complete–but I am not sure how to use a service that is in fact waiting for the resolve functions in the same route.

I’m already calling those APIs once, so I shouldn’t need to call them again. I just want to wait on those two that are already getting called before I redirect. But how?

Solution

I’ve found an answer, after moving around a few different things. This works, but still generates console errors that I have yet to diagnose. If anyone could provide more insight or a more in depth answer, I would appreciate it.

One of the first things I ended up doing was removing all of my multiple <ui-view> tags. I only left one, unnamed tag in the Parent Component template. With only one, I also don’t need the mysterious isCurrentView() function.

Parent Component Template:

<ui-view></ui-view>

States:

Then, in my states, I defined the redirectTo function in this way:

{
    name: "parentComponent",
    url: "/parent",
    component: ParentComponent,
    resolve: [
        {
            token: "apiDetail1",
            deps: [MyAPIService],
            resolveFn: (myAPIService) => { return myAPIService.getApiDetail1(); }
        },
        {
            token: "apiDetail2",
            deps: [MyAPIService],
            resolveFn: (myAPIService) => { return myAPIService.getApiDetail2(); }
        }
    ],
    redirectTo: (transitionService: Transition) => { // This service is of type Transition, the same object you would grab parameters from
        const apiCall1Promise = transitionService.injector().getAsync("apiDetail1"); 
        const apiCall2Promise = transitionService.injector().getAsync("apiDetail2");
        // I getAsync the same tokens I defined in my resolve functions
        return Promise.all([apiCall1Promise, apiCall2Promise]).then(()=>{
            // I don't need any of the results of the promises, since I already grab the information I need in the resolve functions, so I don't worry about getting the response here.
            return "parentComponent.child1"; // Since I need to wait for both calls, I use Promise.all instead of calling the promises directly.
        }).catch(() => {
            return "parentComponent.child1"; // This is likely bad form, but I want it to always redirect to this state, since I do not have anything else to show the user on the current parentComponent state.
        });
    }
},
{
    name: "parentComponent.child1",
    url: "/child1",
    component: Child1Component // since I'm not using named ui-view tags, I moved info out of the views property into the component property
},
{
    name: "parentComponent.child2",
    url: "/child2",
    component: Child2Component 
}        

}

By tying the tokens to the transitionService.injector() with getAsync(), then the redirect will wait for the promise to return. I wait for both my resolves (which are referenced by their tokens), and once both are done, I return the route I want it to navigate to.

However, I still receive this error when done this way:

Transition Rejection($id: 5 type: 2, message: The transition has been superseded by a different transition, detail: Transition#7( 'routeImNavigatingFrom'{} -> 'parentComponent.child1'{} ))

I’m unclear what this error means or why I am receiving it, since I have followed the redirectTo documentation. There is an ongoing discussion on the error message here.

Answered By – user2494584

Answer Checked By – David Goodson (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.