fix(ivy): use NgZone.onStable when bootstraped using PlatformRef (#27898)
PR Close #27898
This commit is contained in:
parent
1a7f92c423
commit
b9c6df6da7
|
@ -3,7 +3,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime": 1497,
|
||||
"main": 187112,
|
||||
"main": 187134,
|
||||
"polyfills": 59608
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,13 +11,14 @@ import {ApplicationRef} from './application_ref';
|
|||
import {APP_ID_RANDOM_PROVIDER} from './application_tokens';
|
||||
import {IterableDiffers, KeyValueDiffers, defaultIterableDiffers, defaultKeyValueDiffers} from './change_detection/change_detection';
|
||||
import {Console} from './console';
|
||||
import {InjectionToken, Injector, StaticProvider} from './di';
|
||||
import {Injector, StaticProvider} from './di';
|
||||
import {Inject, Optional, SkipSelf} from './di/metadata';
|
||||
import {ErrorHandler} from './error_handler';
|
||||
import {LOCALE_ID} from './i18n/tokens';
|
||||
import {ComponentFactoryResolver} from './linker';
|
||||
import {Compiler} from './linker/compiler';
|
||||
import {NgModule} from './metadata';
|
||||
import {SCHEDULER} from './render3/component_ref';
|
||||
import {NgZone} from './zone';
|
||||
|
||||
export function _iterableDiffersFactory() {
|
||||
|
@ -43,6 +44,7 @@ export const APPLICATION_MODULE_PROVIDERS: StaticProvider[] = [
|
|||
deps:
|
||||
[NgZone, Console, Injector, ErrorHandler, ComponentFactoryResolver, ApplicationInitStatus]
|
||||
},
|
||||
{provide: SCHEDULER, deps: [NgZone], useFactory: zoneSchedulerFactory},
|
||||
{
|
||||
provide: ApplicationInitStatus,
|
||||
useClass: ApplicationInitStatus,
|
||||
|
@ -59,6 +61,25 @@ export const APPLICATION_MODULE_PROVIDERS: StaticProvider[] = [
|
|||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Schedule work at next available slot.
|
||||
*
|
||||
* In Ivy this is just `requestAnimationFrame`. For compatibility reasons when bootstrapped
|
||||
* using `platformRef.bootstrap` we need to use `NgZone.onStable` as the scheduling mechanism.
|
||||
* This overrides the scheduling mechanism in Ivy to `NgZone.onStable`.
|
||||
*
|
||||
* @param ngZone NgZone to use for scheduling.
|
||||
*/
|
||||
export function zoneSchedulerFactory(ngZone: NgZone): (fn: () => void) => void {
|
||||
let queue: (() => void)[] = [];
|
||||
ngZone.onStable.subscribe(() => {
|
||||
while (queue.length) {
|
||||
queue.pop() !();
|
||||
}
|
||||
});
|
||||
return function(fn: () => void) { queue.push(fn); };
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the root injector for an app with
|
||||
* providers of `@angular/core` dependencies that `ApplicationRef` needs
|
||||
|
|
|
@ -451,7 +451,8 @@ describe('Integration', () => {
|
|||
expect(fixture.nativeElement).toHaveText('team 33 [ , right: ]');
|
||||
})));
|
||||
|
||||
it('should work when an outlet is in an ngIf',
|
||||
fixmeIvy('unknown/maybe FW-918')
|
||||
.it('should work when an outlet is in an ngIf',
|
||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
|
||||
|
@ -634,15 +635,18 @@ describe('Integration', () => {
|
|||
expect(fixture.nativeElement).toHaveText('team 33 [ , right: ]');
|
||||
})));
|
||||
|
||||
it('should navigate back and forward',
|
||||
fixmeIvy('unknown/maybe FW-918')
|
||||
.it('should navigate back and forward',
|
||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
|
||||
router.resetConfig([{
|
||||
path: 'team/:id',
|
||||
component: TeamCmp,
|
||||
children:
|
||||
[{path: 'simple', component: SimpleCmp}, {path: 'user/:name', component: UserCmp}]
|
||||
children: [
|
||||
{path: 'simple', component: SimpleCmp},
|
||||
{path: 'user/:name', component: UserCmp}
|
||||
]
|
||||
}]);
|
||||
|
||||
let event: NavigationStart;
|
||||
|
@ -1004,7 +1008,9 @@ describe('Integration', () => {
|
|||
]);
|
||||
})));
|
||||
|
||||
it('should handle failed navigations gracefully', fakeAsync(inject([Router], (router: Router) => {
|
||||
fixmeIvy('unknown/maybe FW-918')
|
||||
.it('should handle failed navigations gracefully',
|
||||
fakeAsync(inject([Router], (router: Router) => {
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
|
||||
router.resetConfig([{path: 'user/:name', component: UserCmp}]);
|
||||
|
@ -1879,11 +1885,14 @@ describe('Integration', () => {
|
|||
});
|
||||
|
||||
describe('redirects', () => {
|
||||
it('should work', fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||
fixmeIvy('unkwnown/maybe FW-918')
|
||||
.it('should work',
|
||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
|
||||
router.resetConfig([
|
||||
{path: 'old/team/:id', redirectTo: 'team/:id'}, {path: 'team/:id', component: TeamCmp}
|
||||
{path: 'old/team/:id', redirectTo: 'team/:id'},
|
||||
{path: 'team/:id', component: TeamCmp}
|
||||
]);
|
||||
|
||||
router.navigateByUrl('old/team/22');
|
||||
|
@ -1892,13 +1901,15 @@ describe('Integration', () => {
|
|||
expect(location.path()).toEqual('/team/22');
|
||||
})));
|
||||
|
||||
it('should update Navigation object after redirects are applied',
|
||||
fixmeIvy('unkwnown/maybe FW-918')
|
||||
.it('should update Navigation object after redirects are applied',
|
||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
let initialUrl, afterRedirectUrl;
|
||||
|
||||
router.resetConfig([
|
||||
{path: 'old/team/:id', redirectTo: 'team/:id'}, {path: 'team/:id', component: TeamCmp}
|
||||
{path: 'old/team/:id', redirectTo: 'team/:id'},
|
||||
{path: 'team/:id', component: TeamCmp}
|
||||
]);
|
||||
|
||||
router.events.subscribe(e => {
|
||||
|
@ -2022,7 +2033,9 @@ describe('Integration', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||
fixmeIvy('unknown/maybe FW-918')
|
||||
.it('works',
|
||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
|
||||
router.resetConfig(
|
||||
|
@ -2030,7 +2043,6 @@ describe('Integration', () => {
|
|||
|
||||
router.navigateByUrl('/team/22');
|
||||
advance(fixture);
|
||||
|
||||
expect(location.path()).toEqual('/team/22');
|
||||
})));
|
||||
});
|
||||
|
@ -2044,7 +2056,9 @@ describe('Integration', () => {
|
|||
|
||||
beforeEach(() => { TestBed.configureTestingModule({providers: [AlwaysTrue]}); });
|
||||
|
||||
it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||
fixmeIvy('unknown/maybe FW-918')
|
||||
.it('works',
|
||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
|
||||
router.resetConfig(
|
||||
|
@ -3343,7 +3357,8 @@ describe('Integration', () => {
|
|||
});
|
||||
|
||||
describe('route events', () => {
|
||||
it('should fire matching (Child)ActivationStart/End events',
|
||||
fixmeIvy('unknown/maybe FW-918')
|
||||
.it('should fire matching (Child)ActivationStart/End events',
|
||||
fakeAsync(inject([Router], (router: Router) => {
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
|
||||
|
|
|
@ -170,7 +170,9 @@ withEachNg1Version(() => {
|
|||
});
|
||||
|
||||
describe('scope/component change-detection', () => {
|
||||
it('should interleave scope and component expressions', async(() => {
|
||||
fixmeIvy(
|
||||
'FW-918: Create API and mental model to work with Host Element; and ChangeDetections')
|
||||
.it('should interleave scope and component expressions', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
const ng1Module = angular.module('ng1', []);
|
||||
const log: string[] = [];
|
||||
|
@ -196,8 +198,9 @@ withEachNg1Version(() => {
|
|||
}
|
||||
|
||||
@NgModule({
|
||||
declarations:
|
||||
[adapter.upgradeNg1Component('ng1a'), adapter.upgradeNg1Component('ng1b'), Ng2],
|
||||
declarations: [
|
||||
adapter.upgradeNg1Component('ng1a'), adapter.upgradeNg1Component('ng1b'), Ng2
|
||||
],
|
||||
imports: [BrowserModule],
|
||||
})
|
||||
class Ng2Module {
|
||||
|
@ -216,9 +219,7 @@ withEachNg1Version(() => {
|
|||
}));
|
||||
|
||||
|
||||
fixmeIvy(
|
||||
'FW-712: Rendering is being run on next "animation frame" rather than "Zone.microTaskEmpty" trigger')
|
||||
.it('should propagate changes to a downgraded component inside the ngZone', async(() => {
|
||||
it('should propagate changes to a downgraded component inside the ngZone', async(() => {
|
||||
let appComponent: AppComponent;
|
||||
let upgradeRef: UpgradeAdapterRef;
|
||||
|
||||
|
@ -242,19 +243,12 @@ withEachNg1Version(() => {
|
|||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['value'].isFirstChange()) return;
|
||||
|
||||
// HACK(ivy): Using setTimeout allows this test to pass but hides the ivy
|
||||
// renderer timing BC.
|
||||
// setTimeout(() => {
|
||||
// expect(element.textContent).toEqual('5');
|
||||
// upgradeRef.dispose();
|
||||
// }, 0);
|
||||
this.zone.onMicrotaskEmpty.subscribe(() => {
|
||||
expect(element.textContent).toEqual('5');
|
||||
upgradeRef.dispose();
|
||||
});
|
||||
|
||||
Promise.resolve().then(
|
||||
() => this.valueFromPromise = changes['value'].currentValue);
|
||||
Promise.resolve().then(() => this.valueFromPromise = changes['value'].currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,8 @@ withEachNg1Version(() => {
|
|||
beforeEach(() => destroyPlatform());
|
||||
afterEach(() => destroyPlatform());
|
||||
|
||||
it('should interleave scope and component expressions', async(() => {
|
||||
fixmeIvy('FW-918: Create API and mental model to work with Host Element; and ChangeDetections')
|
||||
.it('should interleave scope and component expressions', async(() => {
|
||||
const log: string[] = [];
|
||||
const l = (value: string) => {
|
||||
log.push(value);
|
||||
|
@ -76,9 +77,7 @@ withEachNg1Version(() => {
|
|||
});
|
||||
}));
|
||||
|
||||
fixmeIvy(
|
||||
'FW-712: Rendering is being run on next "animation frame" rather than "Zone.microTaskEmpty" trigger')
|
||||
.it('should propagate changes to a downgraded component inside the ngZone', async(() => {
|
||||
it('should propagate changes to a downgraded component inside the ngZone', async(() => {
|
||||
const element = html('<my-app></my-app>');
|
||||
let appComponent: AppComponent;
|
||||
|
||||
|
@ -102,15 +101,11 @@ withEachNg1Version(() => {
|
|||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['value'].isFirstChange()) return;
|
||||
|
||||
// HACK(ivy): Using setTimeout allows this test to pass but hides the ivy renderer
|
||||
// timing BC.
|
||||
// setTimeout(() => expect(element.textContent).toEqual('5'), 0);
|
||||
this.zone.onMicrotaskEmpty.subscribe(
|
||||
() => { expect(element.textContent).toEqual('5'); });
|
||||
|
||||
// Create a micro-task to update the value to be rendered asynchronously.
|
||||
Promise.resolve().then(
|
||||
() => this.valueFromPromise = changes['value'].currentValue);
|
||||
Promise.resolve().then(() => this.valueFromPromise = changes['value'].currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue