fix(ivy): pass ngContentSelectors through to defineComponent() calls (#27867)

Libraries that create components dynamically using component factories,
such as `@angular/upgrade` need to pass blocks of projected content
through to the `ComponentFactory.create()` method. These blocks
are extracted from the content by matching CSS selectors defined in
`<ng-content select="..">` tags found in the component's template.

The Angular compiler collects these CSS selectors when compiling a component's
template, and exposes them via the `ComponentFactory.ngContentSelectors`
property.

This change ensures that this property is filled correctly when the
component factory is created by compiling a component with the Ivy engine.

PR Close #27867
This commit is contained in:
Pete Bacon Darwin
2018-12-22 16:02:34 +00:00
committed by Andrew Kushnir
parent e8a57f0ee6
commit feebe03523
12 changed files with 301 additions and 262 deletions

View File

@ -119,7 +119,10 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
super();
this.componentType = componentDef.type;
this.selector = componentDef.selectors[0][0] as string;
this.ngContentSelectors = [];
// The component definition does not include the wildcard ('*') selector in its list.
// It is implicitly expected as the first item in the projectable nodes array.
this.ngContentSelectors =
componentDef.ngContentSelectors ? ['*', ...componentDef.ngContentSelectors] : [];
}
create(

View File

@ -182,6 +182,11 @@ export function defineComponent<T>(componentDefinition: {
*/
template: ComponentTemplate<T>;
/**
* An array of `ngContent[selector]` values that were found in the template.
*/
ngContentSelectors?: string[];
/**
* Additional set of instructions specific to view query processing. This could be seen as a
* set of instruction to be inserted into the template function.
@ -249,6 +254,7 @@ export function defineComponent<T>(componentDefinition: {
vars: componentDefinition.vars,
factory: componentDefinition.factory,
template: componentDefinition.template || null !,
ngContentSelectors: componentDefinition.ngContentSelectors,
hostBindings: componentDefinition.hostBindings || null,
contentQueries: componentDefinition.contentQueries || null,
contentQueriesRefresh: componentDefinition.contentQueriesRefresh || null,

View File

@ -189,6 +189,11 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
*/
readonly template: ComponentTemplate<T>;
/**
* An array of `ngContent[selector]` values that were found in the template.
*/
readonly ngContentSelectors?: string[];
/**
* A set of styles that the component needs to be present for component to render correctly.
*/

View File

@ -18,7 +18,28 @@ describe('ComponentFactory', () => {
const cfr = injectComponentFactoryResolver();
describe('constructor()', () => {
it('should correctly populate public properties', () => {
it('should correctly populate default properties', () => {
class TestComponent {
static ngComponentDef = defineComponent({
type: TestComponent,
selectors: [['test', 'foo'], ['bar']],
consts: 0,
vars: 0,
template: () => undefined,
factory: () => new TestComponent(),
});
}
const cf = cfr.resolveComponentFactory(TestComponent);
expect(cf.selector).toBe('test');
expect(cf.componentType).toBe(TestComponent);
expect(cf.ngContentSelectors).toEqual([]);
expect(cf.inputs).toEqual([]);
expect(cf.outputs).toEqual([]);
});
it('should correctly populate defined properties', () => {
class TestComponent {
static ngComponentDef = defineComponent({
type: TestComponent,
@ -27,6 +48,7 @@ describe('ComponentFactory', () => {
consts: 0,
vars: 0,
template: () => undefined,
ngContentSelectors: ['a', 'b'],
factory: () => new TestComponent(),
inputs: {
in1: 'in1',
@ -42,7 +64,7 @@ describe('ComponentFactory', () => {
const cf = cfr.resolveComponentFactory(TestComponent);
expect(cf.componentType).toBe(TestComponent);
expect(cf.ngContentSelectors).toEqual([]);
expect(cf.ngContentSelectors).toEqual(['*', 'a', 'b']);
expect(cf.selector).toBe('test');
expect(cf.inputs).toEqual([