New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fw 1559 #33542
Fw 1559 #33542
Conversation
Under some circumstances inserting `LView` into `LContainer` would get inserted in a wrong DOM location resulting in incorrect DOM node order. These two use cases were corrected: - Inserting in front of empty `LView` ``` <ng-template></ng-template>` ``` - Inserting in front of nested `Lview` ``` <ng-template> <ng-template [ngIf]="true">Nested</ng-template> </ng-template>` ```
} | ||
return getNativeByTNodeOrNull(tViewNodeChild, lView); | ||
return getNativeByTNode(tNode, lView); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that it should be recursive in nature - what might happen is that a child of ElementContainer / IcuContainer is a container which might have views etc.
const childLContainer = lView[tNode.index] as LContainer; | ||
ngDevMode && assertLContainer(childLContainer); | ||
return getFirstRNodeFromLViewInLContainer(childLContainer, 0); | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the projection handled here, but it might show up at the root of a view, right? Plus, projection might have a container at the root so it is recursive in nature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mhevery I did the first, quick pass on this PR and I think that there are more cases to cover here. Essentially the function you are modifying is almost the same as collecting root nodes (
angular/packages/core/src/render3/view_ref.ts
Lines 305 to 348 in ac9d044
function collectNativeNodes(lView: LView, tNode: TNode | null, result: any[]): any[] { | |
while (tNode !== null) { | |
ngDevMode && assertNodeOfPossibleTypes( | |
tNode, TNodeType.Element, TNodeType.Container, TNodeType.Projection, | |
TNodeType.ElementContainer, TNodeType.IcuContainer); | |
const lNode = lView[tNode.index]; | |
if (lNode !== null) { | |
result.push(unwrapRNode(lNode)); | |
} | |
// A given lNode can represent either a native node or a LContainer (when it is a host of a | |
// ViewContainerRef). When we find a LContainer we need to descend into it to collect root nodes | |
// from the views in this container. | |
if (isLContainer(lNode)) { | |
for (let i = CONTAINER_HEADER_OFFSET; i < lNode.length; i++) { | |
const lViewInAContainer = lNode[i]; | |
const lViewFirstChildTNode = lViewInAContainer[TVIEW].firstChild; | |
if (lViewFirstChildTNode !== null) { | |
collectNativeNodes(lViewInAContainer, lViewFirstChildTNode, result); | |
} | |
} | |
} | |
const tNodeType = tNode.type; | |
if (tNodeType === TNodeType.ElementContainer || tNodeType === TNodeType.IcuContainer) { | |
collectNativeNodes(lView, tNode.child, result); | |
} else if (tNodeType === TNodeType.Projection) { | |
const componentView = findComponentView(lView); | |
const componentHost = componentView[T_HOST] as TElementNode; | |
const parentView = getLViewParent(componentView); | |
let currentProjectedNode: TNode|null = | |
(componentHost.projection as(TNode | null)[])[tNode.projection as number]; | |
while (currentProjectedNode !== null && parentView !== null) { | |
result.push(getNativeByTNode(currentProjectedNode, parentView)); | |
currentProjectedNode = currentProjectedNode.next; | |
} | |
} | |
tNode = tNode.next; | |
} | |
return result; | |
} |
More generally, I think that we can fold all those root node traversal routines we've got into one - we've got already applyView
- we would have to "just" add a new tree walk action (collect).
I think that, in this PR, we should:
- add more tests / asserts for all the node types (need to cover the same cases as in
collectNativeNodes
linked above); - add / modify implementation that covers all the cases.
Then, with tests in place and in a separate PR, refactor code to have the root nodes tree walking in one place only (should drop quite a bit of code duplication!). I've spent quite a bit of time poking around the code so happy to discuss this in more details!
@pkozlowski-opensource I have refactored it to use existing code in 9465a38, but as you can see the aesthetics of it are not great. :-( My current thinking is that we should refactor |
@@ -249,4 +277,150 @@ describe('view insertion', () => { | |||
expect(fixture.debugElement.queryAll(By.css('div.dynamic')).length).toBe(4); | |||
}); | |||
}); | |||
|
|||
describe('regression', () => { | |||
it('should allow inserting of all different types', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we split this test into several ones (one test for each case, similarly to what I did in #33493? )? It is super-hard to follow-as is.
@@ -249,4 +277,150 @@ describe('view insertion', () => { | |||
expect(fixture.debugElement.queryAll(By.css('div.dynamic')).length).toBe(4); | |||
}); | |||
}); | |||
|
|||
describe('regression', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nits regarding naming:
- regression -> non-regression? but more generally, I would have sth more descriptive, like "should allow inserting views before views with different node types";
- "should allow inserting of all different types" - the essence of those tests is that we want to insert before a view with specific nodes layout and it should be reflected in the name.
Generally speaking, I would love to have this test split into multiple ones (see below)
@pkozlowski-opensource took over this one here #33627 |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
https://angular-team.atlassian.net/browse/FW-1559
What is the new behavior?
Does this PR introduce a breaking change?