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, | ||||
|             selectors: [["simple"]], | ||||
|             factory: function SimpleComponent_Factory(t) { return new (t || SimpleComponent)(); }, | ||||
|             ngContentSelectors: _c0, | ||||
|             consts: 2, | ||||
|             vars: 0, | ||||
|             template:  function SimpleComponent_Template(rf, ctx) { | ||||
| @ -1167,6 +1168,7 @@ describe('compiler compliance', () => { | ||||
|             type: ComplexComponent, | ||||
|             selectors: [["complex"]], | ||||
|             factory: function ComplexComponent_Factory(t) { return new (t || ComplexComponent)(); }, | ||||
|             ngContentSelectors: _c4, | ||||
|             consts: 4, | ||||
|             vars: 0, | ||||
|             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 + 1)))) && ($instance$.someDirList = $tmp$)); | ||||
|             }, | ||||
|             ngContentSelectors: _c0, | ||||
|             consts: 2, | ||||
|             vars: 0, | ||||
|             template:  function ContentQueryComponent_Template(rf, ctx) { | ||||
|  | ||||
| @ -264,6 +264,13 @@ export function compileComponentFromMetadata( | ||||
| 
 | ||||
|   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`
 | ||||
|   definitionMap.set('consts', o.literal(templateBuilder.getConstCount())); | ||||
| 
 | ||||
|  | ||||
| @ -915,6 +915,12 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver | ||||
| 
 | ||||
|   getVarCount() { return this._pureFunctionSlots; } | ||||
| 
 | ||||
|   getNgContentSelectors(): o.Expression|null { | ||||
|     return this._hasNgContent ? | ||||
|         this.constantPool.getConstLiteral(asLiteral(this._ngContentSelectors), true) : | ||||
|         null; | ||||
|   } | ||||
| 
 | ||||
|   private bindingContext() { return `${this._bindingContext++}`; } | ||||
| 
 | ||||
|   // 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(); | ||||
|     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( | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -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. | ||||
|    */ | ||||
|  | ||||
| @ -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([ | ||||
|  | ||||
| @ -30,31 +30,30 @@ withEachNg1Version(() => { | ||||
|     describe('(basic use)', () => { | ||||
|       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(() => { | ||||
|                 const ng1Module = angular.module('ng1', []); | ||||
|       it('should instantiate ng2 in ng1 template and project content', async(() => { | ||||
|            const ng1Module = angular.module('ng1', []); | ||||
| 
 | ||||
|                 @Component({ | ||||
|                   selector: 'ng2', | ||||
|                   template: `{{ 'NG2' }}(<ng-content></ng-content>)`, | ||||
|                 }) | ||||
|                 class Ng2 { | ||||
|                 } | ||||
|            @Component({ | ||||
|              selector: 'ng2', | ||||
|              template: `{{ 'NG2' }}(<ng-content></ng-content>)`, | ||||
|            }) | ||||
|            class Ng2 { | ||||
|            } | ||||
| 
 | ||||
|                 @NgModule({declarations: [Ng2], imports: [BrowserModule]}) | ||||
|                 class Ng2Module { | ||||
|                 } | ||||
|            @NgModule({declarations: [Ng2], imports: [BrowserModule]}) | ||||
|            class Ng2Module { | ||||
|            } | ||||
| 
 | ||||
|                 const element = | ||||
|                     html('<div>{{ \'ng1[\' }}<ng2>~{{ \'ng-content\' }}~</ng2>{{ \']\' }}</div>'); | ||||
|            const element = | ||||
|                html('<div>{{ \'ng1[\' }}<ng2>~{{ \'ng-content\' }}~</ng2>{{ \']\' }}</div>'); | ||||
| 
 | ||||
|                 const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module); | ||||
|                 ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); | ||||
|                 adapter.bootstrap(element, ['ng1']).ready((ref) => { | ||||
|                   expect(document.body.textContent).toEqual('ng1[NG2(~ng-content~)]'); | ||||
|                   ref.dispose(); | ||||
|                 }); | ||||
|               })); | ||||
|            const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module); | ||||
|            ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); | ||||
|            adapter.bootstrap(element, ['ng1']).ready((ref) => { | ||||
|              expect(document.body.textContent).toEqual('ng1[NG2(~ng-content~)]'); | ||||
|              ref.dispose(); | ||||
|            }); | ||||
|          })); | ||||
| 
 | ||||
|       it('should instantiate ng1 in ng2 template and project content', async(() => { | ||||
|            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(() => { | ||||
|                 const ng1Module = angular.module('ng1', []); | ||||
|       it('should support multi-slot projection', async(() => { | ||||
|            const ng1Module = angular.module('ng1', []); | ||||
| 
 | ||||
|                 @Component({ | ||||
|                   selector: 'ng2', | ||||
|                   template: '2a(<ng-content select=".ng1a"></ng-content>)' + | ||||
|                       '2b(<ng-content select=".ng1b"></ng-content>)' | ||||
|                 }) | ||||
|                 class Ng2 { | ||||
|                 } | ||||
|            @Component({ | ||||
|              selector: 'ng2', | ||||
|              template: '2a(<ng-content select=".ng1a"></ng-content>)' + | ||||
|                  '2b(<ng-content select=".ng1b"></ng-content>)' | ||||
|            }) | ||||
|            class Ng2 { | ||||
|            } | ||||
| 
 | ||||
|                 @NgModule({declarations: [Ng2], imports: [BrowserModule]}) | ||||
|                 class Ng2Module { | ||||
|                 } | ||||
|            @NgModule({declarations: [Ng2], imports: [BrowserModule]}) | ||||
|            class Ng2Module { | ||||
|            } | ||||
| 
 | ||||
|                 // 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.
 | ||||
|                 const element = html( | ||||
|                     '<ng2><div ng-if="true" class="ng1a">1a</div><div' + | ||||
|                     ' class="ng1b">1b</div></ng2>'); | ||||
|            // 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.
 | ||||
|            const element = html( | ||||
|                '<ng2><div ng-if="true" class="ng1a">1a</div><div' + | ||||
|                ' class="ng1b">1b</div></ng2>'); | ||||
| 
 | ||||
|                 const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module); | ||||
|                 ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); | ||||
|                 adapter.bootstrap(element, ['ng1']).ready((ref) => { | ||||
|                   expect(document.body.textContent).toEqual('2a(1a)2b(1b)'); | ||||
|                   ref.dispose(); | ||||
|                 }); | ||||
|               })); | ||||
|            const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module); | ||||
|            ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2)); | ||||
|            adapter.bootstrap(element, ['ng1']).ready((ref) => { | ||||
|              expect(document.body.textContent).toEqual('2a(1a)2b(1b)'); | ||||
|              ref.dispose(); | ||||
|            }); | ||||
|          })); | ||||
| 
 | ||||
|       fixmeIvy('FW-714: ng1 projected content is not being rendered') | ||||
|           .it('should correctly project structural directives', async(() => { | ||||
|                 @Component( | ||||
|                     {selector: 'ng2', template: 'ng2-{{ itemId }}(<ng-content></ng-content>)'}) | ||||
|                 class Ng2Component { | ||||
|                   // TODO(issue/24571): remove '!'.
 | ||||
|                   @Input() itemId !: string; | ||||
|                 } | ||||
|       it('should correctly project structural directives', async(() => { | ||||
|            @Component({selector: 'ng2', template: 'ng2-{{ itemId }}(<ng-content></ng-content>)'}) | ||||
|            class Ng2Component { | ||||
|              // TODO(issue/24571): remove '!'.
 | ||||
|              @Input() itemId !: string; | ||||
|            } | ||||
| 
 | ||||
|                 @NgModule({imports: [BrowserModule], declarations: [Ng2Component]}) | ||||
|                 class Ng2Module { | ||||
|                 } | ||||
|            @NgModule({imports: [BrowserModule], declarations: [Ng2Component]}) | ||||
|            class Ng2Module { | ||||
|            } | ||||
| 
 | ||||
|                 const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module); | ||||
|                 const ng1Module = | ||||
|                     angular.module('ng1', []) | ||||
|                         .directive('ng2', adapter.downgradeNg2Component(Ng2Component)) | ||||
|                         .run(($rootScope: angular.IRootScopeService) => { | ||||
|                           $rootScope['items'] = [ | ||||
|                             {id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]}, | ||||
|                             {id: 'c', subitems: [7, 8, 9]} | ||||
|                           ]; | ||||
|                         }); | ||||
|            const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module); | ||||
|            const ng1Module = angular.module('ng1', []) | ||||
|                                  .directive('ng2', adapter.downgradeNg2Component(Ng2Component)) | ||||
|                                  .run(($rootScope: angular.IRootScopeService) => { | ||||
|                                    $rootScope['items'] = [ | ||||
|                                      {id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]}, | ||||
|                                      {id: 'c', subitems: [7, 8, 9]} | ||||
|                                    ]; | ||||
|                                  }); | ||||
| 
 | ||||
|                 const element = html(` | ||||
|            const element = html(` | ||||
|              <ng2 ng-repeat="item in items" [item-id]="item.id"> | ||||
|                <div ng-repeat="subitem in item.subitems">{{ subitem }}</div> | ||||
|              </ng2> | ||||
|            `);
 | ||||
| 
 | ||||
|                 adapter.bootstrap(element, [ng1Module.name]).ready(ref => { | ||||
|                   expect(multiTrim(document.body.textContent)) | ||||
|                       .toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )'); | ||||
|                   ref.dispose(); | ||||
|                 }); | ||||
|               })); | ||||
|            adapter.bootstrap(element, [ng1Module.name]).ready(ref => { | ||||
|              expect(multiTrim(document.body.textContent)) | ||||
|                  .toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )'); | ||||
|              ref.dispose(); | ||||
|            }); | ||||
|          })); | ||||
| 
 | ||||
