From a756161dc220f2479c624264f23046d8e55ec348 Mon Sep 17 00:00:00 2001 From: zhuyujie <1083941774@qq.com> Date: Sun, 2 Feb 2020 19:04:13 +0800 Subject: [PATCH] fix(core): make subclass inherit developer-defined data (#35105) PR Close #35105 --- .../features/inherit_definition_feature.ts | 14 +- .../inherit_definition_feature_spec.ts | 159 ++++++++++++++++++ 2 files changed, 170 insertions(+), 3 deletions(-) diff --git a/packages/core/src/render3/features/inherit_definition_feature.ts b/packages/core/src/render3/features/inherit_definition_feature.ts index e22e4392ca..4d96eafef3 100644 --- a/packages/core/src/render3/features/inherit_definition_feature.ts +++ b/packages/core/src/render3/features/inherit_definition_feature.ts @@ -7,11 +7,10 @@ */ import {Type, Writable} from '../../interface/type'; -import {assertEqual} from '../../util/assert'; import {fillProperties} from '../../util/property'; import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty'; import {ComponentDef, ContentQueriesFunction, DirectiveDef, DirectiveDefFeature, HostBindingsFunction, RenderFlags, ViewQueriesFunction} from '../interfaces/definition'; -import {AttributeMarker, TAttributes} from '../interfaces/node'; +import {TAttributes} from '../interfaces/node'; import {isComponentDef} from '../interfaces/type_checks'; import {mergeHostAttrs} from '../util/attrs_utils'; @@ -71,6 +70,15 @@ export function ɵɵInheritDefinitionFeature(definition: DirectiveDef| Comp fillProperties(definition.declaredInputs, superDef.declaredInputs); fillProperties(definition.outputs, superDef.outputs); + // Merge animations metadata. + // If `superDef` is a Component, the `data` field is present (defaults to an empty object). + if (isComponentDef(superDef) && superDef.data.animation) { + // If super def is a Component, the `definition` is also a Component, since Directives can + // not inherit Components (we throw an error above and cannot reach this code). + const defData = (definition as ComponentDef).data; + defData.animation = (defData.animation || []).concat(superDef.data.animation); + } + // Inherit hooks // Assume super class inheritance feature has already run. writeableDef.afterContentChecked = @@ -179,4 +187,4 @@ function inheritHostBindings( } else { definition.hostBindings = superHostBindings; } -} +} \ No newline at end of file diff --git a/packages/core/test/acceptance/inherit_definition_feature_spec.ts b/packages/core/test/acceptance/inherit_definition_feature_spec.ts index 745df8146c..91fc4dfa3d 100644 --- a/packages/core/test/acceptance/inherit_definition_feature_spec.ts +++ b/packages/core/test/acceptance/inherit_definition_feature_spec.ts @@ -6,11 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ +import {state, style, trigger} from '@angular/animations'; import {Component, ContentChildren, Directive, EventEmitter, HostBinding, HostListener, Input, OnChanges, Output, QueryList, ViewChildren} from '@angular/core'; import {ivyEnabled} from '@angular/core/src/ivy_switch'; import {getDirectiveDef} from '@angular/core/src/render3/definition'; import {TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; +import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {onlyInIvy} from '@angular/private/testing'; describe('inheritance', () => { @@ -4132,6 +4134,99 @@ describe('inheritance', () => { }); }); + describe('animations', () => { + onlyInIvy('View Engine does not inherit `host` metadata from superclass') + .it('should work with inherited host bindings and animations', () => { + @Component({ + selector: 'super-comp', + template: '
super-comp
', + host: { + '[@animation]': 'colorExp', + }, + animations: [ + trigger('animation', [state('color', style({color: 'red'}))]), + ], + }) + class SuperComponent { + colorExp = 'color'; + } + + @Component({ + selector: 'my-comp', + template: `
my-comp
`, + }) + class MyComponent extends SuperComponent { + } + + @Component({ + template: 'app', + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, SuperComponent], + imports: [NoopAnimationsModule], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const queryResult = fixture.debugElement.query(By.css('my-comp')); + + expect(queryResult.nativeElement.style.color).toBe('red'); + }); + + onlyInIvy('View Engine does not inherit `host` metadata from superclass') + .it('should compose animations (from super class)', () => { + @Component({ + selector: 'super-comp', + template: '...', + animations: [ + trigger('animation1', [state('color', style({color: 'red'}))]), + trigger('animation2', [state('opacity', style({opacity: '0.5'}))]), + ], + }) + class SuperComponent { + } + + @Component({ + selector: 'my-comp', + template: '
my-comp
', + host: { + '[@animation1]': 'colorExp', + '[@animation2]': 'opacityExp', + '[@animation3]': 'bgExp', + }, + animations: [ + trigger('animation1', [state('color', style({color: 'blue'}))]), + trigger('animation3', [state('bg', style({backgroundColor: 'green'}))]), + ], + }) + class MyComponent extends SuperComponent { + colorExp = 'color'; + opacityExp = 'opacity'; + bgExp = 'bg'; + } + + @Component({ + template: 'app', + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, SuperComponent], + imports: [NoopAnimationsModule], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const queryResult = fixture.debugElement.query(By.css('my-comp')); + + expect(queryResult.nativeElement.style.color).toBe('blue'); + expect(queryResult.nativeElement.style.opacity).toBe('0.5'); + expect(queryResult.nativeElement.style.backgroundColor).toBe('green'); + }); + }); + describe('host bindings (style related)', () => { // TODO: sub and super HostBinding same property but different bindings // TODO: sub and super HostBinding same binding on two different properties @@ -4819,6 +4914,70 @@ describe('inheritance', () => { }); }); + describe('animations', () => { + onlyInIvy('View Engine does not inherit `host` metadata from superclass') + .it('should compose animations across multiple inheritance levels', () => { + @Component({ + selector: 'super-comp', + template: '...', + host: { + '[@animation1]': 'colorExp', + '[@animation2]': 'opacityExp', + }, + animations: [ + trigger('animation1', [state('color', style({color: 'red'}))]), + trigger('animation2', [state('opacity', style({opacity: '0.5'}))]), + ], + }) + class SuperComponent { + colorExp = 'color'; + opacityExp = 'opacity'; + } + + @Component({ + selector: 'intermediate-comp', + template: '...', + }) + class IntermediateComponent extends SuperComponent { + } + + @Component({ + selector: 'my-comp', + template: '
my-comp
', + host: { + '[@animation1]': 'colorExp', + '[@animation3]': 'bgExp', + }, + animations: [ + trigger('animation1', [state('color', style({color: 'blue'}))]), + trigger('animation3', [state('bg', style({backgroundColor: 'green'}))]), + ], + }) + class MyComponent extends IntermediateComponent { + colorExp = 'color'; + opacityExp = 'opacity'; + bgExp = 'bg'; + } + + @Component({ + template: 'app', + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, IntermediateComponent, SuperComponent], + imports: [NoopAnimationsModule], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const queryResult = fixture.debugElement.query(By.css('my-comp')); + + expect(queryResult.nativeElement.style.color).toBe('blue'); + expect(queryResult.nativeElement.style.opacity).toBe('0.5'); + expect(queryResult.nativeElement.style.backgroundColor).toBe('green'); + }); + }); describe('host bindings (style related)', () => { // TODO: sub and super HostBinding same property but different bindings // TODO: sub and super HostBinding same binding on two different properties