perf(core): optimize getDirectives (#41525)

This commit introduces the following optimizations:

1. We return an empty array for text nodes in `getDirectives` because
Angular does not support attaching logic to them. This optimization
improves performance of `getDirectives` significantly because text nodes
often result in expensive calls to `loadLContext` since we can't resolve
it from a parent node.
1. `getDirectives` now calls `loadLContext` with second argument `false`
so it doesn't throw an error. This brings another significant
improvement because prevents the VM from deoptimizing calls.

BREAKING CHANGE:

Previously the `ng.getDirectives` function threw an error in case a
given DOM node had no Angular context associated with it (for example
if a function was called for a DOM element outside of an Angular app).
This behavior was inconsistent with other debugging utilities under `ng`
namespace, which handled this situation without raising an exception.
Now calling the `ng.getDirectives` function for such DOM nodes would
result in an empty array returned from that function.

PR Close #41525
This commit is contained in:
mgechev 2021-04-12 14:01:26 -07:00 committed by Zach Arend
parent a07f303708
commit f7e391a912
2 changed files with 23 additions and 7 deletions

View File

@ -172,7 +172,7 @@ export function getInjectionTokens(element: Element): any[] {
}
/**
* Retrieves directive instances associated with a given DOM element. Does not include
* Retrieves directive instances associated with a given DOM node. Does not include
* component instances.
*
* @usageNotes
@ -184,21 +184,35 @@ export function getInjectionTokens(element: Element): any[] {
* </my-app>
* ```
* Calling `getDirectives` on `<button>` will return an array with an instance of the `MyButton`
* directive that is associated with the DOM element.
* directive that is associated with the DOM node.
*
* Calling `getDirectives` on `<my-comp>` will return an empty array.
*
* @param element DOM element for which to get the directives.
* @returns Array of directives associated with the element.
* @param node DOM node for which to get the directives.
* @returns Array of directives associated with the node.
*
* @publicApi
* @globalApi ng
*/
export function getDirectives(element: Element): {}[] {
const context = loadLContext(element)!;
export function getDirectives(node: Node): {}[] {
// Skip text nodes because we can't have directives associated with them.
if (node instanceof Text) {
return [];
}
const context = loadLContext(node, false);
if (context === null) {
return [];
}
const lView = context.lView;
const tView = lView[TVIEW];
const nodeIndex = context.nodeIndex;
if (!tView?.data[nodeIndex]) {
return [];
}
if (context.directives === undefined) {
context.directives = getDirectivesAtNodeIndex(context.nodeIndex, context.lView, false);
context.directives = getDirectivesAtNodeIndex(nodeIndex, lView, false);
}
// The `directives` in this case are a named array called `LComponentView`. Clone the

View File

@ -386,9 +386,11 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils deprecated', () =>
it('should not throw if it cannot find LContext', () => {
let result: any;
expect(() => {
result = getDirectives(document.createElement('div'));
}).not.toThrow();
expect(result).toEqual([]);
});
});