Skip to content
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

Closed
wants to merge 4 commits into from
Closed

Fw 1559 #33542

wants to merge 4 commits into from

Conversation

mhevery
Copy link
Contributor

@mhevery mhevery commented Nov 2, 2019

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.io application / infrastructure changes
  • Other... Please describe:

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?

  • Yes
  • No

@mhevery mhevery requested a review from a team as a code owner November 2, 2019 04:13
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);

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 {
Copy link
Member

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.

@mhevery mhevery added action: review The PR is still awaiting reviews from at least one requested reviewer target: major This PR is targeted for the next major release labels Nov 2, 2019
@ngbot ngbot bot added this to the needsTriage milestone Nov 2, 2019
Copy link
Member

@pkozlowski-opensource pkozlowski-opensource left a 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 (

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;
}
) - the way I understand it is that only difference is that we can stop at the first 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!

@mhevery
Copy link
Contributor Author

mhevery commented Nov 5, 2019

@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 applyView and friends into a cursor pattern. We could than just iterate over the cursor as many times as we need to to either add/remove or to retrieve the first item from the list. What do you think?

@@ -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', () => {

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', () => {

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)

@mhevery
Copy link
Contributor Author

mhevery commented Nov 6, 2019

@pkozlowski-opensource took over this one here #33627

@mhevery mhevery closed this Nov 6, 2019
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Dec 7, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: review The PR is still awaiting reviews from at least one requested reviewer cla: yes target: major This PR is targeted for the next major release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants