mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
1da28f4825
This commit adds the ability to provide a function that will get called immediately after the view transition is created. This will allow developers to do things like add/remove classes from the DOM when the transition animation is finished, skip the transition based on application conditions, etc. Having access to the transition unlocks just about every example outlined in https://developer.chrome.com/docs/web-platform/view-transitions. Note that the timing of the `updateCallback` execution is in the spec as being called asyncronously (https://drafts.csswg.org/css-view-transitions-1/#callbackdef-updatecallback). This means the `onViewTransitionCreated` callback is guaranteed to execute before the update callback which in turn means it is guaranteed to execute before the view transition `ready`/animation. As a result, it is safe/effictive to add classes to the document in the `onViewTransitionCreated` function in order to control animations of that transition and then remove those classes in the transition's `finished` `Promise`. The animation also doesn't start until the `Promise` returned by `updateCallback` resolves, so this would also guarantee that the animation starts asynchronously. resolves #51827 PR Close #52002
110 lines
3.1 KiB
TypeScript
110 lines
3.1 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {DOCUMENT} from '@angular/common';
|
|
import {Component, destroyPlatform, inject} from '@angular/core';
|
|
import {bootstrapApplication} from '@angular/platform-browser';
|
|
import {withBody} from '@angular/private/testing';
|
|
import {Event, NavigationEnd, provideRouter, Router, withDisabledInitialNavigation, withViewTransitions} from '@angular/router';
|
|
|
|
|
|
describe('view transitions', () => {
|
|
if (isNode) {
|
|
it('are not available in node environment', () => {});
|
|
return;
|
|
}
|
|
|
|
beforeEach(destroyPlatform);
|
|
afterEach(destroyPlatform);
|
|
|
|
@Component({
|
|
selector: 'test-app',
|
|
standalone: true,
|
|
template: ``,
|
|
})
|
|
class App {
|
|
}
|
|
beforeEach(withBody('<test-app></test-app>', () => {}));
|
|
it('should skip initial transition', async () => {
|
|
const appRef = await bootstrapApplication(App, {
|
|
providers: [provideRouter(
|
|
[{path: '**', component: App}],
|
|
withDisabledInitialNavigation(),
|
|
withViewTransitions({skipInitialTransition: true}),
|
|
)]
|
|
});
|
|
|
|
const doc = appRef.injector.get(DOCUMENT);
|
|
if (!doc.startViewTransition) {
|
|
return;
|
|
}
|
|
|
|
const viewTransitionSpy = spyOn(doc, 'startViewTransition').and.callThrough();
|
|
await appRef.injector.get(Router).navigateByUrl('/a');
|
|
expect(viewTransitionSpy).not.toHaveBeenCalled();
|
|
await appRef.injector.get(Router).navigateByUrl('/b');
|
|
expect(viewTransitionSpy).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should have the correct event order when using view transitions', async () => {
|
|
@Component({
|
|
selector: 'component-b',
|
|
template: `b`,
|
|
standalone: true,
|
|
})
|
|
class ComponentB {
|
|
}
|
|
|
|
|
|
const res = await bootstrapApplication(
|
|
App,
|
|
{providers: [provideRouter([{path: 'b', component: ComponentB}], withViewTransitions())]});
|
|
const router = res.injector.get(Router);
|
|
const eventLog = [] as Event[];
|
|
router.events.subscribe(e => {
|
|
eventLog.push(e);
|
|
});
|
|
|
|
await router.navigateByUrl('/b');
|
|
expect(eventLog[eventLog.length - 1]).toBeInstanceOf(NavigationEnd);
|
|
});
|
|
|
|
describe('onViewTransitionCreated option', () => {
|
|
it('should not create a view transition if only the fragment changes', async () => {
|
|
@Component({
|
|
selector: 'test-app',
|
|
standalone: true,
|
|
template: `{{checks}}`,
|
|
})
|
|
class App {
|
|
checks = 0;
|
|
ngDoCheck() {
|
|
this.checks++;
|
|
}
|
|
}
|
|
|
|
const transitionSpy = jasmine.createSpy();
|
|
const appRef = await bootstrapApplication(App, {
|
|
providers: [provideRouter(
|
|
[{path: '**', component: App}],
|
|
withDisabledInitialNavigation(),
|
|
withViewTransitions({onViewTransitionCreated: transitionSpy}),
|
|
)]
|
|
});
|
|
|
|
const doc = appRef.injector.get(DOCUMENT);
|
|
if (!doc.startViewTransition) {
|
|
return;
|
|
}
|
|
|
|
await appRef.injector.get(Router).navigateByUrl('/a');
|
|
expect(transitionSpy).toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|