fix(ivy): change detection strategy not being passed to compiler (#27753)

Fixes the defined change detection strategy not being passed to the compiler when a component is being compiled.

PR Close #27753
This commit is contained in:
Kristiyan Kostadinov 2018-12-20 09:49:24 +01:00 committed by Matias Niemelä
parent 4b70a4e905
commit a833b98fd0
8 changed files with 83 additions and 72 deletions

View File

@ -134,10 +134,13 @@ export interface R3ComponentMetadataFacade extends R3DirectiveMetadataFacade {
encapsulation: ViewEncapsulation; encapsulation: ViewEncapsulation;
viewProviders: Provider[]|null; viewProviders: Provider[]|null;
interpolation?: [string, string]; interpolation?: [string, string];
changeDetection?: ChangeDetectionStrategy;
} }
export type ViewEncapsulation = number; export type ViewEncapsulation = number;
export type ChangeDetectionStrategy = number;
export interface R3QueryMetadataFacade { export interface R3QueryMetadataFacade {
propertyName: string; propertyName: string;
first: boolean; first: boolean;

View File

@ -130,6 +130,7 @@ export class CompilerFacadeImpl implements CompilerFacade {
styles: facade.styles || [], styles: facade.styles || [],
encapsulation: facade.encapsulation as any, encapsulation: facade.encapsulation as any,
interpolation: interpolationConfig, interpolation: interpolationConfig,
changeDetection: facade.changeDetection,
animations: facade.animations != null ? new WrappedNodeExpr(facade.animations) : null, animations: facade.animations != null ? new WrappedNodeExpr(facade.animations) : null,
viewProviders: facade.viewProviders != null ? new WrappedNodeExpr(facade.viewProviders) : viewProviders: facade.viewProviders != null ? new WrappedNodeExpr(facade.viewProviders) :
null, null,

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * 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 {InterpolationConfig} from '../../ml_parser/interpolation_config';
import * as o from '../../output/output_ast'; import * as o from '../../output/output_ast';
import {ParseSourceSpan} from '../../parse_util'; import {ParseSourceSpan} from '../../parse_util';
@ -184,14 +184,19 @@ export interface R3ComponentMetadata extends R3DirectiveMetadata {
/** /**
* Whether translation variable name should contain external message id * 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; i18nUseExternalIds: boolean;
/** /**
* Overrides the default interpolation start and end delimiters ({{ and }}) * Overrides the default interpolation start and end delimiters ({{ and }}).
*/ */
interpolation: InterpolationConfig; interpolation: InterpolationConfig;
/**
* Strategy used for detecting changes in the component.
*/
changeDetection?: ChangeDetectionStrategy;
} }
/** /**

View File

@ -251,6 +251,7 @@ export function compileComponentFromMetadata(
const directivesUsed = new Set<o.Expression>(); const directivesUsed = new Set<o.Expression>();
const pipesUsed = new Set<o.Expression>(); const pipesUsed = new Set<o.Expression>();
const changeDetection = meta.changeDetection;
const template = meta.template; const template = meta.template;
const templateBuilder = new TemplateDefinitionBuilder( const templateBuilder = new TemplateDefinitionBuilder(
@ -313,6 +314,11 @@ export function compileComponentFromMetadata(
'data', o.literalMap([{key: 'animation', value: meta.animations, quoted: false}])); '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 // 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. // string literal, which must be on one line.
const selectorForType = (meta.selector || '').replace(/\n/g, ''); const selectorForType = (meta.selector || '').replace(/\n/g, '');

View File

@ -134,10 +134,13 @@ export interface R3ComponentMetadataFacade extends R3DirectiveMetadataFacade {
encapsulation: ViewEncapsulation; encapsulation: ViewEncapsulation;
viewProviders: Provider[]|null; viewProviders: Provider[]|null;
interpolation?: [string, string]; interpolation?: [string, string];
changeDetection?: ChangeDetectionStrategy;
} }
export type ViewEncapsulation = number; export type ViewEncapsulation = number;
export type ChangeDetectionStrategy = number;
export interface R3QueryMetadataFacade { export interface R3QueryMetadataFacade {
propertyName: string; propertyName: string;
first: boolean; first: boolean;

View File

@ -61,6 +61,7 @@ export function compileComponent(type: Type<any>, metadata: Component): void {
animations: metadata.animations, animations: metadata.animations,
viewQueries: extractQueriesMetadata(type, getReflect().propMetadata(type), isViewQuery), viewQueries: extractQueriesMetadata(type, getReflect().propMetadata(type), isViewQuery),
directives: [], directives: [],
changeDetection: metadata.changeDetection,
pipes: new Map(), pipes: new Map(),
encapsulation: metadata.encapsulation || ViewEncapsulation.Emulated, encapsulation: metadata.encapsulation || ViewEncapsulation.Emulated,
interpolation: metadata.interpolation, interpolation: metadata.interpolation,

View File

@ -1297,24 +1297,22 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
})); }));
fixmeIvy( it('Reattaches in the original cd mode', fakeAsync(() => {
'FW-764: fixture.detectChanges() is not respecting OnPush flag on components in the root template') const ctx = createCompFixture('<push-cmp></push-cmp>');
.it('Reattaches in the original cd mode', fakeAsync(() => { const cmp: PushComp = queryDirs(ctx.debugElement, PushComp)[0];
const ctx = createCompFixture('<push-cmp></push-cmp>'); cmp.changeDetectorRef.detach();
const cmp: PushComp = queryDirs(ctx.debugElement, PushComp)[0]; cmp.changeDetectorRef.reattach();
cmp.changeDetectorRef.detach();
cmp.changeDetectorRef.reattach();
// renderCount should NOT be incremented with each CD as CD mode // renderCount should NOT be incremented with each CD as CD mode
// should be resetted to // should be resetted to
// on-push // on-push
ctx.detectChanges(); ctx.detectChanges();
expect(cmp.renderCount).toBeGreaterThan(0); expect(cmp.renderCount).toBeGreaterThan(0);
const count = cmp.renderCount; const count = cmp.renderCount;
ctx.detectChanges(); ctx.detectChanges();
expect(cmp.renderCount).toBe(count); expect(cmp.renderCount).toBe(count);
})); }));
}); });

View File

@ -585,47 +585,43 @@ function declareTests(config?: {useJit: boolean}) {
describe('OnPush components', () => { describe('OnPush components', () => {
fixmeIvy( it('should use ChangeDetectorRef to manually request a check', () => {
'FW-764: fixture.detectChanges() is not respecting OnPush flag on components in the root template') TestBed.configureTestingModule({declarations: [MyComp, [[PushCmpWithRef]]]});
.it('should use ChangeDetectorRef to manually request a check', () => { const template = '<push-cmp-with-ref #cmp></push-cmp-with-ref>';
TestBed.configureTestingModule({declarations: [MyComp, [[PushCmpWithRef]]]}); TestBed.overrideComponent(MyComp, {set: {template}});
const template = '<push-cmp-with-ref #cmp></push-cmp-with-ref>'; const fixture = TestBed.createComponent(MyComp);
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(); fixture.detectChanges();
expect(cmp.numberOfChecks).toEqual(1); expect(cmp.numberOfChecks).toEqual(1);
fixture.detectChanges(); fixture.detectChanges();
expect(cmp.numberOfChecks).toEqual(1); expect(cmp.numberOfChecks).toEqual(1);
cmp.propagate(); cmp.propagate();
fixture.detectChanges(); fixture.detectChanges();
expect(cmp.numberOfChecks).toEqual(2); expect(cmp.numberOfChecks).toEqual(2);
}); });
fixmeIvy( it('should be checked when its bindings got updated', () => {
'FW-764: fixture.detectChanges() is not respecting OnPush flag on components in the root template') TestBed.configureTestingModule(
.it('should be checked when its bindings got updated', () => { {declarations: [MyComp, PushCmp, EventCmp], imports: [CommonModule]});
TestBed.configureTestingModule( const template = '<push-cmp [prop]="ctxProp" #cmp></push-cmp>';
{declarations: [MyComp, PushCmp, EventCmp], imports: [CommonModule]}); TestBed.overrideComponent(MyComp, {set: {template}});
const template = '<push-cmp [prop]="ctxProp" #cmp></push-cmp>'; const fixture = TestBed.createComponent(MyComp);
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.componentInstance.ctxProp = 'one';
fixture.detectChanges(); fixture.detectChanges();
expect(cmp.numberOfChecks).toEqual(1); expect(cmp.numberOfChecks).toEqual(1);
fixture.componentInstance.ctxProp = 'two'; fixture.componentInstance.ctxProp = 'two';
fixture.detectChanges(); fixture.detectChanges();
expect(cmp.numberOfChecks).toEqual(2); expect(cmp.numberOfChecks).toEqual(2);
}); });
if (getDOM().supportsDOMEvents()) { if (getDOM().supportsDOMEvents()) {
it('should allow to destroy a component from within a host event handler', 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'); expect(cmp.prop).toEqual('two');
}); });
fixmeIvy( it('should be checked when an async pipe requests a check', fakeAsync(() => {
'FW-764: fixture.detectChanges() is not respecting OnPush flag on components in the root template') TestBed.configureTestingModule(
.it('should be checked when an async pipe requests a check', fakeAsync(() => { {declarations: [MyComp, PushCmpWithAsyncPipe], imports: [CommonModule]});
TestBed.configureTestingModule( const template = '<push-cmp-with-async #cmp></push-cmp-with-async>';
{declarations: [MyComp, PushCmpWithAsyncPipe], imports: [CommonModule]}); TestBed.overrideComponent(MyComp, {set: {template}});
const template = '<push-cmp-with-async #cmp></push-cmp-with-async>'; const fixture = TestBed.createComponent(MyComp);
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
tick(); tick();
const cmp: PushCmpWithAsyncPipe = const cmp: PushCmpWithAsyncPipe =
fixture.debugElement.children[0].references !['cmp']; fixture.debugElement.children[0].references !['cmp'];
fixture.detectChanges(); fixture.detectChanges();
expect(cmp.numberOfChecks).toEqual(1); expect(cmp.numberOfChecks).toEqual(1);
fixture.detectChanges(); fixture.detectChanges();
fixture.detectChanges(); fixture.detectChanges();
expect(cmp.numberOfChecks).toEqual(1); expect(cmp.numberOfChecks).toEqual(1);
cmp.resolve(2); cmp.resolve(2);
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
expect(cmp.numberOfChecks).toEqual(2); expect(cmp.numberOfChecks).toEqual(2);
})); }));
}); });
it('should create a component that injects an @Host', () => { it('should create a component that injects an @Host', () => {