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鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
bug(core): Error updating transplanted view count with a null parent view #38619
Comments
Ah, I think this could happen if the view was previously attached, then manually detached, then you call angular/packages/core/src/render3/node_manipulation.ts Lines 299 to 304 in 90cec40
but if you still have a handle on the embedded view and call detectChanges , it looks like this could cause the error to happen. I hadn't really considered that use-case. I think it's likely that re-attaching that embedded view would also not be handled correctly because trackMovedView doesn't update the parent counters for a transplanted view that is already dirty.
A reproduction would be really great! I'll need one in order to test that a fix works. I might be able to make one from this information, but that might only be possible if my assumptions about why this broke are correct. |
Reproduction of error, though the scenario is totally bonkers so it would be nice to get a more real-world scenario: https://stackblitz.com/edit/dynamic-views-qflhmd?file=src%2Fapp%2Fapp.component.ts
What happens in the reproduction is as follows:
The most obvious fix to me would be to also clear |
@atscott The entire code is as follows: export class AppComponent {
@ViewChild(TemplateRef, {static: true}) templateRef: TemplateRef<any>;
viewRef: EmbeddedViewRef<any>;
constructor(public vref: ViewContainerRef, public cdr: ChangeDetectorRef){
}
ngOnInit(){
this.viewRef = this.templateRef.createEmbeddedView(null);
console.log('inserting...')
this.vref.insert(this.viewRef);
this.cdr.detectChanges();
console.log('detaching...')
this.vref.detach();
console.log('detecting changes...')
this.viewRef.detectChanges();
}
}
After looking at the source code a bit more, I think this makes sense. And I think it would be a one line fix editing the function detachMovedView(declarationContainer: LContainer, lView: LView) {
ngDevMode && assertLContainer(declarationContainer);
ngDevMode &&
assertDefined(
declarationContainer[MOVED_VIEWS],
'A projected view should belong to a non-empty projected views collection');
const movedViews = declarationContainer[MOVED_VIEWS]!;
const declarationViewIndex = movedViews.indexOf(lView);
const insertionLContainer = lView[PARENT] as LContainer;
ngDevMode && assertLContainer(insertionLContainer);
// If the view was marked for refresh but then detached before it was checked (where the flag
// would be cleared and the counter decremented), we need to decrement the view counter here
// instead.
if (lView[FLAGS] & LViewFlags.RefreshTransplantedView) {
// lView[FLAGS] &= ~LViewFlags.RefreshTransplantedView; // <-- ADD THIS LINE -->
updateTransplantedViewCount(insertionLContainer, -1);
}
movedViews.splice(declarationViewIndex, 1);
} |
Yep, I believe that's the right fix as well! Do you want to send a PR that I can review? Your test can be added to |
I'm a total noob when it comes to testing in Angular, and still am somewhat new to Git, but I'll give it a go. |
The `RefreshTransplantedView` flag is used to indicate that the view or one of its children is transplanted and dirty, so it should still be refreshed as part of change detection. This flag is set on the transplanted view itself as well setting a counter on as its parents. When a transplanted view is detached and still has this flag, it means it got detached before it was refreshed. This can happen for "backwards references" or transplanted views that are inserted at a location that was already checked. In this case, we should decrement the parent counters _and_ clear the flag on the detached view so it's not seen as "transplanted" anymore (it is detached and has no parent counters to adjust). fixes angular#38619
The `RefreshTransplantedView` flag is used to indicate that the view or one of its children is transplanted and dirty, so it should still be refreshed as part of change detection. This flag is set on the transplanted view itself as well setting a counter on as its parents. When a transplanted view is detached and still has this flag, it means it got detached before it was refreshed. This can happen for "backwards references" or transplanted views that are inserted at a location that was already checked. In this case, we should decrement the parent counters _and_ clear the flag on the detached view so it's not seen as "transplanted" anymore (it is detached and has no parent counters to adjust). fixes #38619 PR Close #38768
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. |
馃悶 bug report
Affected Package
The issue is caused by package @angular/coreIs this a regression?
Yes, the previous version in which this bug was not present was: I don't know. The error is occurring with Ivy and not View Engine.Description
I am working with dynamic views and decided to add an
embeddedViewRef.detectChanges()
call in my app which unexpectedly spit out the following error:core.js:4197 ERROR TypeError: Cannot read property '5' of null at updateTransplantedViewCount (core.js:2425) at refreshView (core.js:7268) at detectChangesInternal (core.js:8506) at ViewRef.detectChanges (core.js:9864)
This error is occurring in the first line of the
updateTransplantedViewCount
function:angular/packages/core/src/render3/util/view_utils.ts
Lines 198 to 199 in 90cec40
due to the
LContainer
input variable beingnull
.And
updateTransplantedViewCount
is being called insiderefreshView
here:angular/packages/core/src/render3/instructions/shared.ts
Lines 520 to 527 in 90cec40
...so
lView[PARENT]
here is resolving tonull
.The obvious first reaction is "well, let's add a
null
check to one of these functions"...but I'm not familiar enough with the Ivy internals to know iflView[PARENT]
being null alongsideLViewFlags.RefreshTransplantedView
being set represents some larger architecture issue (i.e. this scenario should be "impossible").For a little more context, I am calling
embeddedViewRef.detectChanges()
on a non-destroyed embedded view ref that is not attached to a view container ref (was previously attached, but had been detached).馃敩 Minimal Reproduction
Currently struggling to make a repro. If I can't get it to repro, then I'll upload a snapshot of my code.馃實 Your Environment
Angular Version:
The text was updated successfully, but these errors were encountered: