mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
refactor(core): Add more detail to NG0750 error message
This adds a bit more context to the NG0750 error message to provide details about which module failed to load when executing the dependencyResolverFn. This can help with debugging a failed lazy load in a defer block.
(cherry picked from commit 8218d2e34a)
This commit is contained in:
parent
05bcc601ac
commit
e583f4c808
2 changed files with 90 additions and 7 deletions
|
|
@ -222,10 +222,12 @@ export function triggerResourceLoading(
|
|||
// Start downloading of defer block dependencies.
|
||||
tDetails.loadingPromise = Promise.allSettled(dependenciesFn()).then((results) => {
|
||||
let failed = false;
|
||||
let failedReason: Error | null = null;
|
||||
const directiveDefs: DirectiveDefList = [];
|
||||
const pipeDefs: PipeDefList = [];
|
||||
|
||||
for (const result of results) {
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const result = results[i];
|
||||
if (result.status === 'fulfilled') {
|
||||
const dependency = result.value;
|
||||
const directiveDef = getComponentDef(dependency) || getDirectiveDef(dependency);
|
||||
|
|
@ -239,6 +241,8 @@ export function triggerResourceLoading(
|
|||
}
|
||||
} else {
|
||||
failed = true;
|
||||
failedReason =
|
||||
result.reason instanceof Error ? result.reason : new Error(String(result.reason));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -248,13 +252,24 @@ export function triggerResourceLoading(
|
|||
|
||||
if (tDetails.errorTmplIndex === null) {
|
||||
const templateLocation = ngDevMode ? getTemplateLocationDetails(lView) : '';
|
||||
const error = new RuntimeError(
|
||||
RuntimeErrorCode.DEFER_LOADING_FAILED,
|
||||
ngDevMode &&
|
||||
let errorMsg: string | false = false;
|
||||
|
||||
if (ngDevMode) {
|
||||
errorMsg =
|
||||
'Loading dependencies for `@defer` block failed, ' +
|
||||
`but no \`@error\` block was configured${templateLocation}. ` +
|
||||
'Consider using the `@error` block to render an error state.',
|
||||
);
|
||||
`but no \`@error\` block was configured${templateLocation}. ` +
|
||||
'Consider using the `@error` block to render an error state.';
|
||||
|
||||
const depsFnStr = tDetails.dependencyResolverFn?.toString() ?? 'unknown';
|
||||
const errorReason = failedReason ? failedReason.message : 'Unknown';
|
||||
errorMsg +=
|
||||
`\n\nAngular tried to invoke the following dependency function (compiler-generated):\n` +
|
||||
`\`\`\`\n${depsFnStr}\n\`\`\`\n` +
|
||||
`but it resulted in the following error:\n\n` +
|
||||
`${errorReason}`;
|
||||
}
|
||||
|
||||
const error = new RuntimeError(RuntimeErrorCode.DEFER_LOADING_FAILED, errorMsg);
|
||||
handleUncaughtError(lView, error);
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1034,6 +1034,74 @@ describe('@defer', () => {
|
|||
expect(reportedErrors[0].message).toContain(`(used in the 'MyCmp' component template)`);
|
||||
});
|
||||
|
||||
it('should include detailed failure info in the error message when no `@error` block is defined', async () => {
|
||||
@Component({
|
||||
selector: 'nested-cmp',
|
||||
template: 'NestedCmp',
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
})
|
||||
class NestedCmp {}
|
||||
|
||||
@Component({
|
||||
selector: 'simple-app',
|
||||
imports: [NestedCmp],
|
||||
template: `
|
||||
@defer (when isVisible) {
|
||||
<nested-cmp />
|
||||
} @loading {
|
||||
Loading...
|
||||
} @placeholder {
|
||||
Placeholder
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.Eager,
|
||||
})
|
||||
class MyCmp {
|
||||
isVisible = false;
|
||||
}
|
||||
|
||||
const failedReason = new Error('Failed to load module X');
|
||||
const deferDepsInterceptor = {
|
||||
intercept() {
|
||||
return () => [new Promise((_, reject) => setTimeout(() => reject(failedReason), 0))];
|
||||
},
|
||||
};
|
||||
|
||||
const reportedErrors: Error[] = [];
|
||||
TestBed.configureTestingModule({
|
||||
rethrowApplicationErrors: false,
|
||||
providers: [
|
||||
{
|
||||
provide: ɵDEFER_BLOCK_DEPENDENCY_INTERCEPTOR,
|
||||
useValue: deferDepsInterceptor,
|
||||
},
|
||||
{
|
||||
provide: ErrorHandler,
|
||||
useClass: class extends ErrorHandler {
|
||||
override handleError(error: Error) {
|
||||
reportedErrors.push(error);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(MyCmp);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.componentInstance.isVisible = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
await allPendingDynamicImports();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(reportedErrors.length).toBe(1);
|
||||
const errorMsg = reportedErrors[0].message;
|
||||
expect(errorMsg).toContain('NG0750');
|
||||
expect(errorMsg).toContain('Angular tried to invoke the following dependency function');
|
||||
expect(errorMsg).toContain('Failed to load module X');
|
||||
});
|
||||
|
||||
it('should not render `@error` block if loaded component has errors', async () => {
|
||||
@Component({
|
||||
selector: 'cmp-with-error',
|
||||
|
|
|
|||
Loading…
Reference in a new issue