Skip to content

Commit d47cf58

Browse files
dgp1130thePunderWoman
authored andcommitted
refactor(core): update ng.getDirectiveMetadata to support Wiz and ACX (angular#60475)
This allows `ng.getDirectiveMetadata` to be implemented by Wiz and ACX with subtly different shapes to match the nuances of those frameworks. Existing usage of `{Component,Directive}DebugMetadata` was moved over to `Angular{Component,Directive}DebugMetadata` as appropriate, since the implementation of `ng` in `@angular/core` is specific to Angular. Only the types support Wiz and ACX. I opted to merge `ComponentDebugMetadata` and `DirectiveDebugMetadata` into a single type of all the frameworks including both components and directives (recall that components extend directives). The reasoning for this is because Wiz does not support directives (you can kind of think of "Wiz Directive" as an abstract class extended by "Wiz Components"). I felt that a `DirectiveDebugMetadata` containing only Angular and ACX types would be a bit of a trap and lead to bugs when used. It's safer to just have the single type containing all the possible results from `ng.getDirectiveMetadata`. I also chose to leave the `ng` type as is internally, since `@angular/core` implements a specific concrete version of it narrowed to Angular types. Separately I defined an expanded `FrameworkAgnosticGlobalUtils` which redefines `ng.getDirectiveMetadata` to include Wiz and ACX. We want this type to exist in the Angular GitHub repo so it can be referenced as a common primitive across all three frameworks. This is sufficient for now, however longer term we will likely want to actually manually define the function types in this framework-agnostic interface and make Angular's version properly implement it rather than extend and overwrite Angular's type. PR Close angular#60475
1 parent 765ba1e commit d47cf58

File tree

9 files changed

+123
-50
lines changed

9 files changed

+123
-50
lines changed

devtools/projects/ng-devtools-backend/src/lib/component-tree.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import type {
1515
Injector,
1616
Type,
1717
ValueProvider,
18-
ɵComponentDebugMetadata as ComponentDebugMetadata,
18+
ɵAngularComponentDebugMetadata as AngularComponentDebugMetadata,
1919
ɵProviderRecord as ProviderRecord,
2020
} from '@angular/core';
2121
import {
@@ -219,7 +219,7 @@ const enum DirectiveMetadataKey {
219219
// the method directly interacts with the directive/component definition.
220220
const getDirectiveMetadata = (dir: any): DirectiveMetadata => {
221221
const getMetadata = ngDebugClient().getDirectiveMetadata!;
222-
const metadata = getMetadata?.(dir) as ComponentDebugMetadata;
222+
const metadata = getMetadata?.(dir) as AngularComponentDebugMetadata;
223223
if (metadata) {
224224
return {
225225
inputs: metadata.inputs,

devtools/projects/ng-devtools-backend/src/lib/ng-debug-api/ng-debug-api.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,24 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import type {ɵGlobalDevModeUtils as GlobalDevModeUtils} from '@angular/core';
9+
import type {ɵFrameworkAgnosticGlobalUtils as GlobalUtils} from '@angular/core';
1010

1111
/**
1212
* Returns a handle to window.ng APIs (global angular debugging).
1313
*
1414
* @returns window.ng
1515
*/
16-
export const ngDebugClient = () => (window as any).ng as Partial<GlobalDevModeUtils['ng']>;
16+
export const ngDebugClient = () => (window as any).ng as Partial<GlobalUtils>;
1717

1818
/**
1919
* Type guard that checks whether a given debug API is supported within window.ng
2020
*
2121
* @returns whether the ng object includes the given debug API
2222
*/
23-
export function ngDebugApiIsSupported<
24-
T extends Partial<GlobalDevModeUtils['ng']>,
25-
K extends keyof T,
26-
>(ng: T, api: K): ng is T & Record<K, NonNullable<T[K]>> {
23+
export function ngDebugApiIsSupported<T extends Partial<GlobalUtils>, K extends keyof T>(
24+
ng: T,
25+
api: K,
26+
): ng is T & Record<K, NonNullable<T[K]>> {
2727
return typeof ng[api] === 'function';
2828
}
2929

goldens/public-api/core/global_utils.api.md

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,7 @@
88
export function applyChanges(component: {}): void;
99

1010
// @public
11-
export interface ComponentDebugMetadata extends DirectiveDebugMetadata {
12-
// (undocumented)
13-
changeDetection: ChangeDetectionStrategy;
14-
// (undocumented)
15-
encapsulation: ViewEncapsulation;
16-
}
17-
18-
// @public
19-
export interface DirectiveDebugMetadata {
20-
// (undocumented)
21-
inputs: Record<string, string>;
22-
// (undocumented)
23-
outputs: Record<string, string>;
24-
}
11+
export type DirectiveDebugMetadata = AngularDirectiveDebugMetadata | AcxDirectiveDebugMetadata | AngularComponentDebugMetadata | AcxComponentDebugMetadata | WizComponentDebugMetadata;
2512

2613
// @public
2714
export function getComponent<T>(element: Element): T | null;
@@ -30,7 +17,7 @@ export function getComponent<T>(element: Element): T | null;
3017
export function getContext<T extends {}>(element: Element): T | null;
3118

3219
// @public
33-
export function getDirectiveMetadata(directiveOrComponentInstance: any): ComponentDebugMetadata | DirectiveDebugMetadata | null;
20+
export function getDirectiveMetadata(directiveOrComponentInstance: any): AngularComponentDebugMetadata | AngularDirectiveDebugMetadata | null;
3421

3522
// @public
3623
export function getDirectives(node: Node): {}[];

packages/core/src/core_render3_private_export.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ export {
3939
export {
4040
AttributeMarker as ɵAttributeMarker,
4141
ComponentDef as ɵComponentDef,
42-
ComponentDebugMetadata as ɵComponentDebugMetadata,
4342
ComponentFactory as ɵRender3ComponentFactory,
4443
ComponentRef as ɵRender3ComponentRef,
4544
ComponentType as ɵComponentType,
@@ -62,6 +61,16 @@ export {
6261
ɵDeferBlockDependencyInterceptor,
6362
ɵDEFER_BLOCK_DEPENDENCY_INTERCEPTOR,
6463
ɵDEFER_BLOCK_CONFIG,
64+
Framework as ɵFramework,
65+
BaseDirectiveDebugMetadata as ɵBaseDirectiveDebugMetadata,
66+
AngularDirectiveDebugMetadata as ɵAngularDirectiveDebugMetadata,
67+
AngularComponentDebugMetadata as ɵAngularComponentDebugMetadata,
68+
AcxChangeDetectionStrategy as ɵAcxChangeDetectionStrategy,
69+
AcxViewEncapsulation as ɵAcxViewEncapsulation,
70+
AcxDirectiveDebugMetadata as ɵAcxDirectiveDebugMetadata,
71+
AcxComponentDebugMetadata as ɵAcxComponentDebugMetadata,
72+
WizComponentDebugMetadata as ɵWizComponentDebugMetadata,
73+
DirectiveDebugMetadata as ɵDirectiveDebugMetadata,
6574
ɵɵadvance,
6675
ɵɵattribute,
6776
ɵɵattributeInterpolate1,
@@ -282,7 +291,10 @@ export {
282291
export {compilePipe as ɵcompilePipe} from './render3/jit/pipe';
283292
export {isNgModule as ɵisNgModule} from './render3/jit/util';
284293
export {Profiler as ɵProfiler, ProfilerEvent as ɵProfilerEvent} from './render3/profiler_types';
285-
export {GlobalDevModeUtils as ɵGlobalDevModeUtils} from './render3/util/global_utils';
294+
export {
295+
FrameworkAgnosticGlobalUtils as ɵFrameworkAgnosticGlobalUtils,
296+
GlobalDevModeUtils as ɵGlobalDevModeUtils,
297+
} from './render3/util/global_utils';
286298
export {
287299
ViewRef as ɵViewRef,
288300
isViewDirty as ɵisViewDirty,

packages/core/src/render3/global_utils_api.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
export {applyChanges} from './util/change_detection_utils';
1919
export {
20-
ComponentDebugMetadata,
2120
DirectiveDebugMetadata,
2221
getComponent,
2322
getContext,

packages/core/src/render3/index.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,15 @@ import {
3030
} from './interfaces/public_definitions';
3131
import {ɵɵsetComponentScope, ɵɵsetNgModuleScope} from './scope';
3232
import {
33-
ComponentDebugMetadata,
33+
Framework,
34+
BaseDirectiveDebugMetadata,
35+
AngularDirectiveDebugMetadata,
36+
AngularComponentDebugMetadata,
37+
AcxChangeDetectionStrategy,
38+
AcxViewEncapsulation,
39+
AcxDirectiveDebugMetadata,
40+
AcxComponentDebugMetadata,
41+
WizComponentDebugMetadata,
3442
DirectiveDebugMetadata,
3543
getComponent,
3644
getDirectiveMetadata,
@@ -223,10 +231,18 @@ export {ɵsetClassDebugInfo} from './debug/set_debug_info';
223231
export {ɵɵreplaceMetadata} from './hmr';
224232

225233
export {
226-
ComponentDebugMetadata,
227234
ComponentDef,
228235
ComponentTemplate,
229236
ComponentType,
237+
Framework,
238+
BaseDirectiveDebugMetadata,
239+
AngularDirectiveDebugMetadata,
240+
AngularComponentDebugMetadata,
241+
AcxChangeDetectionStrategy,
242+
AcxViewEncapsulation,
243+
AcxDirectiveDebugMetadata,
244+
AcxComponentDebugMetadata,
245+
WizComponentDebugMetadata,
230246
DirectiveDebugMetadata,
231247
DirectiveDef,
232248
DirectiveType,

packages/core/src/render3/util/discovery_utils.ts

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -224,34 +224,79 @@ export function getDirectives(node: Node): {}[] {
224224
return context.directives === null ? [] : [...context.directives];
225225
}
226226

227+
/** The framework used to author a particular application or component. */
228+
export enum Framework {
229+
Angular = 'angular',
230+
ACX = 'acx',
231+
Wiz = 'wiz',
232+
}
233+
234+
/** Metadata common to directives from all frameworks. */
235+
export interface BaseDirectiveDebugMetadata {
236+
name?: string;
237+
framework?: Framework;
238+
}
239+
227240
/**
228-
* Partial metadata for a given directive instance.
229-
* This information might be useful for debugging purposes or tooling.
230-
* Currently only `inputs` and `outputs` metadata is available.
241+
* Partial metadata for a given Angular directive instance.
231242
*
232243
* @publicApi
233244
*/
234-
export interface DirectiveDebugMetadata {
245+
export interface AngularDirectiveDebugMetadata extends BaseDirectiveDebugMetadata {
246+
framework?: Framework.Angular; // Optional for backwards compatibility.
235247
inputs: Record<string, string>;
236248
outputs: Record<string, string>;
237249
}
238250

239251
/**
240-
* Partial metadata for a given component instance.
241-
* This information might be useful for debugging purposes or tooling.
242-
* Currently the following fields are available:
243-
* - inputs
244-
* - outputs
245-
* - encapsulation
246-
* - changeDetection
252+
* Partial metadata for a given Angular component instance.
247253
*
248254
* @publicApi
249255
*/
250-
export interface ComponentDebugMetadata extends DirectiveDebugMetadata {
256+
export interface AngularComponentDebugMetadata extends AngularDirectiveDebugMetadata {
251257
encapsulation: ViewEncapsulation;
252258
changeDetection: ChangeDetectionStrategy;
253259
}
254260

261+
/** ACX change detection strategies. */
262+
export enum AcxChangeDetectionStrategy {
263+
Default = 0,
264+
OnPush = 1,
265+
}
266+
267+
/** ACX view encapsulation modes. */
268+
export enum AcxViewEncapsulation {
269+
Emulated = 0,
270+
None = 1,
271+
}
272+
273+
/** Partial metadata for a given ACX directive instance. */
274+
export interface AcxDirectiveDebugMetadata extends BaseDirectiveDebugMetadata {
275+
framework: Framework.ACX;
276+
inputs: Record<string, string>;
277+
outputs: Record<string, string>;
278+
}
279+
280+
/** Partial metadata for a given ACX component instance. */
281+
export interface AcxComponentDebugMetadata extends AcxDirectiveDebugMetadata {
282+
changeDetection: AcxChangeDetectionStrategy;
283+
encapsulation: AcxViewEncapsulation;
284+
}
285+
286+
/** Partial metadata for a given Wiz component instance. */
287+
export interface WizComponentDebugMetadata extends BaseDirectiveDebugMetadata {
288+
framework: Framework.Wiz;
289+
props: Record<string, string>;
290+
}
291+
292+
/** All potential debug metadata types across all frameworks. */
293+
export type DirectiveDebugMetadata =
294+
| AngularDirectiveDebugMetadata
295+
| AcxDirectiveDebugMetadata
296+
| AngularComponentDebugMetadata
297+
| AcxComponentDebugMetadata
298+
| WizComponentDebugMetadata;
299+
255300
/**
256301
* Returns the debug (partial) metadata for a particular directive or component instance.
257302
* The function accepts an instance of a directive or component and returns the corresponding
@@ -264,7 +309,7 @@ export interface ComponentDebugMetadata extends DirectiveDebugMetadata {
264309
*/
265310
export function getDirectiveMetadata(
266311
directiveOrComponentInstance: any,
267-
): ComponentDebugMetadata | DirectiveDebugMetadata | null {
312+
): AngularComponentDebugMetadata | AngularDirectiveDebugMetadata | null {
268313
const {constructor} = directiveOrComponentInstance;
269314
if (!constructor) {
270315
throw new Error('Unable to find the instance constructor');
@@ -478,7 +523,7 @@ function assertDomElement(value: any) {
478523
* mappings for backwards compatibility.
479524
*/
480525
function extractInputDebugMetadata<T>(inputs: DirectiveDef<T>['inputs']) {
481-
const res: DirectiveDebugMetadata['inputs'] = {};
526+
const res: AngularDirectiveDebugMetadata['inputs'] = {};
482527

483528
for (const key in inputs) {
484529
if (inputs.hasOwnProperty(key)) {

packages/core/src/render3/util/global_utils.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {isSignal} from '../reactivity/api';
1414
import {applyChanges} from './change_detection_utils';
1515
import {getDeferBlocks} from './defer';
1616
import {
17+
DirectiveDebugMetadata,
1718
getComponent,
1819
getContext,
1920
getDirectiveMetadata,
@@ -125,6 +126,21 @@ export function publishGlobalUtil<K extends CoreGlobalUtilsFunctions>(
125126
publishUtil(name, fn);
126127
}
127128

129+
/**
130+
* Defines the framework-agnostic `ng` global type, not just the `@angular/core` implementation.
131+
*
132+
* `typeof globalUtilsFunctions` is specifically the `@angular/core` implementation, so we
133+
* overwrite some properties to make them more framework-agnostic. Longer term, we should define
134+
* the `ng` global type as an interface implemented by `globalUtilsFunctions` rather than a type
135+
* derived from it.
136+
*/
137+
export type FrameworkAgnosticGlobalUtils = Omit<
138+
typeof globalUtilsFunctions,
139+
'getDirectiveMetadata'
140+
> & {
141+
getDirectiveMetadata(directiveOrComponentInstance: any): DirectiveDebugMetadata | null;
142+
};
143+
128144
/**
129145
* Publishes the given function to `window.ng` from package other than @angular/core
130146
* So that it can be used from the browser console when an application is not in production.

packages/core/test/acceptance/discover_utils_spec.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {ComponentFixture, TestBed} from '../../testing';
2424
import {getLContext} from '../../src/render3/context_discovery';
2525
import {getHostElement} from '../../src/render3/index';
2626
import {
27-
ComponentDebugMetadata,
27+
AngularComponentDebugMetadata,
2828
getComponent,
2929
getComponentLView,
3030
getContext,
@@ -383,13 +383,11 @@ describe('discovery utils', () => {
383383

384384
describe('getDirectiveMetadata', () => {
385385
it('should work with components', () => {
386-
const metadata = getDirectiveMetadata(myApp);
387-
expect(metadata!.inputs).toEqual({a: 'b'});
388-
expect(metadata!.outputs).toEqual({c: 'd'});
389-
expect((metadata as ComponentDebugMetadata).changeDetection).toBe(
390-
ChangeDetectionStrategy.Default,
391-
);
392-
expect((metadata as ComponentDebugMetadata).encapsulation).toBe(ViewEncapsulation.None);
386+
const metadata = getDirectiveMetadata(myApp)! as AngularComponentDebugMetadata;
387+
expect(metadata.inputs).toEqual({a: 'b'});
388+
expect(metadata.outputs).toEqual({c: 'd'});
389+
expect(metadata.changeDetection).toBe(ChangeDetectionStrategy.Default);
390+
expect(metadata.encapsulation).toBe(ViewEncapsulation.None);
393391
});
394392

395393
it('should work with directives', () => {

0 commit comments

Comments
 (0)