|       it('should allow attribute selectors for components in ng2', async(() => { | ||||
|            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(() => { | ||||
|                 const ng1Module = angular.module('ng1', []); | ||||
| 
 | ||||
| @ -3213,45 +3208,44 @@ withEachNg1Version(() => { | ||||
|     }); | ||||
| 
 | ||||
|     describe('examples', () => { | ||||
|       fixmeIvy('FW-714: ng1 projected content is not being rendered') | ||||
|           .it('should verify UpgradeAdapter example', async(() => { | ||||
|                 const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); | ||||
|                 const module = angular.module('myExample', []); | ||||
|       it('should verify UpgradeAdapter example', async(() => { | ||||
|            const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); | ||||
|            const module = angular.module('myExample', []); | ||||
| 
 | ||||
|                 const ng1 = () => { | ||||
|                   return { | ||||
|                     scope: {title: '='}, | ||||
|                     transclude: true, | ||||
|                     template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)' | ||||
|                   }; | ||||
|                 }; | ||||
|                 module.directive('ng1', ng1); | ||||
|            const ng1 = () => { | ||||
|              return { | ||||
|                scope: {title: '='}, | ||||
|                transclude: true, | ||||
|                template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)' | ||||
|              }; | ||||
|            }; | ||||
|            module.directive('ng1', ng1); | ||||
| 
 | ||||
|                 @Component({ | ||||
|                   selector: 'ng2', | ||||
|                   inputs: ['name'], | ||||
|                   template: 'ng2[<ng1 [title]="name">transclude</ng1>](<ng-content></ng-content>)' | ||||
|                 }) | ||||
|                 class Ng2 { | ||||
|                 } | ||||
|            @Component({ | ||||
|              selector: 'ng2', | ||||
|              inputs: ['name'], | ||||
|              template: 'ng2[<ng1 [title]="name">transclude</ng1>](<ng-content></ng-content>)' | ||||
|            }) | ||||
|            class Ng2 { | ||||
|            } | ||||
| 
 | ||||
|                 @NgModule({ | ||||
|                   declarations: [adapter.upgradeNg1Component('ng1'), Ng2], | ||||
|                   imports: [BrowserModule], | ||||
|                 }) | ||||
|                 class Ng2Module { | ||||
|                 } | ||||
|            @NgModule({ | ||||
|              declarations: [adapter.upgradeNg1Component('ng1'), Ng2], | ||||
|              imports: [BrowserModule], | ||||
|            }) | ||||
|            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) => { | ||||
|                   expect(multiTrim(document.body.textContent)) | ||||
|                       .toEqual('ng2[ng1[Hello World!](transclude)](project)'); | ||||
|                   ref.dispose(); | ||||
|                 }); | ||||
|               })); | ||||
|            adapter.bootstrap(document.body.firstElementChild !, ['myExample']).ready((ref) => { | ||||
|              expect(multiTrim(document.body.textContent)) | ||||
|                  .toEqual('ng2[ng1[Hello World!](transclude)](project)'); | ||||
|              ref.dispose(); | ||||
|            }); | ||||
|          })); | ||||
|     }); | ||||
| 
 | ||||
