Этапы воспроизведения:
- Перейдите к Управлению пользователями
- Нажмите на пользователя (например, «Алиса»).
- Нажмите Изменить роли.
- Измените форму, чтобы она стала грязной.
- Нажмите кнопку возврата браузера (Chrome).
- Если я нажму Отмена, навигация будет правильно заблокирована, и я останусь на странице.
- Если я повторю то же действие (назад браузер → Отмена) еще раз, история браузера станет пустой.
- После этого кнопка возврата браузера больше не будет работать (продолжительность истории равна 0).
Отмена навигации с помощью CanDeactivate не должна потреблять записи истории браузера, даже если это делается несколько раз.
Фактическое поведение:
Каждая отмененная обратная навигация браузера потребляет одну запись истории.
Guard реализация:
const canLeaveRoles: CanDeactivateFn = (component) => {
if (!component.dirty) return true;
return confirm('You have unsaved changes. Leave anyway?');
};
Примечания:
- Кнопки возврата в приложении используют Location.back()
- Проблема возникает только при использовании кнопки возврата браузера
- Это постоянно происходит в Chrome
Это ожидаемое поведение Angular Router/истории браузера?
Если да, то каков рекомендуемый способ предотвратить использование истории браузера при отмене навигации?
stackblitz demo
import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import {
Routes,
provideRouter,
RouterOutlet,
RouterLink,
ActivatedRoute,
CanDeactivateFn,
} from '@angular/router';
import { Location, NgFor } from '@angular/common';
import { FormsModule } from '@angular/forms';
/* -------------------- App Root + Sidenav -------------------- */
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, RouterLink],
template: `
Admin
User Management
`,
styles: [
`
.layout { display: flex; height: 100vh; font-family: Arial; }
.sidenav { width: 200px; padding: 16px; border-right: 1px solid #ddd; background: #f7f7f7; }
.sidenav a { display: block; padding: 8px 0; text-decoration: none; color: #333; }
.sidenav a.active { font-weight: bold; }
.content { padding: 16px; flex: 1; }
`,
],
})
class AppComponent {}
/* -------------------- Management -------------------- */
@Component({
standalone: true,
imports: [NgFor, RouterLink],
template: `
User Management
-
{{ user.name }}
})
class ManagementComponent {
users = [
{ id: '1', name: 'Alice' },
{ id: '2', name: 'Bob' },
{ id: '3', name: 'Charlie' },
];
}
/* -------------------- User -------------------- */
@Component({
standalone: true,
imports: [RouterLink],
template: `
⬅ Back
User {{ userId }}
Roles
Edit roles
`,
})
class UserComponent {
userId = this.route.snapshot.paramMap.get('userId');
constructor(private route: ActivatedRoute, private location: Location) {}
back() {
this.location.back();
}
}
/* -------------------- Roles -------------------- */
@Component({
standalone: true,
imports: [FormsModule],
template: `
Edit Roles
Admin
Editor
Save
Cancel
`,
})
class RolesComponent {
dirty = false;
roles = {
admin: false,
editor: true,
};
constructor(private location: Location) {}
markDirty() {
this.dirty = true;
}
save() {
this.dirty = false;
this.location.back();
}
cancel() {
this.location.back();
}
}
/* -------------------- Guard -------------------- */
const canLeaveRoles: CanDeactivateFn = (component) =>
!component.dirty || confirm('You have unsaved changes. Leave anyway?');
/* -------------------- Routes (with titles) -------------------- */
const routes: Routes = [
{
path: '',
redirectTo: 'management',
pathMatch: 'full',
},
{
path: 'management',
component: ManagementComponent,
title: 'User Management',
},
{
path: 'management/:userId',
component: UserComponent,
title: (route) => `User ${route.paramMap.get('userId')}`,
},
{
path: 'management/:userId/roles',
component: RolesComponent,
title: 'Edit User Roles',
canDeactivate: [canLeaveRoles],
},
];
/* -------------------- Bootstrap -------------------- */
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes)],
});
Подробнее здесь: https://stackoverflow.com/questions/798 ... ling-navig
Мобильная версия