fix(core): make subclass inherit developer-defined data (#35105)

PR Close #35105
This commit is contained in:
zhuyujie 2020-02-02 19:04:13 +08:00 committed by Alex Rickabaugh
parent 8e12707f88
commit a756161dc2
2 changed files with 170 additions and 3 deletions

View File

@ -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<any>| 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<any>).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;
}
}
}

View File

@ -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: '<div>super-comp</div>',
host: {
'[@animation]': 'colorExp',
},
animations: [
trigger('animation', [state('color', style({color: 'red'}))]),
],
})
class SuperComponent {
colorExp = 'color';
}
@Component({
selector: 'my-comp',
template: `<div>my-comp</div>`,
})
class MyComponent extends SuperComponent {
}
@Component({
template: '<my-comp>app</my-comp>',
})
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: '<div>my-comp</div>',
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: '<my-comp>app</my-comp>',
})
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: '<div>my-comp</div>',
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: '<my-comp>app</my-comp>',
})
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