angular: Angular 5 - "Error: Cannot activate an already activated outlet" when named outlets are used in children

I’m submitting a…


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

As stated here on stackoverflow when you have a routing configuration like:

export const routes = [
{
    path: '',
    component: IndexComponent, 
    children: [
        {
            path: 'home',
            children: [
                { path: '', component: FirstComponent, outlet: 'first', data: { state: 'a' } },
                { path: '', component: SecondComponent, outlet: 'second', data: { state: 'b' } }
            ]
        },
        {
            path: 'about', 
            children: [
                { path: '', component: FirstComponent, outlet: 'first', data: { state: 'b' } },
                { path: '', component: SecondComponent, outlet: 'second', data: { state: 'a' } }
            ]
        }
    ]
}

And you are navigating from /home to /about using a link <a routerLink="['/about']">about</a> it throws the following error:

ERROR Error: Uncaught (in promise): Error: Cannot activate an already activated outlet
Error: Cannot activate an already activated outlet

Expected behavior

The expected behavior would be that it navigates to /about and the router populates the named outlets with the new components (taking internally care of deactivation/activation of outlets).

Minimal reproduction of the problem with instructions

Just a simple app, with routing a named outlets with the configuration above.

What is the motivation / use case for changing the behavior?

It’s a show stopper.

Environment


Angular version: 5.0.3


Browser:
- [x] Chrome (desktop) version XX
- [x] Chrome (Android) version XX
- [x] Chrome (iOS) version XX
- [x] Firefox version XX
- [x] Safari (desktop) version XX
- [x] Safari (iOS) version XX
- [x] IE version XX
- [x] Edge version XX
 
Others:

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 35
  • Comments: 43 (3 by maintainers)

Commits related to this issue

Most upvoted comments

…Ah, Do not add <route-outlet></route-outlet> to any Structural Directive(such as ngIF), angular cannot deactivate <route-outlet/> if it is not rendered 😆

We’ve adopted angular at my place of work, and we’re relying on this technique for routing because we don’t want to embed router-outlet information into the url. Until this works we’ll have to rely on a fork of @angular/router that has the fix in the pull request in it to keep moving forward.

I guess it’s a more involved way of +1’ing, but I just want it clear that there are others that need this fix too.

You need to change a little bit the routes. One of the 2 child routes should not be named. And of course the IndexComponent should also have two outlets one named and one without name.

export const routes = [
{
    path: '',
    component: IndexComponent, 
    children: [
        {
            path: 'home',
            children: [
                { path: '', component: FirstComponent, data: { state: 'a' } },
                { path: '', component: SecondComponent, outlet: 'second', data: { state: 'b' } }
            ]
        },
        {
            path: 'about', 
            children: [
                { path: '', component: FirstComponent, data: { state: 'b' } },
                { path: '', component: SecondComponent, outlet: 'second', data: { state: 'a' } }
            ]
        }
    ]
}

Come on, guys! It’s been more than one year since creating of the issue! >_<

In my situation, Angular misactivated a child route, path changed and Angular tried to reactivate it again. The temporary solution is to attach to that problematic router-outlet’s activate event, check the activation logic yourself and if the logic fails, call deactivate on the RouterOutlet instance.

<router-outlet #ro="outlet" (activate)="_routerOutletActivated(ro)"></router-outlet>
_router: Router;
_routerOutletActivated(ro: RouterOutlet) {
  if (!_shouldActivate(_router.url))
    ro.deactivate();
}

This guy’s got a working answer: https://stackoverflow.com/a/55742986/3000466. The workaround is deactivating manually the router on each event:

ngOnInit(): void {
    this.router.events.subscribe(e => {
      if (e instanceof ActivationStart && e.snapshot.outlet === "outletname")
        this.outlet.deactivate();
    });

This a popular ignore case (seems like) or…? Because it is NOT just named outlets any longer

And if i name them, the behavior doesn’t change

                       `{
			path: 'search/:queue',
			component: Orders,
			children: [					
				{
					path: 'createOrder',
					loadChildren:
						'./create-modal/create-modal.module#CreateModalModule',
					data: { preload: true }
				},
				{
					path: 'shipment',
					loadChildren: './search/ship/ship.module#ShipModule',
					data: { preload: true }
				},
				{ path: '**', redirectTo: 'search/all' }
			]
		},`

I have the same problem but I strongly disagree with replacing the error with a call to deactivate() because that is a bandaid fix that only hides the underlying problem.

A better fix would be changing the router to not call activateWith again if the outlet is already activated.

It would be better if someone could investigate how it’s possible that named outlets remain activated after navigating away from them.

Finally! Adding a primary (unnamed), empty router-outlet to my template completely fixed it. Thank you

In my situation, Angular misactivated a child route, path changed and Angular tried to reactivate it again. The temporary solution is to attach to that problematic router-outlet’s activate event, check the activation logic yourself and if the logic fails, call deactivate on the RouterOutlet instance.

<router-outlet #ro="outlet" (activate)="_routerOutletActivated(ro)"></router-outlet>
_router: Router;
_routerOutletActivated(ro: RouterOutlet) {
  if (!_shouldActivate(_router.url))
    ro.deactivate();
}

What method is _shouldActivate ? can you post this also please?

The suggestion above finally solved it for me. It seems like the angular router doesn’t deactivate named router outlets if it can’t first deactivate an un-named one, even if the un-named router-outlet doesn’t exist in the template. As long as it is present in the router configuration, it’s gonna work. Ex:

This doesn’t work:

export const routes: Routes = [
  { path: '', component: ShellComponent, children: [
    { path: 'child-route', children: [
      { path: '', outlet: 'named-outlet', component: ChildComponent }
    ]},
    { path: '', children: [
      { path: '', outlet: 'named-outlet', component: DefaultComponent }
    ]}
  ]}
];

But this does:

@Component({ template: '' })
export class EmptyComponent {}

export const routes: Routes = [
  { path: '', component: ShellComponent, children: [
    { path: 'child-route', children: [
      { path: '', component: EmptyComponent },
      { path: '', outlet: 'named-outlet', component: ChildComponent }
    ]},
    { path: '', children: [
      { path: '', component: EmptyComponent },
      { path: '', outlet: 'named-outlet', component: DefaultComponent }
    ]}
  ]}
];

Hi, struggled a while with this myself, but found a way to structure my routes so that the issue no longer occurred, have posted an example of my routes below, hope this can help some of you.

const routes: Routes = {
    path: ':pid',
    component: ViewComponent,
    resolve: {
      project: ProjectResolver,
    },
    children: [
      {
        path: '',
        children: [
          {
            path: '',
            component: InfoComponent,
          },
          {
            path: '',
            component: InfoComponent,
            outlet: 'tabcontent',
          },
        ]
      },
      {
        path: 'contract',
        resolve: {
          contracts: ContractsResolver,
        },
        children: [
          {
            path: '',
            component: ContractsComponent
          },
          {
            path: '',
            component: ContractsComponent,
            outlet: 'tabcontent',
          },
        ]
      },
      {
        path: 'contract/:cid',
        resolve: {
          contract: ContractResolver,
        },
        children: [
          {
            path: '',
            component: ContractComponent,
          },
          {
            path: '',
            component: ContractComponent,
            outlet: 'tabcontent'
          }
        ]
      },
    ]
  }

Also hitting this issue, but only when webpack recompiles and on screen load i get Cannot activate an already activated outlet albeit using Ionic 4/Angular.