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:
parent
e8a57f0ee6
commit
feebe03523
|
@ -1144,6 +1144,7 @@ describe('compiler compliance', () => {
|
||||||
type: SimpleComponent,
|
type: SimpleComponent,
|
||||||
selectors: [["simple"]],
|
selectors: [["simple"]],
|
||||||
factory: function SimpleComponent_Factory(t) { return new (t || SimpleComponent)(); },
|
factory: function SimpleComponent_Factory(t) { return new (t || SimpleComponent)(); },
|
||||||
|
ngContentSelectors: _c0,
|
||||||
consts: 2,
|
consts: 2,
|
||||||
vars: 0,
|
vars: 0,
|
||||||
template: function SimpleComponent_Template(rf, ctx) {
|
template: function SimpleComponent_Template(rf, ctx) {
|
||||||
|
@ -1167,6 +1168,7 @@ describe('compiler compliance', () => {
|
||||||
type: ComplexComponent,
|
type: ComplexComponent,
|
||||||
selectors: [["complex"]],
|
selectors: [["complex"]],
|
||||||
factory: function ComplexComponent_Factory(t) { return new (t || ComplexComponent)(); },
|
factory: function ComplexComponent_Factory(t) { return new (t || ComplexComponent)(); },
|
||||||
|
ngContentSelectors: _c4,
|
||||||
consts: 4,
|
consts: 4,
|
||||||
vars: 0,
|
vars: 0,
|
||||||
template: function ComplexComponent_Template(rf, ctx) {
|
template: function ComplexComponent_Template(rf, ctx) {
|
||||||
|
@ -1561,6 +1563,7 @@ describe('compiler compliance', () => {
|
||||||
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList(queryStartIndex))) && ($instance$.someDir = $tmp$.first));
|
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList(queryStartIndex))) && ($instance$.someDir = $tmp$.first));
|
||||||
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList((queryStartIndex + 1)))) && ($instance$.someDirList = $tmp$));
|
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList((queryStartIndex + 1)))) && ($instance$.someDirList = $tmp$));
|
||||||
},
|
},
|
||||||
|
ngContentSelectors: _c0,
|
||||||
consts: 2,
|
consts: 2,
|
||||||
vars: 0,
|
vars: 0,
|
||||||
template: function ContentQueryComponent_Template(rf, ctx) {
|
template: function ContentQueryComponent_Template(rf, ctx) {
|
||||||
|
|
|
@ -264,6 +264,13 @@ export function compileComponentFromMetadata(
|
||||||
|
|
||||||
const templateFunctionExpression = templateBuilder.buildTemplateFunction(template.nodes, []);
|
const templateFunctionExpression = templateBuilder.buildTemplateFunction(template.nodes, []);
|
||||||
|
|
||||||
|
// We need to provide this so that dynamically generated components know what
|
||||||
|
// projected content blocks to pass through to the component when it is instantiated.
|
||||||
|
const ngContentSelectors = templateBuilder.getNgContentSelectors();
|
||||||
|
if (ngContentSelectors) {
|
||||||
|
definitionMap.set('ngContentSelectors', ngContentSelectors);
|
||||||
|
}
|
||||||
|
|
||||||
// e.g. `consts: 2`
|
// e.g. `consts: 2`
|
||||||
definitionMap.set('consts', o.literal(templateBuilder.getConstCount()));
|
definitionMap.set('consts', o.literal(templateBuilder.getConstCount()));
|
||||||
|
|
||||||
|
|
|
@ -915,6 +915,12 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
|
|
||||||
getVarCount() { return this._pureFunctionSlots; }
|
getVarCount() { return this._pureFunctionSlots; }
|
||||||
|
|
||||||
|
getNgContentSelectors(): o.Expression|null {
|
||||||
|
return this._hasNgContent ?
|
||||||
|
this.constantPool.getConstLiteral(asLiteral(this._ngContentSelectors), true) :
|
||||||
|
null;
|
||||||
|
}
|
||||||
|
|
||||||
private bindingContext() { return `${this._bindingContext++}`; }
|
private bindingContext() { return `${this._bindingContext++}`; }
|
||||||
|
|
||||||
// Bindings must only be resolved after all local refs have been visited, so all
|
// Bindings must only be resolved after all local refs have been visited, so all
|
||||||
|
|
|
@ -119,7 +119,10 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
||||||
super();
|
super();
|
||||||
this.componentType = componentDef.type;
|
this.componentType = componentDef.type;
|
||||||
this.selector = componentDef.selectors[0][0] as string;
|
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(
|
create(
|
||||||
|
|
|
@ -182,6 +182,11 @@ export function defineComponent<T>(componentDefinition: {
|
||||||
*/
|
*/
|
||||||
template: ComponentTemplate<T>;
|
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
|
* 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.
|
* set of instruction to be inserted into the template function.
|
||||||
|
@ -249,6 +254,7 @@ export function defineComponent<T>(componentDefinition: {
|
||||||
vars: componentDefinition.vars,
|
vars: componentDefinition.vars,
|
||||||
factory: componentDefinition.factory,
|
factory: componentDefinition.factory,
|
||||||
template: componentDefinition.template || null !,
|
template: componentDefinition.template || null !,
|
||||||
|
ngContentSelectors: componentDefinition.ngContentSelectors,
|
||||||
hostBindings: componentDefinition.hostBindings || null,
|
hostBindings: componentDefinition.hostBindings || null,
|
||||||
contentQueries: componentDefinition.contentQueries || null,
|
contentQueries: componentDefinition.contentQueries || null,
|
||||||
contentQueriesRefresh: componentDefinition.contentQueriesRefresh || null,
|
contentQueriesRefresh: componentDefinition.contentQueriesRefresh || null,
|
||||||
|
|
|
@ -189,6 +189,11 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
|
||||||
*/
|
*/
|
||||||
readonly template: ComponentTemplate<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.
|
* A set of styles that the component needs to be present for component to render correctly.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -18,7 +18,28 @@ describe('ComponentFactory', () => {
|
||||||
const cfr = injectComponentFactoryResolver();
|
const cfr = injectComponentFactoryResolver();
|
||||||
|
|
||||||
describe('constructor()', () => {
|
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 {
|
class TestComponent {
|
||||||
static ngComponentDef = defineComponent({
|
static ngComponentDef = defineComponent({
|
||||||
type: TestComponent,
|
type: TestComponent,
|
||||||
|
@ -27,6 +48,7 @@ describe('ComponentFactory', () => {
|
||||||
consts: 0,
|
consts: 0,
|
||||||
vars: 0,
|
vars: 0,
|
||||||
template: () => undefined,
|
template: () => undefined,
|
||||||
|
ngContentSelectors: ['a', 'b'],
|
||||||
factory: () => new TestComponent(),
|
factory: () => new TestComponent(),
|
||||||
inputs: {
|
inputs: {
|
||||||
in1: 'in1',
|
in1: 'in1',
|
||||||
|
@ -42,7 +64,7 @@ describe('ComponentFactory', () => {
|
||||||
const cf = cfr.resolveComponentFactory(TestComponent);
|
const cf = cfr.resolveComponentFactory(TestComponent);
|
||||||
|
|
||||||
expect(cf.componentType).toBe(TestComponent);
|
expect(cf.componentType).toBe(TestComponent);
|
||||||
expect(cf.ngContentSelectors).toEqual([]);
|
expect(cf.ngContentSelectors).toEqual(['*', 'a', 'b']);
|
||||||
expect(cf.selector).toBe('test');
|
expect(cf.selector).toBe('test');
|
||||||
|
|
||||||
expect(cf.inputs).toEqual([
|
expect(cf.inputs).toEqual([
|
||||||
|
|
|
@ -30,31 +30,30 @@ withEachNg1Version(() => {
|
||||||
describe('(basic use)', () => {
|
describe('(basic use)', () => {
|
||||||
it('should have AngularJS loaded', () => expect(angular.version.major).toBe(1));
|
it('should have AngularJS loaded', () => expect(angular.version.major).toBe(1));
|
||||||
|
|
||||||
fixmeIvy('FW-714: ng1 projected content is not being rendered')
|
it('should instantiate ng2 in ng1 template and project content', async(() => {
|
||||||
.it('should instantiate ng2 in ng1 template and project content', async(() => {
|
const ng1Module = angular.module('ng1', []);
|
||||||
const ng1Module = angular.module('ng1', []);
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ng2',
|
selector: 'ng2',
|
||||||
template: `{{ 'NG2' }}(<ng-content></ng-content>)`,
|
template: `{{ 'NG2' }}(<ng-content></ng-content>)`,
|
||||||
})
|
})
|
||||||
class Ng2 {
|
class Ng2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({declarations: [Ng2], imports: [BrowserModule]})
|
@NgModule({declarations: [Ng2], imports: [BrowserModule]})
|
||||||
class Ng2Module {
|
class Ng2Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
const element =
|
const element =
|
||||||
html('<div>{{ \'ng1[\' }}<ng2>~{{ \'ng-content\' }}~</ng2>{{ \']\' }}</div>');
|
html('<div>{{ \'ng1[\' }}<ng2>~{{ \'ng-content\' }}~</ng2>{{ \']\' }}</div>');
|
||||||
|
|
||||||
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module);
|
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module);
|
||||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||||
expect(document.body.textContent).toEqual('ng1[NG2(~ng-content~)]');
|
expect(document.body.textContent).toEqual('ng1[NG2(~ng-content~)]');
|
||||||
ref.dispose();
|
ref.dispose();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should instantiate ng1 in ng2 template and project content', async(() => {
|
it('should instantiate ng1 in ng2 template and project content', async(() => {
|
||||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||||
|
@ -724,72 +723,68 @@ withEachNg1Version(() => {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fixmeIvy('FW-714: ng1 projected content is not being rendered')
|
it('should support multi-slot projection', async(() => {
|
||||||
.it('should support multi-slot projection', async(() => {
|
const ng1Module = angular.module('ng1', []);
|
||||||
const ng1Module = angular.module('ng1', []);
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ng2',
|
selector: 'ng2',
|
||||||
template: '2a(<ng-content select=".ng1a"></ng-content>)' +
|
template: '2a(<ng-content select=".ng1a"></ng-content>)' +
|
||||||
'2b(<ng-content select=".ng1b"></ng-content>)'
|
'2b(<ng-content select=".ng1b"></ng-content>)'
|
||||||
})
|
})
|
||||||
class Ng2 {
|
class Ng2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({declarations: [Ng2], imports: [BrowserModule]})
|
@NgModule({declarations: [Ng2], imports: [BrowserModule]})
|
||||||
class Ng2Module {
|
class Ng2Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The ng-if on one of the projected children is here to make sure
|
// The ng-if on one of the projected children is here to make sure
|
||||||
// the correct slot is targeted even with structural directives in play.
|
// the correct slot is targeted even with structural directives in play.
|
||||||
const element = html(
|
const element = html(
|
||||||
'<ng2><div ng-if="true" class="ng1a">1a</div><div' +
|
'<ng2><div ng-if="true" class="ng1a">1a</div><div' +
|
||||||
' class="ng1b">1b</div></ng2>');
|
' class="ng1b">1b</div></ng2>');
|
||||||
|
|
||||||
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module);
|
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module);
|
||||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||||
expect(document.body.textContent).toEqual('2a(1a)2b(1b)');
|
expect(document.body.textContent).toEqual('2a(1a)2b(1b)');
|
||||||
ref.dispose();
|
ref.dispose();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fixmeIvy('FW-714: ng1 projected content is not being rendered')
|
it('should correctly project structural directives', async(() => {
|
||||||
.it('should correctly project structural directives', async(() => {
|
@Component({selector: 'ng2', template: 'ng2-{{ itemId }}(<ng-content></ng-content>)'})
|
||||||
@Component(
|
class Ng2Component {
|
||||||
{selector: 'ng2', template: 'ng2-{{ itemId }}(<ng-content></ng-content>)'})
|
// TODO(issue/24571): remove '!'.
|
||||||
class Ng2Component {
|
@Input() itemId !: string;
|
||||||
// TODO(issue/24571): remove '!'.
|
}
|
||||||
@Input() itemId !: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({imports: [BrowserModule], declarations: [Ng2Component]})
|
@NgModule({imports: [BrowserModule], declarations: [Ng2Component]})
|
||||||
class Ng2Module {
|
class Ng2Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module);
|
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module);
|
||||||
const ng1Module =
|
const ng1Module = angular.module('ng1', [])
|
||||||
angular.module('ng1', [])
|
.directive('ng2', adapter.downgradeNg2Component(Ng2Component))
|
||||||
.directive('ng2', adapter.downgradeNg2Component(Ng2Component))
|
.run(($rootScope: angular.IRootScopeService) => {
|
||||||
.run(($rootScope: angular.IRootScopeService) => {
|
$rootScope['items'] = [
|
||||||
$rootScope['items'] = [
|
{id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]},
|
||||||
{id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]},
|
{id: 'c', subitems: [7, 8, 9]}
|
||||||
{id: 'c', subitems: [7, 8, 9]}
|
];
|
||||||
];
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const element = html(`
|
const element = html(`
|
||||||
<ng2 ng-repeat="item in items" [item-id]="item.id">
|
<ng2 ng-repeat="item in items" [item-id]="item.id">
|
||||||
<div ng-repeat="subitem in item.subitems">{{ subitem }}</div>
|
<div ng-repeat="subitem in item.subitems">{{ subitem }}</div>
|
||||||
</ng2>
|
</ng2>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
adapter.bootstrap(element, [ng1Module.name]).ready(ref => {
|
adapter.bootstrap(element, [ng1Module.name]).ready(ref => {
|
||||||
expect(multiTrim(document.body.textContent))
|
expect(multiTrim(document.body.textContent))
|
||||||
.toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )');
|
.toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )');
|
||||||
ref.dispose();
|
ref.dispose();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should allow attribute selectors for components in ng2', async(() => {
|
it('should allow attribute selectors for components in ng2', async(() => {
|
||||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => MyNg2Module));
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => MyNg2Module));
|
||||||
|
@ -3110,7 +3105,7 @@ withEachNg1Version(() => {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fixmeIvy('FW-714: ng1 projected content is not being rendered')
|
fixmeIvy('FW-873: projected component injector hierarchy not wired up correctly')
|
||||||
.it('should respect hierarchical dependency injection for ng2', async(() => {
|
.it('should respect hierarchical dependency injection for ng2', async(() => {
|
||||||
const ng1Module = angular.module('ng1', []);
|
const ng1Module = angular.module('ng1', []);
|
||||||
|
|
||||||
|
@ -3213,45 +3208,44 @@ withEachNg1Version(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('examples', () => {
|
describe('examples', () => {
|
||||||
fixmeIvy('FW-714: ng1 projected content is not being rendered')
|
it('should verify UpgradeAdapter example', async(() => {
|
||||||
.it('should verify UpgradeAdapter example', async(() => {
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
const module = angular.module('myExample', []);
|
||||||
const module = angular.module('myExample', []);
|
|
||||||
|
|
||||||
const ng1 = () => {
|
const ng1 = () => {
|
||||||
return {
|
return {
|
||||||
scope: {title: '='},
|
scope: {title: '='},
|
||||||
transclude: true,
|
transclude: true,
|
||||||
template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)'
|
template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)'
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
module.directive('ng1', ng1);
|
module.directive('ng1', ng1);
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ng2',
|
selector: 'ng2',
|
||||||
inputs: ['name'],
|
inputs: ['name'],
|
||||||
template: 'ng2[<ng1 [title]="name">transclude</ng1>](<ng-content></ng-content>)'
|
template: 'ng2[<ng1 [title]="name">transclude</ng1>](<ng-content></ng-content>)'
|
||||||
})
|
})
|
||||||
class Ng2 {
|
class Ng2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
||||||
imports: [BrowserModule],
|
imports: [BrowserModule],
|
||||||
})
|
})
|
||||||
class Ng2Module {
|
class Ng2Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
|
|
||||||
document.body.innerHTML = '<ng2 name="World">project</ng2>';
|
document.body.innerHTML = '<ng2 name="World">project</ng2>';
|
||||||
|
|
||||||
adapter.bootstrap(document.body.firstElementChild !, ['myExample']).ready((ref) => {
|
adapter.bootstrap(document.body.firstElementChild !, ['myExample']).ready((ref) => {
|
||||||
expect(multiTrim(document.body.textContent))
|
expect(multiTrim(document.body.textContent))
|
||||||
.toEqual('ng2[ng1[Hello World!](transclude)](project)');
|
.toEqual('ng2[ng1[Hello World!](transclude)](project)');
|
||||||
ref.dispose();
|
ref.dispose();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('registerForNg1Tests', () => {
|
describe('registerForNg1Tests', () => {
|
||||||
|
|
|
@ -22,82 +22,78 @@ withEachNg1Version(() => {
|
||||||
beforeEach(() => destroyPlatform());
|
beforeEach(() => destroyPlatform());
|
||||||
afterEach(() => destroyPlatform());
|
afterEach(() => destroyPlatform());
|
||||||
|
|
||||||
fixmeIvy('FW-714: ng1 projected content is not being rendered')
|
it('should instantiate ng2 in ng1 template and project content', async(() => {
|
||||||
.it('should instantiate ng2 in ng1 template and project content', async(() => {
|
|
||||||
|
|
||||||
// the ng2 component that will be used in ng1 (downgraded)
|
// the ng2 component that will be used in ng1 (downgraded)
|
||||||
@Component({selector: 'ng2', template: `{{ prop }}(<ng-content></ng-content>)`})
|
@Component({selector: 'ng2', template: `{{ prop }}(<ng-content></ng-content>)`})
|
||||||
class Ng2Component {
|
class Ng2Component {
|
||||||
prop = 'NG2';
|
prop = 'NG2';
|
||||||
ngContent = 'ng2-content';
|
ngContent = 'ng2-content';
|
||||||
}
|
}
|
||||||
|
|
||||||
// our upgrade module to host the component to downgrade
|
// our upgrade module to host the component to downgrade
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [BrowserModule, UpgradeModule],
|
imports: [BrowserModule, UpgradeModule],
|
||||||
declarations: [Ng2Component],
|
declarations: [Ng2Component],
|
||||||
entryComponents: [Ng2Component]
|
entryComponents: [Ng2Component]
|
||||||
})
|
})
|
||||||
class Ng2Module {
|
class Ng2Module {
|
||||||
ngDoBootstrap() {}
|
ngDoBootstrap() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the ng1 app module that will consume the downgraded component
|
// the ng1 app module that will consume the downgraded component
|
||||||
const ng1Module = angular
|
const ng1Module = angular
|
||||||
.module('ng1', [])
|
.module('ng1', [])
|
||||||
// create an ng1 facade of the ng2 component
|
// create an ng1 facade of the ng2 component
|
||||||
.directive('ng2', downgradeComponent({component: Ng2Component}))
|
.directive('ng2', downgradeComponent({component: Ng2Component}))
|
||||||
.run(($rootScope: angular.IRootScopeService) => {
|
.run(($rootScope: angular.IRootScopeService) => {
|
||||||
$rootScope['prop'] = 'NG1';
|
$rootScope['prop'] = 'NG1';
|
||||||
$rootScope['ngContent'] = 'ng1-content';
|
$rootScope['ngContent'] = 'ng1-content';
|
||||||
});
|
});
|
||||||
|
|
||||||
const element =
|
const element = html('<div>{{ \'ng1[\' }}<ng2>~{{ ngContent }}~</ng2>{{ \']\' }}</div>');
|
||||||
html('<div>{{ \'ng1[\' }}<ng2>~{{ ngContent }}~</ng2>{{ \']\' }}</div>');
|
|
||||||
|
|
||||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||||
expect(document.body.textContent).toEqual('ng1[NG2(~ng1-content~)]');
|
expect(document.body.textContent).toEqual('ng1[NG2(~ng1-content~)]');
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fixmeIvy('FW-714: ng1 projected content is not being rendered')
|
it('should correctly project structural directives', async(() => {
|
||||||
.it('should correctly project structural directives', async(() => {
|
@Component({selector: 'ng2', template: 'ng2-{{ itemId }}(<ng-content></ng-content>)'})
|
||||||
@Component({selector: 'ng2', template: 'ng2-{{ itemId }}(<ng-content></ng-content>)'})
|
class Ng2Component {
|
||||||
class Ng2Component {
|
// TODO(issue/24571): remove '!'.
|
||||||
// TODO(issue/24571): remove '!'.
|
@Input() itemId !: string;
|
||||||
@Input() itemId !: string;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [BrowserModule, UpgradeModule],
|
imports: [BrowserModule, UpgradeModule],
|
||||||
declarations: [Ng2Component],
|
declarations: [Ng2Component],
|
||||||
entryComponents: [Ng2Component]
|
entryComponents: [Ng2Component]
|
||||||
})
|
})
|
||||||
class Ng2Module {
|
class Ng2Module {
|
||||||
ngDoBootstrap() {}
|
ngDoBootstrap() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ng1Module =
|
const ng1Module = angular.module('ng1', [])
|
||||||
angular.module('ng1', [])
|
.directive('ng2', downgradeComponent({component: Ng2Component}))
|
||||||
.directive('ng2', downgradeComponent({component: Ng2Component}))
|
.run(($rootScope: angular.IRootScopeService) => {
|
||||||
.run(($rootScope: angular.IRootScopeService) => {
|
$rootScope['items'] = [
|
||||||
$rootScope['items'] = [
|
{id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]},
|
||||||
{id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]},
|
{id: 'c', subitems: [7, 8, 9]}
|
||||||
{id: 'c', subitems: [7, 8, 9]}
|
];
|
||||||
];
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const element = html(`
|
const element = html(`
|
||||||
<ng2 ng-repeat="item in items" [item-id]="item.id">
|
<ng2 ng-repeat="item in items" [item-id]="item.id">
|
||||||
<div ng-repeat="subitem in item.subitems">{{ subitem }}</div>
|
<div ng-repeat="subitem in item.subitems">{{ subitem }}</div>
|
||||||
</ng2>
|
</ng2>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
|
||||||
expect(multiTrim(document.body.textContent))
|
expect(multiTrim(document.body.textContent))
|
||||||
.toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )');
|
.toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )');
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should instantiate ng1 in ng2 template and project content', async(() => {
|
it('should instantiate ng1 in ng2 template and project content', async(() => {
|
||||||
|
|
||||||
|
@ -145,39 +141,38 @@ withEachNg1Version(() => {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fixmeIvy('FW-714: ng1 projected content is not being rendered')
|
it('should support multi-slot projection', async(() => {
|
||||||
.it('should support multi-slot projection', async(() => {
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ng2',
|
selector: 'ng2',
|
||||||
template: '2a(<ng-content select=".ng1a"></ng-content>)' +
|
template: '2a(<ng-content select=".ng1a"></ng-content>)' +
|
||||||
'2b(<ng-content select=".ng1b"></ng-content>)'
|
'2b(<ng-content select=".ng1b"></ng-content>)'
|
||||||
})
|
})
|
||||||
class Ng2Component {
|
class Ng2Component {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [Ng2Component],
|
declarations: [Ng2Component],
|
||||||
entryComponents: [Ng2Component],
|
entryComponents: [Ng2Component],
|
||||||
imports: [BrowserModule, UpgradeModule]
|
imports: [BrowserModule, UpgradeModule]
|
||||||
})
|
})
|
||||||
class Ng2Module {
|
class Ng2Module {
|
||||||
ngDoBootstrap() {}
|
ngDoBootstrap() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ng1Module = angular.module('ng1', []).directive(
|
const ng1Module = angular.module('ng1', []).directive(
|
||||||
'ng2', downgradeComponent({component: Ng2Component}));
|
'ng2', downgradeComponent({component: Ng2Component}));
|
||||||
|
|
||||||
// The ng-if on one of the projected children is here to make sure
|
// The ng-if on one of the projected children is here to make sure
|
||||||
// the correct slot is targeted even with structural directives in play.
|
// the correct slot is targeted even with structural directives in play.
|
||||||
const element = html(
|
const element = html(
|
||||||
'<ng2><div ng-if="true" class="ng1a">1a</div><div' +
|
'<ng2><div ng-if="true" class="ng1a">1a</div><div' +
|
||||||
' class="ng1b">1b</div></ng2>');
|
' class="ng1b">1b</div></ng2>');
|
||||||
|
|
||||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||||
expect(document.body.textContent).toEqual('2a(1a)2b(1b)');
|
expect(document.body.textContent).toEqual('2a(1a)2b(1b)');
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -709,7 +709,7 @@ withEachNg1Version(() => {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fixmeIvy('FW-714: ng1 projected content is not being rendered')
|
fixmeIvy('FW-873: projected component injector hierarchy not wired up correctly')
|
||||||
.it('should respect hierarchical dependency injection for ng2', async(() => {
|
.it('should respect hierarchical dependency injection for ng2', async(() => {
|
||||||
@Component({selector: 'parent', template: 'parent(<ng-content></ng-content>)'})
|
@Component({selector: 'parent', template: 'parent(<ng-content></ng-content>)'})
|
||||||
class ParentComponent {
|
class ParentComponent {
|
||||||
|
|
|
@ -952,7 +952,6 @@ withEachNg1Version(() => {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
|
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
|
||||||
.fixmeIvy('FW-714: ng1 projected content is not being rendered')
|
|
||||||
.it('should run the lifecycle hooks in the correct order', async(() => {
|
.it('should run the lifecycle hooks in the correct order', async(() => {
|
||||||
const logs: string[] = [];
|
const logs: string[] = [];
|
||||||
let rootScope: angular.IRootScopeService;
|
let rootScope: angular.IRootScopeService;
|
||||||
|
|
|
@ -24,70 +24,69 @@ withEachNg1Version(() => {
|
||||||
|
|
||||||
it('should have AngularJS loaded', () => expect(angular.version.major).toBe(1));
|
it('should have AngularJS loaded', () => expect(angular.version.major).toBe(1));
|
||||||
|
|
||||||
fixmeIvy('FW-714: ng1 projected content is not being rendered')
|
it('should verify UpgradeAdapter example', async(() => {
|
||||||
.it('should verify UpgradeAdapter example', async(() => {
|
|
||||||
|
|
||||||
// This is wrapping (upgrading) an AngularJS component to be used in an Angular
|
// This is wrapping (upgrading) an AngularJS component to be used in an Angular
|
||||||
// component
|
// component
|
||||||
@Directive({selector: 'ng1'})
|
@Directive({selector: 'ng1'})
|
||||||
class Ng1Component extends UpgradeComponent {
|
class Ng1Component extends UpgradeComponent {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
@Input() title !: string;
|
@Input() title !: string;
|
||||||
|
|
||||||
constructor(elementRef: ElementRef, injector: Injector) {
|
constructor(elementRef: ElementRef, injector: Injector) {
|
||||||
super('ng1', elementRef, injector);
|
super('ng1', elementRef, injector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is an Angular component that will be downgraded
|
// This is an Angular component that will be downgraded
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ng2',
|
selector: 'ng2',
|
||||||
template: 'ng2[<ng1 [title]="nameProp">transclude</ng1>](<ng-content></ng-content>)'
|
template: 'ng2[<ng1 [title]="nameProp">transclude</ng1>](<ng-content></ng-content>)'
|
||||||
})
|
})
|
||||||
class Ng2Component {
|
class Ng2Component {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
@Input('name') nameProp !: string;
|
@Input('name') nameProp !: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This module represents the Angular pieces of the application
|
// This module represents the Angular pieces of the application
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [Ng1Component, Ng2Component],
|
declarations: [Ng1Component, Ng2Component],
|
||||||
entryComponents: [Ng2Component],
|
entryComponents: [Ng2Component],
|
||||||
imports: [BrowserModule, UpgradeModule]
|
imports: [BrowserModule, UpgradeModule]
|
||||||
})
|
})
|
||||||
class Ng2Module {
|
class Ng2Module {
|
||||||
ngDoBootstrap() { /* this is a placeholder to stop the bootstrapper from
|
ngDoBootstrap() { /* this is a placeholder to stop the bootstrapper from
|
||||||
complaining */
|
complaining */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This module represents the AngularJS pieces of the application
|
// This module represents the AngularJS pieces of the application
|
||||||
const ng1Module =
|
const ng1Module =
|
||||||
angular
|
angular
|
||||||
.module('myExample', [])
|
.module('myExample', [])
|
||||||
// This is an AngularJS component that will be upgraded
|
// This is an AngularJS component that will be upgraded
|
||||||
.directive(
|
.directive(
|
||||||
'ng1',
|
'ng1',
|
||||||
() => {
|
() => {
|
||||||
return {
|
return {
|
||||||
scope: {title: '='},
|
scope: {title: '='},
|
||||||
transclude: true,
|
transclude: true,
|
||||||
template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)'
|
template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)'
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
// This is wrapping (downgrading) an Angular component to be used in
|
// This is wrapping (downgrading) an Angular component to be used in
|
||||||
// AngularJS
|
// AngularJS
|
||||||
.directive('ng2', downgradeComponent({component: Ng2Component}));
|
.directive('ng2', downgradeComponent({component: Ng2Component}));
|
||||||
|
|
||||||
// This is the (AngularJS) application bootstrap element
|
// This is the (AngularJS) application bootstrap element
|
||||||
// Notice that it is actually a downgraded Angular component
|
// Notice that it is actually a downgraded Angular component
|
||||||
const element = html('<ng2 name="World">project</ng2>');
|
const element = html('<ng2 name="World">project</ng2>');
|
||||||
|
|
||||||
// Let's use a helper function to make this simpler
|
// Let's use a helper function to make this simpler
|
||||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
|
||||||
expect(multiTrim(element.textContent))
|
expect(multiTrim(element.textContent))
|
||||||
.toBe('ng2[ng1[Hello World!](transclude)](project)');
|
.toBe('ng2[ng1[Hello World!](transclude)](project)');
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue