Skip to content

Commit

Permalink
feat(core): introduce ChangeDetectionScheduler
Browse files Browse the repository at this point in the history
  • Loading branch information
trotyl committed Apr 1, 2018
1 parent d17f318 commit 6771813
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

# IDE - VSCode
.vscode/*
!.vscode/settings.json
.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
Expand Down
32 changes: 32 additions & 0 deletions packages/core/change-detection-scheduler/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Auto Change Detection with NoopZone

Automatically trigger change detection without Zone.js.

## Type

**Enhancement**

## Provenance

+ https://github.com/angular/angular/issues/20204

## NgModule

`@angular-contrib/core#ChangeDetectionSchedulerModule`

## Usage

```typescript
import { ChangeDetectionSchedulerModule } from '@angular-contrib/core';

@NgModule({
imports: [ ChangeDetectionSchedulerModule ],
})
class MyModule { }

platformBrowserDynamic.bootstrapModule(MyModule, { ngZone: 'noop' });
```

## Note

+ Depends on `markForCheck` integration like event binding, async pipe;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ComponentFactoryResolver, Injector, NgModule, RendererFactory2 } from '@angular/core';
import { ChangeDetectionScheduler, ChangeDetectionScheduler_, ChangeDetectionSchedulerInitializer } from './change-detection-scheduler';

@NgModule({
declarations: [ ChangeDetectionSchedulerInitializer ],
entryComponents: [ ChangeDetectionSchedulerInitializer ],
providers: [
{ provide: ChangeDetectionScheduler, useClass: ChangeDetectionScheduler_ },
],
})
export class ChangeDetectionSchedulerModule {
constructor(cfResolver: ComponentFactoryResolver, injector: Injector, rendererFactory: RendererFactory2) {
const componentFactory = cfResolver.resolveComponentFactory(ChangeDetectionSchedulerInitializer);
const renderer = rendererFactory.createRenderer(null, null);
const host = renderer.createElement('div');
const componentRef = componentFactory.create(injector, undefined, host);
componentRef.destroy();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { ChangeDetectorRef, Component, EventEmitter, NgZone } from '@angular/core';
import { async, inject, ComponentFixture, TestBed } from '@angular/core/testing';
import { ChangeDetectionScheduler } from './change-detection-scheduler';
import { ChangeDetectionSchedulerModule } from './change-detection-scheduler.module';

describe('ChangeDetectionScheduler', () => {
let fixture: ComponentFixture<TestComponent>;
let component: TestComponent;

let mockScheduler: ChangeDetectionScheduler;

const dummyNoopNgZone: NgZone = {
hasPendingMicrotasks: false,
hasPendingMacrotasks: false,
isStable: true,
onUnstable: new EventEmitter(),
onMicrotaskEmpty: new EventEmitter(),
onStable: new EventEmitter(),
onError: new EventEmitter(),
run(fn: () => any): any { return fn(); },
runGuarded(fn: () => any): any { return fn(); },
runOutsideAngular(fn: () => any): any { return fn(); },
runTask(fn: () => any): any { return fn(); },
};

beforeEach(() => {
mockScheduler = {
schedule: jasmine.createSpy('schedule'),
};
});

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TestComponent],
imports: [ChangeDetectionSchedulerModule],
providers: [
{ provide: NgZone, useValue: dummyNoopNgZone },
{ provide: ChangeDetectionScheduler, useValue: mockScheduler },
],
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
});

it('should perform change detection in startup', inject([ChangeDetectionScheduler], (scheduler: ChangeDetectionScheduler) => {
expect(scheduler.schedule).toHaveBeenCalledTimes(1);
}));

it('should perform change detection after events', inject([ChangeDetectionScheduler], (scheduler: ChangeDetectionScheduler) => {
component.changeDetectorRef.markForCheck();
expect(scheduler.schedule).toHaveBeenCalledTimes(2);
}));
});

@Component({
selector: 'test-cmp',
template: ``,
})
class TestComponent {
constructor(public changeDetectorRef: ChangeDetectorRef) { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { ApplicationRef, ChangeDetectorRef, Component, Injectable, NgZone } from '@angular/core';

let tickScheduled = false;

export abstract class ChangeDetectionScheduler {
abstract schedule(): void;
}

@Injectable()
export class ChangeDetectionScheduler_ implements ChangeDetectionScheduler {
constructor(private appRef: ApplicationRef) { }

schedule(): void {
tickScheduled = true;
Promise.resolve(null).then(() => {
tickScheduled = false;
this.appRef.tick();
});
}
}

@Component({
template: '',
})
export class ChangeDetectionSchedulerInitializer {
constructor(appRef: ApplicationRef, cdRef: ChangeDetectorRef, ngZone: NgZone, scheduler: ChangeDetectionScheduler) {
const markForCheck = cdRef.markForCheck;

const scheduleTickIfNeeded = () => {
if (!(ngZone instanceof NgZone) && !tickScheduled) {
scheduler.schedule();
}
};

while (typeof cdRef === 'object' && !cdRef.hasOwnProperty('markForCheck') && cdRef.constructor.prototype) {
cdRef = cdRef.constructor.prototype;
}

cdRef.markForCheck = function () {
markForCheck.call(this);
scheduleTickIfNeeded();
};

scheduleTickIfNeeded();
}
}
2 changes: 2 additions & 0 deletions packages/core/change-detection-scheduler/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './change-detection-scheduler';
export * from './change-detection-scheduler.module';
1 change: 1 addition & 0 deletions packages/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './change-detection-scheduler/index';
export * from './iterable-differs/index';
export * from './key-value-differs/index';
2 changes: 1 addition & 1 deletion packages/core/iterable-differs/iterable-differs.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Inject, IterableDiffers, IterableDifferFactory, ModuleWithProviders, NgModule } from '@angular/core';
import { Inject, IterableDiffers, IterableDifferFactory, NgModule } from '@angular/core';
import { ITERABLE_DIFFER_FACTORIES } from './iterable-differs';

@NgModule()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Inject, KeyValueDiffers, KeyValueDifferFactory, ModuleWithProviders, NgModule } from '@angular/core';
import { Inject, KeyValueDiffers, KeyValueDifferFactory, NgModule } from '@angular/core';
import { KEY_VALUE_DIFFER_FACTORIES } from './key-value-differs';

@NgModule()
Expand Down
2 changes: 1 addition & 1 deletion tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"rules": {
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"class-name": false,
"comment-format": [
true,
"check-space"
Expand Down

0 comments on commit 6771813

Please sign in to comment.