From cf8770f3ccf56bebb518894ed479a9cb713ab2c2 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Mon, 21 Jan 2019 14:55:37 +0100 Subject: [PATCH] fix(ivy): don't create TNodes for native projectable nodes (#28275) Before this commit we were creating a "fake" TNode for each and every projectable node passed during dynamic component creation. This approach had several problems: - the existing TView structure had to be mutated to accomodate new TNodes and it was very easy to "corrupt" TView / TNode data structures; - TNodes are not really needed to fully support projectable nodes so we were creating objects and updating existing data structures for nothing. This commit changes the approach so we don't create "fake" TNodes for projectable nodes but instead we process projectable nodes directly in the projection instruction. As a result we've got less code, less object allocation and - as a bonus - we fix few bugs where TView / TNode data structures were corrupted when using projectable nodes. PR Close #28275 --- packages/core/src/render3/component_ref.ts | 42 ++---- packages/core/src/render3/instructions.ts | 58 ++++---- packages/core/src/render3/interfaces/node.ts | 12 +- .../core/src/render3/node_manipulation.ts | 87 +++++++---- .../hello_world/bundle.golden_symbols.json | 12 ++ .../bundling/todo/bundle.golden_symbols.json | 12 ++ .../linker/projection_integration_spec.ts | 119 ++++++++++++++- .../test/render3/renderer_factory_spec.ts | 1 - packages/upgrade/test/dynamic/upgrade_spec.ts | 45 +++--- .../integration/downgrade_component_spec.ts | 51 ++++--- .../integration/downgrade_module_spec.ts | 140 +++++++++--------- 11 files changed, 369 insertions(+), 210 deletions(-) diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index be48fabeed..18318a9a64 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -24,11 +24,11 @@ import {assertComponentType} from './assert'; import {LifecycleHooksFeature, createRootComponent, createRootComponentView, createRootContext} from './component'; import {getComponentDef} from './definition'; import {NodeInjector} from './di'; -import {addToViewTree, createLView, createNodeAtIndex, createTView, createViewNode, elementCreate, locateHostElement, refreshDescendantViews} from './instructions'; -import {ComponentDef, RenderFlags} from './interfaces/definition'; -import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType} from './interfaces/node'; -import {RElement, RendererFactory3, domRendererFactory3, isProceduralRenderer} from './interfaces/renderer'; -import {HEADER_OFFSET, LView, LViewFlags, RootContext, TVIEW} from './interfaces/view'; +import {addToViewTree, createLView, createTView, createViewNode, elementCreate, locateHostElement, refreshDescendantViews} from './instructions'; +import {ComponentDef} from './interfaces/definition'; +import {TContainerNode, TElementContainerNode, TElementNode} from './interfaces/node'; +import {RNode, RendererFactory3, domRendererFactory3, isProceduralRenderer} from './interfaces/renderer'; +import {HEADER_OFFSET, LView, LViewFlags, RootContext} from './interfaces/view'; import {enterView, leaveView} from './state'; import {defaultScheduler, getTNode} from './util'; import {createElementRef} from './view_engine_compatibility'; @@ -174,34 +174,12 @@ export class ComponentFactory extends viewEngine_ComponentFactory { tElementNode = getTNode(0, rootLView) as TElementNode; - // Transform the arrays of native nodes into a structure that can be consumed by the - // projection instruction. This is needed to support the reprojection of these nodes. if (projectableNodes) { - let index = 0; - const tView = rootLView[TVIEW]; - const projection: TNode[] = tElementNode.projection = []; - for (let i = 0; i < projectableNodes.length; i++) { - const nodeList = projectableNodes[i]; - let firstTNode: TNode|null = null; - let previousTNode: TNode|null = null; - for (let j = 0; j < nodeList.length; j++) { - if (tView.firstTemplatePass) { - // For dynamically created components such as ComponentRef, we create a new TView for - // each insert. This is not ideal since we should be sharing the TViews. - // Also the logic here should be shared with `component.ts`'s `renderComponent` - // method. - tView.expandoStartIndex++; - tView.blueprint.splice(++index + HEADER_OFFSET, 0, null); - tView.data.splice(index + HEADER_OFFSET, 0, null); - rootLView.splice(index + HEADER_OFFSET, 0, null); - } - const tNode = - createNodeAtIndex(index, TNodeType.Element, nodeList[j] as RElement, null, null); - previousTNode ? (previousTNode.next = tNode) : (firstTNode = tNode); - previousTNode = tNode; - } - projection.push(firstTNode !); - } + // projectable nodes can be passed as array of arrays or an array of iterables (ngUpgrade + // case). Here we do normalize passed data structure to be an array of arrays to avoid + // complex checks down the line. + tElementNode.projection = + projectableNodes.map((nodesforSlot: RNode[]) => { return Array.from(nodesforSlot); }); } // TODO: should LifecycleHooksFeature and other host features be generated by the compiler and diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 38b2541fb7..fbe0a010d2 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -2471,36 +2471,44 @@ export function projection(nodeIndex: number, selectorIndex: number = 0, attrs?: let projectedView = componentView[PARENT] !; let projectionNodeIndex = -1; - while (nodeToProject) { - if (nodeToProject.type === TNodeType.Projection) { - // This node is re-projected, so we must go up the tree to get its projected nodes. - const currentComponentView = findComponentView(projectedView); - const currentComponentHost = currentComponentView[HOST_NODE] as TElementNode; - const firstProjectedNode = - (currentComponentHost.projection as(TNode | null)[])[nodeToProject.projection as number]; + if (Array.isArray(nodeToProject)) { + appendChild(nodeToProject, tProjectionNode, lView); + } else { + while (nodeToProject) { + if (nodeToProject.type === TNodeType.Projection) { + // This node is re-projected, so we must go up the tree to get its projected nodes. + const currentComponentView = findComponentView(projectedView); + const currentComponentHost = currentComponentView[HOST_NODE] as TElementNode; + const firstProjectedNode = (currentComponentHost.projection as( + TNode | null)[])[nodeToProject.projection as number]; - if (firstProjectedNode) { - projectionNodeStack[++projectionNodeIndex] = nodeToProject; - projectionNodeStack[++projectionNodeIndex] = projectedView; + if (firstProjectedNode) { + if (Array.isArray(firstProjectedNode)) { + appendChild(firstProjectedNode, tProjectionNode, lView); + } else { + projectionNodeStack[++projectionNodeIndex] = nodeToProject; + projectionNodeStack[++projectionNodeIndex] = projectedView; - nodeToProject = firstProjectedNode; - projectedView = currentComponentView[PARENT] !; - continue; + nodeToProject = firstProjectedNode; + projectedView = currentComponentView[PARENT] !; + continue; + } + } + } else { + // This flag must be set now or we won't know that this node is projected + // if the nodes are inserted into a container later. + nodeToProject.flags |= TNodeFlags.isProjected; + appendProjectedNode(nodeToProject, tProjectionNode, lView, projectedView); } - } else { - // This flag must be set now or we won't know that this node is projected - // if the nodes are inserted into a container later. - nodeToProject.flags |= TNodeFlags.isProjected; - appendProjectedNode(nodeToProject, tProjectionNode, lView, projectedView); - } - // If we are finished with a list of re-projected nodes, we need to get - // back to the root projection node that was re-projected. - if (nodeToProject.next === null && projectedView !== componentView[PARENT] !) { - projectedView = projectionNodeStack[projectionNodeIndex--] as LView; - nodeToProject = projectionNodeStack[projectionNodeIndex--] as TNode; + // If we are finished with a list of re-projected nodes, we need to get + // back to the root projection node that was re-projected. + if (nodeToProject.next === null && projectedView !== componentView[PARENT] !) { + projectedView = projectionNodeStack[projectionNodeIndex--] as LView; + nodeToProject = projectionNodeStack[projectionNodeIndex--] as TNode; + } + nodeToProject = nodeToProject.next; } - nodeToProject = nodeToProject.next; } } diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index 07a6ed6971..a86ac83727 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {RNode} from './renderer'; import {StylingContext} from './styling'; import {LView, TView} from './view'; @@ -333,8 +334,11 @@ export interface TNode { * `getHost(currentTNode).projection[currentTNode.projection]`. * - When projecting nodes the parent node retrieved may be a `` node, in which case * the process is recursive in nature (not implementation). + * + * If `projection` is of type `RNode[][]` than we have a collection of native nodes passed as + * projectable nodes during dynamic component creation. */ - projection: (TNode|null)[]|number|null; + projection: (TNode|RNode[])[]|number|null; } /** Static data for an element */ @@ -352,10 +356,10 @@ export interface TElementNode extends TNode { /** * If this is a component TNode with projection, this will be an array of projected - * TNodes (see TNode.projection for more info). If it's a regular element node or a - * component without projection, it will be null. + * TNodes or native nodes (see TNode.projection for more info). If it's a regular element node or + * a component without projection, it will be null. */ - projection: (TNode|null)[]|null; + projection: (TNode|RNode[])[]|null; } /** Static data for a text node */ diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 62be5b49b3..79ec137a94 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -9,7 +9,7 @@ import {attachPatchData} from './context_discovery'; import {callHooks} from './hooks'; import {LContainer, NATIVE, VIEWS, unusedValueExportToPlacateAjd as unused1} from './interfaces/container'; -import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; +import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection'; import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer'; import {CLEANUP, CONTAINER_INDEX, FLAGS, HEADER_OFFSET, HOST_NODE, HookData, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; @@ -109,14 +109,22 @@ function walkTNodeTree( const head: TNode|null = (componentHost.projection as(TNode | null)[])[tNode.projection as number]; - // Must store both the TNode and the view because this projection node could be nested - // deeply inside embedded views, and we need to get back down to this particular nested view. - projectionNodeStack[++projectionNodeIndex] = tNode; - projectionNodeStack[++projectionNodeIndex] = currentView !; - if (head) { - currentView = componentView[PARENT] !; - nextTNode = currentView[TVIEW].data[head.index] as TNode; + if (Array.isArray(head)) { + for (let nativeNode of head) { + executeNodeAction(action, renderer, renderParent, nativeNode, tNode, beforeNode); + } + } else { + // Must store both the TNode and the view because this projection node could be nested + // deeply inside embedded views, and we need to get back down to this particular nested + // view. + projectionNodeStack[++projectionNodeIndex] = tNode; + projectionNodeStack[++projectionNodeIndex] = currentView !; + if (head) { + currentView = componentView[PARENT] !; + nextTNode = currentView[TVIEW].data[head.index] as TNode; + } } + } else { // Otherwise, this is a View or an ElementContainer nextTNode = tNode.child; @@ -548,6 +556,23 @@ export function nativeInsertBefore( } } +function nativeAppendChild(renderer: Renderer3, parent: RElement, child: RNode): void { + if (isProceduralRenderer(renderer)) { + renderer.appendChild(parent, child); + } else { + parent.appendChild(child); + } +} + +function nativeAppendOrInsertBefore( + renderer: Renderer3, parent: RElement, child: RNode, beforeNode: RNode | null) { + if (beforeNode) { + nativeInsertBefore(renderer, parent, child, beforeNode); + } else { + nativeAppendChild(renderer, parent, child); + } +} + /** * Removes a native child node from a given native parent node. */ @@ -572,35 +597,47 @@ export function nativeNextSibling(renderer: Renderer3, node: RNode): RNode|null } /** - * Appends the `child` element to the `parent`. + * Finds a native "anchor" node for cases where we can't append a native child directly + * (`appendChild`) and need to use a reference (anchor) node for the `insertBefore` operation. + * @param parentTNode + * @param lView + */ +function getNativeAnchorNode(parentTNode: TNode, lView: LView): RNode|null { + if (parentTNode.type === TNodeType.View) { + const lContainer = getLContainer(parentTNode as TViewNode, lView) !; + const views = lContainer[VIEWS]; + const index = views.indexOf(lView); + return getBeforeNodeForView(index, views, lContainer[NATIVE]); + } else if ( + parentTNode.type === TNodeType.ElementContainer || + parentTNode.type === TNodeType.IcuContainer) { + return getNativeByTNode(parentTNode, lView); + } + return null; +} + +/** + * Appends the `child` native node (or a collection of nodes) to the `parent`. * * The element insertion might be delayed {@link canInsertNativeNode}. * - * @param childEl The child that should be appended + * @param childEl The native child (or children) that should be appended * @param childTNode The TNode of the child element * @param currentView The current LView * @returns Whether or not the child was appended */ -export function appendChild(childEl: RNode, childTNode: TNode, currentView: LView): void { +export function appendChild(childEl: RNode | RNode[], childTNode: TNode, currentView: LView): void { const renderParent = getRenderParent(childTNode, currentView); if (renderParent != null) { const renderer = currentView[RENDERER]; const parentTNode: TNode = childTNode.parent || currentView[HOST_NODE] !; - - if (parentTNode.type === TNodeType.View) { - const lContainer = getLContainer(parentTNode as TViewNode, currentView) !; - const views = lContainer[VIEWS]; - const index = views.indexOf(currentView); - nativeInsertBefore( - renderer, renderParent, childEl, getBeforeNodeForView(index, views, lContainer[NATIVE])); - } else if ( - parentTNode.type === TNodeType.ElementContainer || - parentTNode.type === TNodeType.IcuContainer) { - const anchorNode = getNativeByTNode(parentTNode, currentView); - nativeInsertBefore(renderer, renderParent, childEl, anchorNode); + const anchorNode = getNativeAnchorNode(parentTNode, currentView); + if (Array.isArray(childEl)) { + for (let nativeNode of childEl) { + nativeAppendOrInsertBefore(renderer, renderParent, nativeNode, anchorNode); + } } else { - isProceduralRenderer(renderer) ? renderer.appendChild(renderParent, childEl) : - renderParent.appendChild(childEl); + nativeAppendOrInsertBefore(renderer, renderParent, childEl, anchorNode); } } } diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 28212a2e4d..bff40dcc6f 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -119,6 +119,9 @@ { "name": "__self" }, + { + "name": "__values" + }, { "name": "__window" }, @@ -254,6 +257,9 @@ { "name": "getLViewChild" }, + { + "name": "getNativeAnchorNode" + }, { "name": "getNativeByTNode" }, @@ -344,6 +350,12 @@ { "name": "namespaceHTML" }, + { + "name": "nativeAppendChild" + }, + { + "name": "nativeAppendOrInsertBefore" + }, { "name": "nativeInsertBefore" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 38ec613887..442a8dc109 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -275,6 +275,9 @@ { "name": "__spread" }, + { + "name": "__values" + }, { "name": "__window" }, @@ -704,6 +707,9 @@ { "name": "getMultiStartIndex" }, + { + "name": "getNativeAnchorNode" + }, { "name": "getNativeByIndex" }, @@ -983,6 +989,12 @@ { "name": "namespaceHTML" }, + { + "name": "nativeAppendChild" + }, + { + "name": "nativeAppendOrInsertBefore" + }, { "name": "nativeInsertBefore" }, diff --git a/packages/core/test/linker/projection_integration_spec.ts b/packages/core/test/linker/projection_integration_spec.ts index 91a48c28b4..1b52edfafc 100644 --- a/packages/core/test/linker/projection_integration_spec.ts +++ b/packages/core/test/linker/projection_integration_spec.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, Directive, ElementRef, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, Injector, NgModule, OnInit, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation} from '@angular/core'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser/src/dom/debug/by'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {expect} from '@angular/platform-browser/testing/src/matchers'; @@ -611,6 +611,121 @@ describe('projection', () => { main.detectChanges(); expect(main.nativeElement).toHaveText('(, D)'); }); + + describe('projectable nodes', () => { + + @Component({selector: 'test', template: ''}) + class TestComponent { + constructor(public cfr: ComponentFactoryResolver) {} + } + + @Component({selector: 'with-content', template: ''}) + class WithContentCmpt { + @ViewChild('ref') directiveRef: any; + } + + @Component({selector: 're-project', template: ''}) + class ReProjectCmpt { + } + + @Directive({selector: '[insert]'}) + class InsertTplRef implements OnInit { + constructor(private _vcRef: ViewContainerRef, private _tplRef: TemplateRef<{}>) {} + + ngOnInit() { this._vcRef.createEmbeddedView(this._tplRef); } + } + + @Directive({selector: '[delayedInsert]', exportAs: 'delayedInsert'}) + class DelayedInsertTplRef { + constructor(public vc: ViewContainerRef, public templateRef: TemplateRef) {} + show() { this.vc.createEmbeddedView(this.templateRef); } + hide() { this.vc.clear(); } + } + + @NgModule({ + declarations: [WithContentCmpt, InsertTplRef, DelayedInsertTplRef, ReProjectCmpt], + entryComponents: [WithContentCmpt] + }) + class TestModule { + } + + let fixture: ComponentFixture; + + function createCmptInstance( + tpl: string, projectableNodes: any[][]): ComponentRef { + TestBed.configureTestingModule({declarations: [TestComponent], imports: [TestModule]}); + TestBed.overrideTemplate(WithContentCmpt, tpl); + + fixture = TestBed.createComponent(TestComponent); + const cfr = fixture.componentInstance.cfr; + const cf = cfr.resolveComponentFactory(WithContentCmpt); + const cmptRef = cf.create(Injector.NULL, projectableNodes); + + cmptRef.changeDetectorRef.detectChanges(); + + return cmptRef; + } + + it('should pass nodes to the default ng-content without selectors', () => { + const cmptRef = createCmptInstance( + '
()
', [[document.createTextNode('A')]]); + expect(cmptRef.location.nativeElement).toHaveText('(A)'); + }); + + it('should pass nodes to the default ng-content at the root', () => { + const cmptRef = + createCmptInstance('', [[document.createTextNode('A')]]); + expect(cmptRef.location.nativeElement).toHaveText('A'); + }); + + it('should pass nodes to multiple ng-content tags', () => { + const cmptRef = createCmptInstance( + 'A:()B:()C:()', + [ + [document.createTextNode('A')], [document.createTextNode('B')], + [document.createTextNode('C')] + ]); + expect(cmptRef.location.nativeElement).toHaveText('A:(A)B:(B)C:(C)'); + }); + + it('should pass nodes to the default ng-content inside ng-container', () => { + const cmptRef = createCmptInstance( + 'A()C', + [[document.createTextNode('B')]]); + expect(cmptRef.location.nativeElement).toHaveText('A(B)C'); + }); + + it('should pass nodes to the default ng-content inside an embedded view', () => { + const cmptRef = createCmptInstance( + 'A()C', + [[document.createTextNode('B')]]); + expect(cmptRef.location.nativeElement).toHaveText('A(B)C'); + }); + + it('should pass nodes to the default ng-content inside a delayed embedded view', () => { + const cmptRef = createCmptInstance( + 'A([])C', + [[document.createTextNode('B')]]); + expect(cmptRef.location.nativeElement).toHaveText('A()C'); + + const delayedInsert = cmptRef.instance.directiveRef as DelayedInsertTplRef; + + delayedInsert.show(); + cmptRef.changeDetectorRef.detectChanges(); + expect(cmptRef.location.nativeElement).toHaveText('A([B])C'); + + delayedInsert.hide(); + cmptRef.changeDetectorRef.detectChanges(); + expect(cmptRef.location.nativeElement).toHaveText('A()C'); + }); + + it('should re-project at the root', () => { + const cmptRef = createCmptInstance( + 'A[()]C', + [[document.createTextNode('B')]]); + expect(cmptRef.location.nativeElement).toHaveText('A[(B)]C'); + }); + }); }); @Component({selector: 'main', template: ''}) diff --git a/packages/core/test/render3/renderer_factory_spec.ts b/packages/core/test/render3/renderer_factory_spec.ts index 42fc1f4102..b32da94ffa 100644 --- a/packages/core/test/render3/renderer_factory_spec.ts +++ b/packages/core/test/render3/renderer_factory_spec.ts @@ -13,7 +13,6 @@ import {RendererType2, ViewEncapsulation} from '../../src/core'; import {defineComponent} from '../../src/render3/index'; import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, listener, text, tick} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; -import {createRendererType2} from '../../src/view/index'; import {getAnimationRendererFactory2, getRendererFactory2} from './imported_renderer2'; import {TemplateFixture, containerEl, document, renderComponent, renderToHtml, toHtml} from './render_util'; diff --git a/packages/upgrade/test/dynamic/upgrade_spec.ts b/packages/upgrade/test/dynamic/upgrade_spec.ts index 11160bd777..32e8626355 100644 --- a/packages/upgrade/test/dynamic/upgrade_spec.ts +++ b/packages/upgrade/test/dynamic/upgrade_spec.ts @@ -10,7 +10,6 @@ import {ChangeDetectorRef, Component, EventEmitter, Input, NO_ERRORS_SCHEMA, NgM import {async, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; -import {fixmeIvy} from '@angular/private/testing'; import * as angular from '@angular/upgrade/src/common/angular1'; import {$EXCEPTION_HANDLER} from '@angular/upgrade/src/common/constants'; import {UpgradeAdapter, UpgradeAdapterRef} from '@angular/upgrade/src/dynamic/upgrade_adapter'; @@ -3098,33 +3097,31 @@ withEachNg1Version(() => { }); })); - 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', []); + it('should respect hierarchical dependency injection for ng2', async(() => { + const ng1Module = angular.module('ng1', []); - @Component( - {selector: 'ng2-parent', template: `ng2-parent()`}) - class Ng2Parent { - } - @Component({selector: 'ng2-child', template: `ng2-child`}) - class Ng2Child { - constructor(parent: Ng2Parent) {} - } + @Component({selector: 'ng2-parent', template: `ng2-parent()`}) + class Ng2Parent { + } + @Component({selector: 'ng2-child', template: `ng2-child`}) + class Ng2Child { + constructor(parent: Ng2Parent) {} + } - @NgModule({declarations: [Ng2Parent, Ng2Child], imports: [BrowserModule]}) - class Ng2Module { - } + @NgModule({declarations: [Ng2Parent, Ng2Child], imports: [BrowserModule]}) + class Ng2Module { + } - const element = html(''); + const element = html(''); - const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module); - ng1Module.directive('ng2Parent', adapter.downgradeNg2Component(Ng2Parent)) - .directive('ng2Child', adapter.downgradeNg2Component(Ng2Child)); - adapter.bootstrap(element, ['ng1']).ready((ref) => { - expect(document.body.textContent).toEqual('ng2-parent(ng2-child)'); - ref.dispose(); - }); - })); + const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module); + ng1Module.directive('ng2Parent', adapter.downgradeNg2Component(Ng2Parent)) + .directive('ng2Child', adapter.downgradeNg2Component(Ng2Child)); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(document.body.textContent).toEqual('ng2-parent(ng2-child)'); + ref.dispose(); + }); + })); }); describe('testability', () => { diff --git a/packages/upgrade/test/static/integration/downgrade_component_spec.ts b/packages/upgrade/test/static/integration/downgrade_component_spec.ts index 5ca71fd7fc..fe78f26f03 100644 --- a/packages/upgrade/test/static/integration/downgrade_component_spec.ts +++ b/packages/upgrade/test/static/integration/downgrade_component_spec.ts @@ -705,37 +705,36 @@ withEachNg1Version(() => { }); })); - 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()'}) - class ParentComponent { - } + it('should respect hierarchical dependency injection for ng2', async(() => { + @Component({selector: 'parent', template: 'parent()'}) + class ParentComponent { + } - @Component({selector: 'child', template: 'child'}) - class ChildComponent { - constructor(parent: ParentComponent) {} - } + @Component({selector: 'child', template: 'child'}) + class ChildComponent { + constructor(parent: ParentComponent) {} + } - @NgModule({ - declarations: [ParentComponent, ChildComponent], - entryComponents: [ParentComponent, ChildComponent], - imports: [BrowserModule, UpgradeModule] - }) - class Ng2Module { - ngDoBootstrap() {} - } + @NgModule({ + declarations: [ParentComponent, ChildComponent], + entryComponents: [ParentComponent, ChildComponent], + imports: [BrowserModule, UpgradeModule] + }) + class Ng2Module { + ngDoBootstrap() {} + } - const ng1Module = - angular.module('ng1', []) - .directive('parent', downgradeComponent({component: ParentComponent})) - .directive('child', downgradeComponent({component: ChildComponent})); + const ng1Module = + angular.module('ng1', []) + .directive('parent', downgradeComponent({component: ParentComponent})) + .directive('child', downgradeComponent({component: ChildComponent})); - const element = html(''); + const element = html(''); - bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { - expect(multiTrim(document.body.textContent)).toBe('parent(child)'); - }); - })); + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { + expect(multiTrim(document.body.textContent)).toBe('parent(child)'); + }); + })); fixmeIvy( 'FW-717: Injector on lazy loaded components are not the same as their NgModule\'s injector') diff --git a/packages/upgrade/test/static/integration/downgrade_module_spec.ts b/packages/upgrade/test/static/integration/downgrade_module_spec.ts index 4924cc8b73..663158ae11 100644 --- a/packages/upgrade/test/static/integration/downgrade_module_spec.ts +++ b/packages/upgrade/test/static/integration/downgrade_module_spec.ts @@ -330,93 +330,91 @@ withEachNg1Version(() => { expect(multiTrim(element.children[1].textContent)).toBe('Counter:1'); })); - fixmeIvy('FW-873: projected component injector hierarchy not wired up correctly') - .it('should correctly traverse the injector tree of downgraded components', async(() => { - @Component({ - selector: 'ng2A', - template: 'ng2A()', - providers: [ - {provide: 'FOO', useValue: 'CompA-foo'}, - {provide: 'BAR', useValue: 'CompA-bar'}, - ], - }) - class Ng2ComponentA { - } + it('should correctly traverse the injector tree of downgraded components', async(() => { + @Component({ + selector: 'ng2A', + template: 'ng2A()', + providers: [ + {provide: 'FOO', useValue: 'CompA-foo'}, + {provide: 'BAR', useValue: 'CompA-bar'}, + ], + }) + class Ng2ComponentA { + } - @Component({ - selector: 'ng2B', - template: ` + @Component({ + selector: 'ng2B', + template: ` FOO:{{ foo }} BAR:{{ bar }} BAZ:{{ baz }} QUX:{{ qux }} `, - providers: [ - {provide: 'FOO', useValue: 'CompB-foo'}, - ], - }) - class Ng2ComponentB { - constructor( - @Inject('FOO') public foo: string, @Inject('BAR') public bar: string, - @Inject('BAZ') public baz: string, @Inject('QUX') public qux: string) {} - } + providers: [ + {provide: 'FOO', useValue: 'CompB-foo'}, + ], + }) + class Ng2ComponentB { + constructor( + @Inject('FOO') public foo: string, @Inject('BAR') public bar: string, + @Inject('BAZ') public baz: string, @Inject('QUX') public qux: string) {} + } - @NgModule({ - declarations: [Ng2ComponentA, Ng2ComponentB], - entryComponents: [Ng2ComponentA, Ng2ComponentB], - imports: [BrowserModule], - providers: [ - {provide: 'FOO', useValue: 'Mod-foo'}, - {provide: 'BAR', useValue: 'Mod-bar'}, - {provide: 'BAZ', useValue: 'Mod-baz'}, - ], - }) - class Ng2Module { - ngDoBootstrap() {} - } + @NgModule({ + declarations: [Ng2ComponentA, Ng2ComponentB], + entryComponents: [Ng2ComponentA, Ng2ComponentB], + imports: [BrowserModule], + providers: [ + {provide: 'FOO', useValue: 'Mod-foo'}, + {provide: 'BAR', useValue: 'Mod-bar'}, + {provide: 'BAZ', useValue: 'Mod-baz'}, + ], + }) + class Ng2Module { + ngDoBootstrap() {} + } - const bootstrapFn = (extraProviders: StaticProvider[]) => { - const platformRef = getPlatform() || platformBrowserDynamic([ - ...extraProviders, - {provide: 'FOO', useValue: 'Plat-foo'}, - {provide: 'BAR', useValue: 'Plat-bar'}, - {provide: 'BAZ', useValue: 'Plat-baz'}, - {provide: 'QUX', useValue: 'Plat-qux'}, - ]); - return platformRef.bootstrapModule(Ng2Module); - }; + const bootstrapFn = (extraProviders: StaticProvider[]) => { + const platformRef = getPlatform() || platformBrowserDynamic([ + ...extraProviders, + {provide: 'FOO', useValue: 'Plat-foo'}, + {provide: 'BAR', useValue: 'Plat-bar'}, + {provide: 'BAZ', useValue: 'Plat-baz'}, + {provide: 'QUX', useValue: 'Plat-qux'}, + ]); + return platformRef.bootstrapModule(Ng2Module); + }; - const downMod = downgradeModule(bootstrapFn); - const ng1Module = - angular.module('ng1', [downMod]) - .directive( - 'ng2A', downgradeComponent({component: Ng2ComponentA, propagateDigest})) - .directive( - 'ng2B', - downgradeComponent({component: Ng2ComponentB, propagateDigest})); + const downMod = downgradeModule(bootstrapFn); + const ng1Module = + angular.module('ng1', [downMod]) + .directive( + 'ng2A', downgradeComponent({component: Ng2ComponentA, propagateDigest})) + .directive( + 'ng2B', downgradeComponent({component: Ng2ComponentB, propagateDigest})); - const element = html(` + const element = html(` `); - const $injector = angular.bootstrap(element, [ng1Module.name]); - const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService; + const $injector = angular.bootstrap(element, [ng1Module.name]); + const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService; - // Wait for the module to be bootstrapped. - setTimeout(() => { - expect(multiTrim(element.textContent)).toBe('ng2A()'); + // Wait for the module to be bootstrapped. + setTimeout(() => { + expect(multiTrim(element.textContent)).toBe('ng2A()'); - // Nested component B. - $rootScope.$apply('showB1 = true'); - expect(multiTrim(element.children[0].textContent)) - .toBe('ng2A( FOO:CompB-foo BAR:CompA-bar BAZ:Mod-baz QUX:Plat-qux )'); + // Nested component B. + $rootScope.$apply('showB1 = true'); + expect(multiTrim(element.children[0].textContent)) + .toBe('ng2A( FOO:CompB-foo BAR:CompA-bar BAZ:Mod-baz QUX:Plat-qux )'); - // Standalone component B. - $rootScope.$apply('showB2 = true'); - expect(multiTrim(element.children[1].textContent)) - .toBe('FOO:CompB-foo BAR:Mod-bar BAZ:Mod-baz QUX:Plat-qux'); - }); - })); + // Standalone component B. + $rootScope.$apply('showB2 = true'); + expect(multiTrim(element.children[1].textContent)) + .toBe('FOO:CompB-foo BAR:Mod-bar BAZ:Mod-baz QUX:Plat-qux'); + }); + })); fixmeIvy('FW-873: projected component injector hierarchy not wired up correctly') .it('should correctly traverse the injector tree of downgraded components (from different modules)',