diff --git a/packages/compiler/src/compiler_facade_interface.ts b/packages/compiler/src/compiler_facade_interface.ts index f0da45188b..372483897a 100644 --- a/packages/compiler/src/compiler_facade_interface.ts +++ b/packages/compiler/src/compiler_facade_interface.ts @@ -134,10 +134,13 @@ export interface R3ComponentMetadataFacade extends R3DirectiveMetadataFacade { encapsulation: ViewEncapsulation; viewProviders: Provider[]|null; interpolation?: [string, string]; + changeDetection?: ChangeDetectionStrategy; } export type ViewEncapsulation = number; +export type ChangeDetectionStrategy = number; + export interface R3QueryMetadataFacade { propertyName: string; first: boolean; diff --git a/packages/compiler/src/jit_compiler_facade.ts b/packages/compiler/src/jit_compiler_facade.ts index 85ffdd3613..717efa833d 100644 --- a/packages/compiler/src/jit_compiler_facade.ts +++ b/packages/compiler/src/jit_compiler_facade.ts @@ -130,6 +130,7 @@ export class CompilerFacadeImpl implements CompilerFacade { styles: facade.styles || [], encapsulation: facade.encapsulation as any, interpolation: interpolationConfig, + changeDetection: facade.changeDetection, animations: facade.animations != null ? new WrappedNodeExpr(facade.animations) : null, viewProviders: facade.viewProviders != null ? new WrappedNodeExpr(facade.viewProviders) : null, diff --git a/packages/compiler/src/render3/view/api.ts b/packages/compiler/src/render3/view/api.ts index ae0929bcaf..8daba96587 100644 --- a/packages/compiler/src/render3/view/api.ts +++ b/packages/compiler/src/render3/view/api.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ViewEncapsulation} from '../../core'; +import {ViewEncapsulation, ChangeDetectionStrategy} from '../../core'; import {InterpolationConfig} from '../../ml_parser/interpolation_config'; import * as o from '../../output/output_ast'; import {ParseSourceSpan} from '../../parse_util'; @@ -184,14 +184,19 @@ export interface R3ComponentMetadata extends R3DirectiveMetadata { /** * Whether translation variable name should contain external message id - * (used by Closure Compiler's output of `goog.getMsg` for transition period) + * (used by Closure Compiler's output of `goog.getMsg` for transition period). */ i18nUseExternalIds: boolean; /** - * Overrides the default interpolation start and end delimiters ({{ and }}) + * Overrides the default interpolation start and end delimiters ({{ and }}). */ interpolation: InterpolationConfig; + + /** + * Strategy used for detecting changes in the component. + */ + changeDetection?: ChangeDetectionStrategy; } /** diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index 659fa3ca04..22705300ff 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -251,6 +251,7 @@ export function compileComponentFromMetadata( const directivesUsed = new Set(); const pipesUsed = new Set(); + const changeDetection = meta.changeDetection; const template = meta.template; const templateBuilder = new TemplateDefinitionBuilder( @@ -313,6 +314,11 @@ export function compileComponentFromMetadata( 'data', o.literalMap([{key: 'animation', value: meta.animations, quoted: false}])); } + // Only set the change detection flag if it's defined and it's not the default. + if (changeDetection != null && changeDetection !== core.ChangeDetectionStrategy.Default) { + definitionMap.set('changeDetection', o.literal(changeDetection)); + } + // On the type side, remove newlines from the selector as it will need to fit into a TypeScript // string literal, which must be on one line. const selectorForType = (meta.selector || '').replace(/\n/g, ''); diff --git a/packages/core/src/render3/jit/compiler_facade_interface.ts b/packages/core/src/render3/jit/compiler_facade_interface.ts index 20e49a1852..fe230636ff 100644 --- a/packages/core/src/render3/jit/compiler_facade_interface.ts +++ b/packages/core/src/render3/jit/compiler_facade_interface.ts @@ -134,10 +134,13 @@ export interface R3ComponentMetadataFacade extends R3DirectiveMetadataFacade { encapsulation: ViewEncapsulation; viewProviders: Provider[]|null; interpolation?: [string, string]; + changeDetection?: ChangeDetectionStrategy; } export type ViewEncapsulation = number; +export type ChangeDetectionStrategy = number; + export interface R3QueryMetadataFacade { propertyName: string; first: boolean; diff --git a/packages/core/src/render3/jit/directive.ts b/packages/core/src/render3/jit/directive.ts index 51dd00653c..383f124240 100644 --- a/packages/core/src/render3/jit/directive.ts +++ b/packages/core/src/render3/jit/directive.ts @@ -61,6 +61,7 @@ export function compileComponent(type: Type, metadata: Component): void { animations: metadata.animations, viewQueries: extractQueriesMetadata(type, getReflect().propMetadata(type), isViewQuery), directives: [], + changeDetection: metadata.changeDetection, pipes: new Map(), encapsulation: metadata.encapsulation || ViewEncapsulation.Emulated, interpolation: metadata.interpolation, diff --git a/packages/core/test/linker/change_detection_integration_spec.ts b/packages/core/test/linker/change_detection_integration_spec.ts index 69e4711fec..bf460c5dd8 100644 --- a/packages/core/test/linker/change_detection_integration_spec.ts +++ b/packages/core/test/linker/change_detection_integration_spec.ts @@ -1297,24 +1297,22 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [ })); - fixmeIvy( - 'FW-764: fixture.detectChanges() is not respecting OnPush flag on components in the root template') - .it('Reattaches in the original cd mode', fakeAsync(() => { - const ctx = createCompFixture(''); - const cmp: PushComp = queryDirs(ctx.debugElement, PushComp)[0]; - cmp.changeDetectorRef.detach(); - cmp.changeDetectorRef.reattach(); + it('Reattaches in the original cd mode', fakeAsync(() => { + const ctx = createCompFixture(''); + const cmp: PushComp = queryDirs(ctx.debugElement, PushComp)[0]; + cmp.changeDetectorRef.detach(); + cmp.changeDetectorRef.reattach(); - // renderCount should NOT be incremented with each CD as CD mode - // should be resetted to - // on-push - ctx.detectChanges(); - expect(cmp.renderCount).toBeGreaterThan(0); - const count = cmp.renderCount; + // renderCount should NOT be incremented with each CD as CD mode + // should be resetted to + // on-push + ctx.detectChanges(); + expect(cmp.renderCount).toBeGreaterThan(0); + const count = cmp.renderCount; - ctx.detectChanges(); - expect(cmp.renderCount).toBe(count); - })); + ctx.detectChanges(); + expect(cmp.renderCount).toBe(count); + })); }); diff --git a/packages/core/test/linker/integration_spec.ts b/packages/core/test/linker/integration_spec.ts index 0a19e360cb..bf32f820c4 100644 --- a/packages/core/test/linker/integration_spec.ts +++ b/packages/core/test/linker/integration_spec.ts @@ -585,47 +585,43 @@ function declareTests(config?: {useJit: boolean}) { describe('OnPush components', () => { - fixmeIvy( - 'FW-764: fixture.detectChanges() is not respecting OnPush flag on components in the root template') - .it('should use ChangeDetectorRef to manually request a check', () => { - TestBed.configureTestingModule({declarations: [MyComp, [[PushCmpWithRef]]]}); - const template = ''; - TestBed.overrideComponent(MyComp, {set: {template}}); - const fixture = TestBed.createComponent(MyComp); + it('should use ChangeDetectorRef to manually request a check', () => { + TestBed.configureTestingModule({declarations: [MyComp, [[PushCmpWithRef]]]}); + const template = ''; + TestBed.overrideComponent(MyComp, {set: {template}}); + const fixture = TestBed.createComponent(MyComp); - const cmp = fixture.debugElement.children[0].references !['cmp']; + const cmp = fixture.debugElement.children[0].references !['cmp']; - fixture.detectChanges(); - expect(cmp.numberOfChecks).toEqual(1); + fixture.detectChanges(); + expect(cmp.numberOfChecks).toEqual(1); - fixture.detectChanges(); - expect(cmp.numberOfChecks).toEqual(1); + fixture.detectChanges(); + expect(cmp.numberOfChecks).toEqual(1); - cmp.propagate(); + cmp.propagate(); - fixture.detectChanges(); - expect(cmp.numberOfChecks).toEqual(2); - }); + fixture.detectChanges(); + expect(cmp.numberOfChecks).toEqual(2); + }); - fixmeIvy( - 'FW-764: fixture.detectChanges() is not respecting OnPush flag on components in the root template') - .it('should be checked when its bindings got updated', () => { - TestBed.configureTestingModule( - {declarations: [MyComp, PushCmp, EventCmp], imports: [CommonModule]}); - const template = ''; - TestBed.overrideComponent(MyComp, {set: {template}}); - const fixture = TestBed.createComponent(MyComp); + it('should be checked when its bindings got updated', () => { + TestBed.configureTestingModule( + {declarations: [MyComp, PushCmp, EventCmp], imports: [CommonModule]}); + const template = ''; + TestBed.overrideComponent(MyComp, {set: {template}}); + const fixture = TestBed.createComponent(MyComp); - const cmp = fixture.debugElement.children[0].references !['cmp']; + const cmp = fixture.debugElement.children[0].references !['cmp']; - fixture.componentInstance.ctxProp = 'one'; - fixture.detectChanges(); - expect(cmp.numberOfChecks).toEqual(1); + fixture.componentInstance.ctxProp = 'one'; + fixture.detectChanges(); + expect(cmp.numberOfChecks).toEqual(1); - fixture.componentInstance.ctxProp = 'two'; - fixture.detectChanges(); - expect(cmp.numberOfChecks).toEqual(2); - }); + fixture.componentInstance.ctxProp = 'two'; + fixture.detectChanges(); + expect(cmp.numberOfChecks).toEqual(2); + }); if (getDOM().supportsDOMEvents()) { it('should allow to destroy a component from within a host event handler', @@ -702,32 +698,30 @@ function declareTests(config?: {useJit: boolean}) { expect(cmp.prop).toEqual('two'); }); - fixmeIvy( - 'FW-764: fixture.detectChanges() is not respecting OnPush flag on components in the root template') - .it('should be checked when an async pipe requests a check', fakeAsync(() => { - TestBed.configureTestingModule( - {declarations: [MyComp, PushCmpWithAsyncPipe], imports: [CommonModule]}); - const template = ''; - TestBed.overrideComponent(MyComp, {set: {template}}); - const fixture = TestBed.createComponent(MyComp); + it('should be checked when an async pipe requests a check', fakeAsync(() => { + TestBed.configureTestingModule( + {declarations: [MyComp, PushCmpWithAsyncPipe], imports: [CommonModule]}); + const template = ''; + TestBed.overrideComponent(MyComp, {set: {template}}); + const fixture = TestBed.createComponent(MyComp); - tick(); + tick(); - const cmp: PushCmpWithAsyncPipe = - fixture.debugElement.children[0].references !['cmp']; - fixture.detectChanges(); - expect(cmp.numberOfChecks).toEqual(1); + const cmp: PushCmpWithAsyncPipe = + fixture.debugElement.children[0].references !['cmp']; + fixture.detectChanges(); + expect(cmp.numberOfChecks).toEqual(1); - fixture.detectChanges(); - fixture.detectChanges(); - expect(cmp.numberOfChecks).toEqual(1); + fixture.detectChanges(); + fixture.detectChanges(); + expect(cmp.numberOfChecks).toEqual(1); - cmp.resolve(2); - tick(); + cmp.resolve(2); + tick(); - fixture.detectChanges(); - expect(cmp.numberOfChecks).toEqual(2); - })); + fixture.detectChanges(); + expect(cmp.numberOfChecks).toEqual(2); + })); }); it('should create a component that injects an @Host', () => {