Skip to content

Commit f886e17

Browse files
committed
Add an explainer for navigateEvent.redirect()
Part of #124. This replaces the previous idea of navigation.transition.rollback(), with something that is only scoped to the { commit: "after-transition" } case for now. A future extension to { commit: "immediate" } is gestured at. Also remove the explainer section for rollback, since we've had fewer requests for it. We can keep #124 open to discuss it though.
1 parent 85edaf1 commit f886e17

File tree

1 file changed

+22
-39
lines changed

1 file changed

+22
-39
lines changed

README.md

Lines changed: 22 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ backButtonEl.addEventListener("click", () => {
7575
- [Scrolling to fragments and scroll resetting](#scrolling-to-fragments-and-scroll-resetting)
7676
- [Scroll position restoration](#scroll-position-restoration)
7777
- [Deferred commit](#deferred-commit)
78+
- [Redirects during deferred commit](#redirects-during-deferred-commit)
7879
- [Transitional time after navigation interception](#transitional-time-after-navigation-interception)
7980
- [Example: handling failed navigations](#example-handling-failed-navigations)
8081
- [The `navigate()` and `reload()` methods](#the-navigate-and-reload-methods)
@@ -100,7 +101,6 @@ backButtonEl.addEventListener("click", () => {
100101
- [Future extensions](#future-extensions)
101102
- [More per-entry events](#more-per-entry-events)
102103
- [Performance timeline API integration](#performance-timeline-api-integration)
103-
- [Navigation transition rollbacks and redirects](#navigation-transition-rollbacks-and-redirects)
104104
- [More](#more)
105105
- [Stakeholder feedback](#stakeholder-feedback)
106106
- [Acknowledgments](#acknowledgments)
@@ -704,9 +704,29 @@ If a handler passed to `intercept()` rejects before `e.commit()` is called, then
704704

705705
Because deferred commit can be used to cancel the navigation before the URL updates, it is only available when `e.cancelable` is true. See [above](#restrictions-on-firing-canceling-and-responding) for details on when `e.cancelable` is set to false, and thus deferred commit is not available.
706706

707+
#### Redirects during deferred commit
708+
709+
If the `{ commit: "after-transition" }` option is passed to `navigateEvent.intercept()`, then an additional method is available on the `NavigateEvent`: `redirect(url)`. This updates the eventual destination of the `"push"` or `"replace"` navigation. An example usage is as follows:
710+
711+
```js
712+
navigation.addEventListener("navigate", e => {
713+
e.intercept({ async handler() {
714+
if (await isLoginGuarded(e.destination)) {
715+
e.redirect("/login");
716+
}
717+
718+
// Render e.destination, keeping in mind e.destination might be updated.
719+
} });
720+
});
721+
```
722+
723+
This is simpler than the alternative of canceling the original navigation and starting a new one to the redirect location, because it avoids exposing the intermediate state. For example, only one `navigatesuccess` or `navigateerror` event fires, and if the navigation was triggered by a call to `navigation.navigate()`, the promise only fulfills once the redirect destination is reached.
724+
725+
It's possible in the future we could contemplate allowing something similar for `{ commit: "immediate" }` navigations as well. There, we would not be able to hide the intermediate state perfectly, as code would still be able to observe the intermediate `location.href` values and such. But we could treat such post-commit redirects as special types of replace navigations, which "take over" any promises returned from `navigation.navigate()`, delay `navigatesuccess`/`navigateerror` events, etc.
726+
707727
### Transitional time after navigation interception
708728
709-
Although calling `event.intercept()` to [intercept a navigation](#navigation-monitoring-and-interception) and convert it into a single-page navigation immediately and synchronously updates `location.href`, `navigation.currentEntry`, and the URL bar, the handlers passed to `intercept()` can return promises that might not settle for a while. During this transitional time, before the promise settles and the `navigatesuccess` or `navigateerror` events fire, an additional API is available, `navigation.transition`. It has the following properties:
729+
As part of calling `event.intercept()` to [intercept a navigation](#navigation-monitoring-and-interception) and convert it into a single-page navigation, the handlers passed to `intercept()` can return promises that might not settle for a while. During this transitional time, before the promise settles and the `navigatesuccess` or `navigateerror` events fire, an additional API is available, `navigation.transition`. It has the following properties:
710730
711731
- `navigationType`: either `"reload"`, `"push"`, `"replace"`, or `"traverse"` indicating what type of navigation this is
712732
- `from`: the `NavigationHistoryEntry` that was the current one before the transition
@@ -1384,43 +1404,6 @@ const observer = new PerformanceObserver(list => {
13841404
observer.observe({ type: "same-document-navigation" });
13851405
```
13861406
1387-
### Navigation transition rollbacks and redirects
1388-
1389-
During the [transitional time after navigation interception](#transitional-time-after-navigation-interception), there are several higher-level operations that we believe would be useful for developers:
1390-
1391-
- `navigation.transition.rollback()`: a method which allows easy rollback to the `navigation.transition.from` entry.
1392-
- `navigation.transition.redirect()`: a method which allows changing the destination of the current transition to a new URL.
1393-
1394-
Note that `navigation.transition.rollback()` is not the same as `navigation.back()`: for example, if the user navigates two steps back, then `navigation.rollback()` will actually go forward two steps. Similarly, it handles rolling back replace navigations by reverting back to the previous URL and navigation API state. And it rolls back push navigations by actually removing the entry that was previously pushed, instead of leaving it there for the user to reach by pressing their forward button.
1395-
1396-
Here is an example of how you could use `navigation.transition.rollback()` to give a different sort of experience for [handling failed navigations](#example-handling-failed-navigations):
1397-
1398-
```js
1399-
navigation.addEventListener("navigateerror", async e => {
1400-
const attemptedURL = location.href;
1401-
1402-
await navigation.transition.rollback().committed;
1403-
showErrorToast(`Could not load ${attemptedURL}: ${e.message}`);
1404-
});
1405-
```
1406-
1407-
And here is an example of how you could use `navigation.transition.redirect()` to implement a "redirect" to a login page:
1408-
1409-
```js
1410-
navigation.addEventListener("navigate", e => {
1411-
e.intercept({ async handler() {
1412-
if (await isLoginGuarded(e.destination)) {
1413-
await navigation.transition.redirect("/login").finished;
1414-
return;
1415-
}
1416-
1417-
// Render e.destination as normal
1418-
} });
1419-
});
1420-
```
1421-
1422-
In more detail, such a "redirect" would consist of either doing a replacement navigation (if we're past navigation commit time), or canceling the current not-yet-committed navigation and starting a new one. In both cases, the main value add is to avoid extra intermediate events such as `navigateerror` or `navigatesuccess`; it would seem to the rest of the code as if the navigation just took longer to finish.
1423-
14241407
### More
14251408
14261409
Check out the [addition](https://github.com/WICG/navigation-api/issues?q=is%3Aissue+is%3Aopen+label%3Aaddition) label on our issue tracker to see more proposals we've thought about!

0 commit comments

Comments
 (0)