feat(ivy): add a runtime feature to copy cmp/dir definitions (#33362)
This commit adds CopyDefinitionFeature, which supports the case where an entire decorator (@Component or @Directive) is inherited from parent to child. The existing inheritance feature, InheritDefinitionFeature, supports merging of parent and child definitions when both were originally present. This merges things like inputs, outputs, host bindings, etc. CopyDefinitionFeature, on the other hand, compensates for a definition that was missing entirely on the child class, by copying fields that aren't ordinarily inherited (like the template function itself). This feature is intended to only be used as part of ngcc code generation. PR Close #33362
This commit is contained in:

committed by
Andrew Kushnir

parent
6b267482d7
commit
818c514968
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, NgModule, ɵɵCopyDefinitionFeature as CopyDefinitionFeature, ɵɵInheritDefinitionFeature as InheritDefinitionFeature, ɵɵdefineComponent as defineComponent} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {onlyInIvy} from '@angular/private/testing';
|
||||
|
||||
describe('Ivy CopyDefinitionFeature', () => {
|
||||
onlyInIvy('this feature is not required in View Engine')
|
||||
.it('should copy the template function of a component definition from parent to child',
|
||||
() => {
|
||||
|
||||
// It would be nice if the base component could be JIT compiled. However, this creates
|
||||
// a getter for ɵcmp which precludes adding a static definition of that field for the
|
||||
// child class.
|
||||
// TODO(alxhub): see if there's a cleaner way to do this.
|
||||
class BaseComponent {
|
||||
name !: string;
|
||||
static ɵcmp = defineComponent({
|
||||
type: BaseComponent,
|
||||
selectors: [['some-cmp']],
|
||||
decls: 0,
|
||||
vars: 0,
|
||||
inputs: {name: 'name'},
|
||||
template: function BaseComponent_Template(rf, ctx) { ctx.rendered = true; },
|
||||
encapsulation: 2
|
||||
});
|
||||
static ɵfac = function BaseComponent_Factory(t: any) {
|
||||
return new (t || BaseComponent)();
|
||||
};
|
||||
|
||||
rendered = false;
|
||||
}
|
||||
|
||||
class ChildComponent extends BaseComponent {
|
||||
static ɵcmp = defineComponent({
|
||||
type: ChildComponent,
|
||||
selectors: [['some-cmp']],
|
||||
features: [InheritDefinitionFeature, CopyDefinitionFeature],
|
||||
decls: 0,
|
||||
vars: 0,
|
||||
template: function ChildComponent_Template(rf, ctx) {},
|
||||
encapsulation: 2
|
||||
});
|
||||
static ɵfac = function ChildComponent_Factory(t: any) {
|
||||
return new (t || ChildComponent)();
|
||||
};
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [ChildComponent],
|
||||
exports: [ChildComponent],
|
||||
})
|
||||
class Module {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'test-cmp',
|
||||
template: '<some-cmp name="Success!"></some-cmp>',
|
||||
})
|
||||
class TestCmp {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TestCmp],
|
||||
imports: [Module],
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(TestCmp);
|
||||
|
||||
// The child component should have matched and been instantiated.
|
||||
const child = fixture.debugElement.children[0].componentInstance as ChildComponent;
|
||||
expect(child instanceof ChildComponent).toBe(true);
|
||||
|
||||
// And the base class template function should've been called.
|
||||
expect(child.rendered).toBe(true);
|
||||
|
||||
// The input binding should have worked.
|
||||
expect(child.name).toBe('Success!');
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user