|     describe('registerForNg1Tests', () => { | ||||
|  | ||||
| @ -22,82 +22,78 @@ withEachNg1Version(() => { | ||||
|     beforeEach(() => 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)
 | ||||
|               @Component({selector: 'ng2', template: `{{ prop }}(<ng-content></ng-content>)`}) | ||||
|               class Ng2Component { | ||||
|                 prop = 'NG2'; | ||||
|                 ngContent = 'ng2-content'; | ||||
|               } | ||||
|          // the ng2 component that will be used in ng1 (downgraded)
 | ||||
|          @Component({selector: 'ng2', template: `{{ prop }}(<ng-content></ng-content>)`}) | ||||
|          class Ng2Component { | ||||
|            prop = 'NG2'; | ||||
|            ngContent = 'ng2-content'; | ||||
|          } | ||||
| 
 | ||||
|               // our upgrade module to host the component to downgrade
 | ||||
|               @NgModule({ | ||||
|                 imports: [BrowserModule, UpgradeModule], | ||||
|                 declarations: [Ng2Component], | ||||
|                 entryComponents: [Ng2Component] | ||||
|               }) | ||||
|               class Ng2Module { | ||||
|                 ngDoBootstrap() {} | ||||
|               } | ||||
|          // our upgrade module to host the component to downgrade
 | ||||
|          @NgModule({ | ||||
|            imports: [BrowserModule, UpgradeModule], | ||||
|            declarations: [Ng2Component], | ||||
|            entryComponents: [Ng2Component] | ||||
|          }) | ||||
|          class Ng2Module { | ||||
|            ngDoBootstrap() {} | ||||
|          } | ||||
| 
 | ||||
|               // the ng1 app module that will consume the downgraded component
 | ||||
|               const ng1Module = angular | ||||
|                                     .module('ng1', []) | ||||
|                                     // create an ng1 facade of the ng2 component
 | ||||
|                                     .directive('ng2', downgradeComponent({component: Ng2Component})) | ||||
|                                     .run(($rootScope: angular.IRootScopeService) => { | ||||
|                                       $rootScope['prop'] = 'NG1'; | ||||
|                                       $rootScope['ngContent'] = 'ng1-content'; | ||||
|                                     }); | ||||
|          // the ng1 app module that will consume the downgraded component
 | ||||
|          const ng1Module = angular | ||||
|                                .module('ng1', []) | ||||
|                                // create an ng1 facade of the ng2 component
 | ||||
|                                .directive('ng2', downgradeComponent({component: Ng2Component})) | ||||
|                                .run(($rootScope: angular.IRootScopeService) => { | ||||
|                                  $rootScope['prop'] = 'NG1'; | ||||
|                                  $rootScope['ngContent'] = 'ng1-content'; | ||||
|                                }); | ||||
| 
 | ||||
|               const element = | ||||
|                   html('<div>{{ \'ng1[\' }}<ng2>~{{ ngContent }}~</ng2>{{ \']\' }}</div>'); | ||||
|          const element = html('<div>{{ \'ng1[\' }}<ng2>~{{ ngContent }}~</ng2>{{ \']\' }}</div>'); | ||||
| 
 | ||||
|               bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { | ||||
|                 expect(document.body.textContent).toEqual('ng1[NG2(~ng1-content~)]'); | ||||
|               }); | ||||
|             })); | ||||
|          bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { | ||||
|            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(() => { | ||||
|               @Component({selector: 'ng2', template: 'ng2-{{ itemId }}(<ng-content></ng-content>)'}) | ||||
|               class Ng2Component { | ||||
|                 // TODO(issue/24571): remove '!'.
 | ||||
|                 @Input() itemId !: string; | ||||
|               } | ||||
|     it('should correctly project structural directives', async(() => { | ||||
|          @Component({selector: 'ng2', template: 'ng2-{{ itemId }}(<ng-content></ng-content>)'}) | ||||
|          class Ng2Component { | ||||
|            // TODO(issue/24571): remove '!'.
 | ||||
|            @Input() itemId !: string; | ||||
|          } | ||||
| 
 | ||||
|               @NgModule({ | ||||
|                 imports: [BrowserModule, UpgradeModule], | ||||
|                 declarations: [Ng2Component], | ||||
|                 entryComponents: [Ng2Component] | ||||
|               }) | ||||
|               class Ng2Module { | ||||
|                 ngDoBootstrap() {} | ||||
|               } | ||||
|          @NgModule({ | ||||
|            imports: [BrowserModule, UpgradeModule], | ||||
|            declarations: [Ng2Component], | ||||
|            entryComponents: [Ng2Component] | ||||
|          }) | ||||
|          class Ng2Module { | ||||
|            ngDoBootstrap() {} | ||||
|          } | ||||
| 
 | ||||
|               const ng1Module = | ||||
|                   angular.module('ng1', []) | ||||
|                       .directive('ng2', downgradeComponent({component: Ng2Component})) | ||||
|                       .run(($rootScope: angular.IRootScopeService) => { | ||||
|                         $rootScope['items'] = [ | ||||
|                           {id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]}, | ||||
|                           {id: 'c', subitems: [7, 8, 9]} | ||||
|                         ]; | ||||
|                       }); | ||||
|          const ng1Module = angular.module('ng1', []) | ||||
|                                .directive('ng2', downgradeComponent({component: Ng2Component})) | ||||
|                                .run(($rootScope: angular.IRootScopeService) => { | ||||
|                                  $rootScope['items'] = [ | ||||
|                                    {id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]}, | ||||
|                                    {id: 'c', subitems: [7, 8, 9]} | ||||
|                                  ]; | ||||
|                                }); | ||||
| 
 | ||||
|               const element = html(` | ||||
|          const element = html(` | ||||
|            <ng2 ng-repeat="item in items" [item-id]="item.id"> | ||||
|              <div ng-repeat="subitem in item.subitems">{{ subitem }}</div> | ||||
|            </ng2> | ||||
|          `);
 | ||||
| 
 | ||||
|               bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { | ||||
|                 expect(multiTrim(document.body.textContent)) | ||||
|                     .toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )'); | ||||
|               }); | ||||
|             })); | ||||
|          bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { | ||||
|            expect(multiTrim(document.body.textContent)) | ||||
|                .toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )'); | ||||
|          }); | ||||
|        })); | ||||
| 
 | ||||
|     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({ | ||||
|                 selector: 'ng2', | ||||
|                 template: '2a(<ng-content select=".ng1a"></ng-content>)' + | ||||
|                     '2b(<ng-content select=".ng1b"></ng-content>)' | ||||
|               }) | ||||
|               class Ng2Component { | ||||
|                 constructor() {} | ||||
|               } | ||||
|          @Component({ | ||||
|            selector: 'ng2', | ||||
|            template: '2a(<ng-content select=".ng1a"></ng-content>)' + | ||||
|                '2b(<ng-content select=".ng1b"></ng-content>)' | ||||
|          }) | ||||
|          class Ng2Component { | ||||
|            constructor() {} | ||||
|          } | ||||
| 
 | ||||
|               @NgModule({ | ||||
|                 declarations: [Ng2Component], | ||||
|                 entryComponents: [Ng2Component], | ||||
|                 imports: [BrowserModule, UpgradeModule] | ||||
|               }) | ||||
|               class Ng2Module { | ||||
|                 ngDoBootstrap() {} | ||||
|               } | ||||
|          @NgModule({ | ||||
|            declarations: [Ng2Component], | ||||
|            entryComponents: [Ng2Component], | ||||
|            imports: [BrowserModule, UpgradeModule] | ||||
|          }) | ||||
|          class Ng2Module { | ||||
|            ngDoBootstrap() {} | ||||
|          } | ||||
| 
 | ||||
|               const ng1Module = angular.module('ng1', []).directive( | ||||
|                   'ng2', downgradeComponent({component: Ng2Component})); | ||||
|          const ng1Module = angular.module('ng1', []).directive( | ||||
|              'ng2', downgradeComponent({component: Ng2Component})); | ||||
| 
 | ||||
|               // 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.
 | ||||
|               const element = html( | ||||
|                   '<ng2><div ng-if="true" class="ng1a">1a</div><div' + | ||||
|                   ' class="ng1b">1b</div></ng2>'); | ||||
|          // 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.
 | ||||
|          const element = html( | ||||
|              '<ng2><div ng-if="true" class="ng1a">1a</div><div' + | ||||
|              ' class="ng1b">1b</div></ng2>'); | ||||
| 
 | ||||
|               bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { | ||||
|                 expect(document.body.textContent).toEqual('2a(1a)2b(1b)'); | ||||
|               }); | ||||
|             })); | ||||
|          bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { | ||||
|            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(() => { | ||||
|               @Component({selector: 'parent', template: 'parent(<ng-content></ng-content>)'}) | ||||
|               class ParentComponent { | ||||
|  | ||||
| @ -952,7 +952,6 @@ withEachNg1Version(() => { | ||||
|               })); | ||||
| 
 | ||||
|       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(() => { | ||||
|                 const logs: string[] = []; | ||||
|                 let rootScope: angular.IRootScopeService; | ||||
|  | ||||
| @ -24,70 +24,69 @@ withEachNg1Version(() => { | ||||
| 
 | ||||
|     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
 | ||||
|               // component
 | ||||
|               @Directive({selector: 'ng1'}) | ||||
|               class Ng1Component extends UpgradeComponent { | ||||
|                 // TODO(issue/24571): remove '!'.
 | ||||
|                 @Input() title !: string; | ||||
|          // This is wrapping (upgrading) an AngularJS component to be used in an Angular
 | ||||
|          // component
 | ||||
|          @Directive({selector: 'ng1'}) | ||||
|          class Ng1Component extends UpgradeComponent { | ||||
|            // TODO(issue/24571): remove '!'.
 | ||||
|            @Input() title !: string; | ||||
| 
 | ||||
|                 constructor(elementRef: ElementRef, injector: Injector) { | ||||
|                   super('ng1', elementRef, injector); | ||||
|                 } | ||||
|               } | ||||
|            constructor(elementRef: ElementRef, injector: Injector) { | ||||
|              super('ng1', elementRef, injector); | ||||
|            } | ||||
|          } | ||||
| 
 | ||||
|               // This is an Angular component that will be downgraded
 | ||||
|               @Component({ | ||||
|                 selector: 'ng2', | ||||
|                 template: 'ng2[<ng1 [title]="nameProp">transclude</ng1>](<ng-content></ng-content>)' | ||||
|               }) | ||||
|               class Ng2Component { | ||||
|                 // TODO(issue/24571): remove '!'.
 | ||||
|                 @Input('name') nameProp !: string; | ||||
|               } | ||||
|          // This is an Angular component that will be downgraded
 | ||||
|          @Component({ | ||||
|            selector: 'ng2', | ||||
|            template: 'ng2[<ng1 [title]="nameProp">transclude</ng1>](<ng-content></ng-content>)' | ||||
|          }) | ||||
|          class Ng2Component { | ||||
|            // TODO(issue/24571): remove '!'.
 | ||||
|            @Input('name') nameProp !: string; | ||||
|          } | ||||
| 
 | ||||
|               // This module represents the Angular pieces of the application
 | ||||
|               @NgModule({ | ||||
|                 declarations: [Ng1Component, Ng2Component], | ||||
|                 entryComponents: [Ng2Component], | ||||
|                 imports: [BrowserModule, UpgradeModule] | ||||
|               }) | ||||
|               class Ng2Module { | ||||
|                 ngDoBootstrap() { /* this is a placeholder to stop the bootstrapper from | ||||
|                                      complaining */ | ||||
|                 } | ||||
|               } | ||||
|          // This module represents the Angular pieces of the application
 | ||||
|          @NgModule({ | ||||
|            declarations: [Ng1Component, Ng2Component], | ||||
|            entryComponents: [Ng2Component], | ||||
|            imports: [BrowserModule, UpgradeModule] | ||||
|          }) | ||||
|          class Ng2Module { | ||||
|            ngDoBootstrap() { /* this is a placeholder to stop the bootstrapper from | ||||
|                                 complaining */ | ||||
|            } | ||||
|          } | ||||
| 
 | ||||
|               // This module represents the AngularJS pieces of the application
 | ||||
|               const ng1Module = | ||||
|                   angular | ||||
|                       .module('myExample', []) | ||||
|                       // This is an AngularJS component that will be upgraded
 | ||||
|                       .directive( | ||||
|                           'ng1', | ||||
|                           () => { | ||||
|                             return { | ||||
|                               scope: {title: '='}, | ||||
|                               transclude: true, | ||||
|                               template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)' | ||||
|                             }; | ||||
|                           }) | ||||
|                       // This is wrapping (downgrading) an Angular component to be used in
 | ||||
|                       // AngularJS
 | ||||
|                       .directive('ng2', downgradeComponent({component: Ng2Component})); | ||||
|          // This module represents the AngularJS pieces of the application
 | ||||
|          const ng1Module = | ||||
|              angular | ||||
|                  .module('myExample', []) | ||||
|                  // This is an AngularJS component that will be upgraded
 | ||||
|                  .directive( | ||||
|                      'ng1', | ||||
|                      () => { | ||||
|                        return { | ||||
|                          scope: {title: '='}, | ||||
|                          transclude: true, | ||||
|                          template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)' | ||||
|                        }; | ||||
|                      }) | ||||
|                  // This is wrapping (downgrading) an Angular component to be used in
 | ||||
|                  // AngularJS
 | ||||
|                  .directive('ng2', downgradeComponent({component: Ng2Component})); | ||||
| 
 | ||||
|               // This is the (AngularJS) application bootstrap element
 | ||||
|               // Notice that it is actually a downgraded Angular component
 | ||||
|               const element = html('<ng2 name="World">project</ng2>'); | ||||
|          // This is the (AngularJS) application bootstrap element
 | ||||
|          // Notice that it is actually a downgraded Angular component
 | ||||
|          const element = html('<ng2 name="World">project</ng2>'); | ||||
| 
 | ||||
|               // Let's use a helper function to make this simpler
 | ||||
|               bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { | ||||
|                 expect(multiTrim(element.textContent)) | ||||
|                     .toBe('ng2[ng1[Hello World!](transclude)](project)'); | ||||
|               }); | ||||
|             })); | ||||
|          // Let's use a helper function to make this simpler
 | ||||
|          bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { | ||||
|            expect(multiTrim(element.textContent)) | ||||
|                .toBe('ng2[ng1[Hello World!](transclude)](project)'); | ||||
|          }); | ||||
|        })); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user