fix(core): incorrect error reported when trying to re-create view which had an error during creation (#43005)
Currently if a view throws an error during creation mode, we mark it as `incompleteFirstPass` so that we can try to recover later. The recovery is only possible inside component views. The problem is that when this was introduced, I forgot to flip the `firstCreatePass` when an error is thrown which meant that calling `renderView` on the same component again is allowed. It will eventually hit an assertion which can be confusing for the end user. This issue only manifests itself when rendering views "manually" through `ViewContainerRef` (e.g. using `NgIf`). These changes flip the `firstCreatePass` back to false on errors so that trying to re-render the same view will throw an error which is consistent to the one that broke the view during creation. Fixes #41383. PR Close #43005
This commit is contained in:
parent
f99ec27596
commit
8628826535
|
@ -30,7 +30,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 1190,
|
||||
"main-es2015": 136546,
|
||||
"main-es2015": 137055,
|
||||
"polyfills-es2015": 37641
|
||||
}
|
||||
}
|
||||
|
|
|
@ -336,6 +336,7 @@ export function renderView<T>(tView: TView, lView: LView, context: T): void {
|
|||
// an error, mark the view as corrupted so we can try to recover.
|
||||
if (tView.firstCreatePass) {
|
||||
tView.incompleteFirstPass = true;
|
||||
tView.firstCreatePass = false;
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {ChangeDetectorRef, Component, ComponentFactoryResolver, Directive, EmbeddedViewRef, Injector, Input, NgModule, TemplateRef, ViewChild, ViewContainerRef, ViewRef} from '@angular/core';
|
||||
import {ChangeDetectorRef, Component, ComponentFactoryResolver, Directive, EmbeddedViewRef, Injectable, Injector, Input, NgModule, TemplateRef, ViewChild, ViewContainerRef, ViewRef} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {onlyInIvy} from '@angular/private/testing';
|
||||
|
@ -886,5 +886,46 @@ describe('view insertion', () => {
|
|||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.textContent).toContain('2');
|
||||
});
|
||||
|
||||
it('should consistently report errors raised by createEmbeddedView', () => {
|
||||
// Intentionally hasn't been added to `providers` so that it throws a DI error.
|
||||
@Injectable()
|
||||
class DoesNotExist {
|
||||
}
|
||||
|
||||
@Directive({selector: 'dir'})
|
||||
class Dir {
|
||||
constructor(willCauseError: DoesNotExist) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ng-template #broken>
|
||||
<dir></dir>
|
||||
</ng-template>
|
||||
`,
|
||||
})
|
||||
class App {
|
||||
@ViewChild('broken') template !: TemplateRef<unknown>;
|
||||
|
||||
constructor(private _viewContainerRef: ViewContainerRef) {}
|
||||
|
||||
insertTemplate() {
|
||||
this._viewContainerRef.createEmbeddedView(this.template);
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [App, Dir]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
const tryRender = () => {
|
||||
fixture.componentInstance.insertTemplate();
|
||||
fixture.detectChanges();
|
||||
};
|
||||
fixture.detectChanges();
|
||||
|
||||
// We try to render the same template twice to ensure that we get consistent error messages.
|
||||
expect(tryRender).toThrowError(/No provider for DoesNotExist/);
|
||||
expect(tryRender).toThrowError(/No provider for DoesNotExist/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue