In Angular's router, what does withViewTransitions() do during a navigation, and what happens in browsers without the View Transitions API?
answer
- a provideRouter feature
- wraps route activation
- guards and resolvers run first
- progressive enhancement
- still developer preview
basics
~20 swithViewTransitions() makes the router activate and deactivate routes inside document.startViewTransition, so the browser animates from a snapshot of the old page to the new one. Without View Transitions support, the router skips it and navigates normally.
solid answer
~40 s`provideRouter(routes, withViewTransitions())`, or `enableViewTransitions: true` in `RouterModule.forRoot`, turns on the integration. On each navigation the router first does its usual preparation: route matching, lazy loading, guards and resolvers. When the new routes are ready to activate, it calls `document.startViewTransition`; the browser captures the old page, the router swaps the routed components, and the transition's update finishes after Angular's next render. By default the browser cross-fades the two states, and CSS on the `::view-transition-*` pseudo-elements customises it. If the browser lacks the API, the router simply updates the DOM without animation, so it is a progressive enhancement. Options include `skipInitialTransition` for the first navigation and `onViewTransitionCreated` for per-navigation control. The feature arrived in v17 and is still marked developer preview in v22.2.
code
ts · 8 linesimport {bootstrapApplication} from '@angular/platform-browser';
import {provideRouter, withViewTransitions} from '@angular/router';
import {App} from './app/app';
import {routes} from './app/app.routes';
bootstrapApplication(App, {
providers: [provideRouter(routes, withViewTransitions({skipInitialTransition: true}))],
});go deeper
Know how to enable it with provideRouter(routes, withViewTransitions()) and that unsupported browsers just navigate without animation.
Explain where the transition sits in the navigation: after guards, resolvers and lazy loading, wrapping activation, ending after the next render.
Decide how to style and scope transitions, handle the initial load and unsupported browsers, and weigh developer-preview status for production.
Set the team's motion strategy for navigation: platform view transitions over a runtime engine, with fallback and accessibility expectations defined up front.
## The browser feature underneath The **View Transitions API** lets a page animate between two DOM states. A script calls `document.startViewTransition(callback)`; the browser captures the current page, runs the callback that changes the DOM, captures the new state and animates between the two with CSS pseudo-elements such as `::view-transition-old(root)` and `::view-transition-new(root)`. By default that animation is a cross-fade. Angular does not reimplement any of this. Its router simply calls the API at the right moment in a navigation. ## Turning it on | Bootstrap style | How to enable | |---|---| | Standalone | `provideRouter(routes, withViewTransitions())` | | NgModule | `RouterModule.forRoot(routes, {enableViewTransitions: true})` | `withViewTransitions()` is a router **feature function** from `@angular/router`, like `withComponentInputBinding()`. It accepts an optional options object with two fields: - **`skipInitialTransition`**: skip the very first `startViewTransition` call, so the application's initial load does not animate; - **`onViewTransitionCreated`**: a callback that runs, in an injection context, every time a transition is created. ## Where it sits in a navigation 1. The router matches routes, loads lazy routes, runs guards and resolvers and any router resources. **None of this is inside the transition**, so the old page stays live while data loads. 2. When the new route tree is ready to activate, the router calls `document.startViewTransition`. 3. Inside the transition's update callback the router is unblocked and **activates the new routes and deactivates the old ones**. 4. The update is considered done only after Angular's **next render** has completed (plus one macrotask so follow-up renders queued in microtasks also land), so the browser captures a fully rendered new page. 5. The browser plays the animation between the two captures. Angular deliberately provides **no way to delay** the transition until more content loads: the page is frozen while the transition's update is pending, so the router keeps that window short. ## Progressive enhancement If `document.startViewTransition` does not exist, the router skips the call and navigates as usual; nothing breaks and there is simply no animation. Since 22.1.5 the router also skips the call when the **browser already provides its own visual transition** for a history navigation, such as a back-swipe gesture, so the two do not stack. ## Customising the look The default cross-fade can be changed entirely in CSS: - give elements a `view-transition-name` to animate them separately from the rest of the page; - style `::view-transition-old(name)` and `::view-transition-new(name)` with your own keyframes. These pseudo-element rules must go in the **global stylesheet**: Angular's emulated view encapsulation rewrites component selectors with attribute scoping, so a rule for `::view-transition-new(photo)` inside a component's styles will not match the pseudo-elements, which belong to the document. ## Status and caveats - The router integration has existed **since v17** and is still labelled **developer preview** in Angular 22.2, so its API may still change. - Browser support for same-document view transitions is uneven; test the fallback path, not only the animated one. - Only one view transition can run at a time; starting a new navigation during one ends the previous transition. ## Checking that it works 1. **Unsupported browser.** Navigate in a browser without `document.startViewTransition` and confirm routes still change instantly with no errors. 2. **Initial load.** Decide whether the first render should animate; if not, set `skipInitialTransition: true`. 3. **Back and forward.** Use history navigation, including any gesture the platform offers, and check that motion is not doubled. 4. **Reduced motion.** Confirm that the global CSS removes or softens large movements for users who ask for less motion. 5. **Slow routes.** Throttle the network and confirm the old page stays usable while resolvers run, and that the transition starts only when the new route is ready. ## When to reach for it For page-level motion (cross-fades between routes, a thumbnail growing into a detail image) view transitions are now the default choice in Angular: no animation engine to ship, motion defined in CSS, and graceful degradation. The deprecated `@angular/animations` approach, a trigger around `<router-outlet>`, remains only in older codebases.
- Does a slow resolver on the photo-detail route freeze the gallery during the transition?No. Resolvers, guards and lazy loading run before the router calls `startViewTransition`, so the gallery stays interactive while data loads. The transition starts only when the detail route is ready to activate, and it lasts roughly until Angular's next render completes.
- Why does a ::view-transition-old(root) rule in a component's styles have no effect?With the default emulated encapsulation, Angular scopes component selectors to that component's elements. The view-transition pseudo-elements belong to the document, not to any component element, so the scoped rule never matches. Put these rules in the global stylesheet.
Like a theatre scene change behind a scrim: the audience keeps seeing a frozen picture of the old set while the crew swaps the scenery, then the lighting fades the picture into the new set. The crew (the router) only starts once the new scenery (data, guards, lazy code) is ready backstage.
saying these in an interview costs you the question
- withViewTransitions() needs @angular/animations and an animations provider.
- Guards and resolvers run inside the view transition.
- Browsers without the API throw an error on navigation.
- The router lets you delay the transition until data arrives.
- View-transition pseudo-element styles work fine inside component styles.