angular: Improve the `The pipe could not be found` error message

Which @angular/* package(s) are relevant/releated to the feature request?

core

Description

Currently Angular throws the following error message at runtime when a pipe can not be found:

The pipe `AsyncPipe` could not be found!

However there is no information on which component has this problem, which makes debugging harder. We should consider adding component class name to the error message.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 11
  • Comments: 23 (22 by maintainers)

Most upvoted comments

@rohan-pednekar please see my comment to your PR: https://github.com/angular/angular/pull/44081#discussion_r743914851. Here is the change that should take care of the mentioned issue:

   if (tView.firstCreatePass) {
-    pipeDef = getPipeDef(pipeName, tView.pipeRegistry);
+    // The `getPipeDef` throws if a pipe with a given name is not found
+    // (so we use non-null assertion below).
+    pipeDef = getPipeDef(pipeName, tView.pipeRegistry)!;

I have tried formatting all the source code. But, I am seeing changes in many files that I haven’t even touched. I used the following command.

You could target changed files only using this command:

yarn ng-dev format changed

More info can be found at https://github.com/angular/angular/blob/master/docs/DEVELOPER.md#formatting-your-source-code.

@rohan-pednekar I think you should add tests to the packages/core/test/acceptance/pipe_spec.ts file only, there is no need to add them to the render3 directory (we would migrate the remaining tests from there to the acceptance folder eventually). Once the tests are added, you can use the following command to run them:

yarn bazel test --config=ivy packages/core/test/acceptance

You could also focus a particular suite or a test using fdescribe or fit.

Please let me know if that worked.

Thank you.

@rohan-pednekar I’ve looked at the code and here is a possible implementation to get the necessary info and include it into the error message:

diff --git a/packages/core/src/render3/pipe.ts b/packages/core/src/render3/pipe.ts
index e3bce4680c..cd56f02d53 100644
--- a/packages/core/src/render3/pipe.ts
+++ b/packages/core/src/render3/pipe.ts
@@ -13,7 +13,7 @@ import {setIncludeViewProviders} from './di';
 import {RuntimeError, RuntimeErrorCode} from './error_code';
 import {store, ɵɵdirectiveInject} from './instructions/all';
 import {PipeDef, PipeDefList} from './interfaces/definition';
-import {HEADER_OFFSET, LView, TVIEW} from './interfaces/view';
+import {CONTEXT, DECLARATION_COMPONENT_VIEW, HEADER_OFFSET, LView, TVIEW} from './interfaces/view';
 import {pureFunction1Internal, pureFunction2Internal, pureFunction3Internal, pureFunction4Internal, pureFunctionVInternal} from './pure_function';
 import {getBindingRoot, getLView, getTView} from './state';
 import {load} from './util/view_utils';
@@ -78,7 +78,15 @@ function getPipeDef(name: string, registry: PipeDefList|null): PipeDef<any> {
       }
     }
   }
-  throw new RuntimeError(RuntimeErrorCode.PIPE_NOT_FOUND, `The pipe '${name}' could not be found!`);
+  let message = '';
+  if (ngDevMode) {
+    const lView = getLView();
+    const declarationLView = lView[DECLARATION_COMPONENT_VIEW];
+    const context = declarationLView[CONTEXT];
+    const component = context ? ` in the '${context.constructor.name}' component` : '';
+    message = `The pipe '${name}' could not be found${component}!`;
+  }
+  throw new RuntimeError(RuntimeErrorCode.PIPE_NOT_FOUND, message);
 }

The idea is that we get a hold of an internal representation of a component view, where a given pipe was declared (in component’s template) and look at the context (which refers to the component class instance itself). We also do that inside the if (ngDevMode) { ... } check, so that we tree-shake away this code from production bundles (so it’s a dev-mode-only code).

If you want to put together a PR - feel free to use the code above. The PR should also have sufficient test coverage, including a few important cases (based on the extra logic from the fix):

  • when a pipe is used in a component: {{ value | missingPipe }}
  • when a pipe is inside an inline template: <ng-container *ngIf="true">{{ value | missingPipe }}</ng-container>
  • when a pipe is inside a projected content: <my-comp>{{ value | missingPipe }}</my-comp>
  • when a pipe is inside a projected content in an inline template:<my-comp><ng-container *ngIf="true">{{ value | missingPipe }}</ng-container></my-comp>
  • when a pipe is used in a binding in a component: <div [title]="value | missingPipe"></div>
  • when a pipe is used inside a structural directive in a component: <div *ngIf="isVisible | missingPipe"></div>

You can find existing test suites for pipes logic in the packages/core/test/acceptance/pipe_spec.ts file.

Please let us know if you have any questions.

Thank you.