Skip to content

Commit 4988451

Browse files
sheikalthafthePunderWoman
authored andcommitted
refactor(devtools): use signal apis in injection and router tree (angular#57047)
Refactor the injection and router tree components to use signal apis, in future we can make the components onPush and zoneless PR Close angular#57047
1 parent d7864e0 commit 4988451

12 files changed

+199
-165
lines changed

devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection/resolution-path.component.ts

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

9-
import {AfterViewInit, Component, ElementRef, Input, OnDestroy, ViewChild} from '@angular/core';
9+
import {
10+
afterNextRender,
11+
Component,
12+
computed,
13+
effect,
14+
ElementRef,
15+
input,
16+
OnDestroy,
17+
viewChild,
18+
} from '@angular/core';
1019
import {SerializedInjector} from 'protocol';
1120

1221
import {InjectorTreeNode, InjectorTreeVisualizer} from './injector-tree-visualizer';
@@ -29,17 +38,17 @@ import {InjectorTreeNode, InjectorTreeVisualizer} from './injector-tree-visualiz
2938
],
3039
standalone: true,
3140
})
32-
export class ResolutionPathComponent implements OnDestroy, AfterViewInit {
33-
@ViewChild('svgContainer', {static: true}) private svgContainer!: ElementRef;
34-
@ViewChild('mainGroup', {static: true}) private g!: ElementRef;
41+
export class ResolutionPathComponent implements OnDestroy {
42+
private svgContainer = viewChild.required<ElementRef>('svgContainer');
43+
private g = viewChild.required<ElementRef>('mainGroup');
3544

36-
@Input() orientation: 'horizontal' | 'vertical' = 'horizontal';
45+
readonly orientation = input<'horizontal' | 'vertical'>('horizontal');
3746

3847
private injectorTree!: InjectorTreeVisualizer;
39-
private pathNode!: InjectorTreeNode;
4048

41-
@Input()
42-
set path(path: SerializedInjector[]) {
49+
readonly path = input<SerializedInjector[]>([]);
50+
private readonly pathNode = computed(() => {
51+
const path = this.path();
4352
const serializedInjectors = path.slice().reverse();
4453
const injectorTreeNodes: InjectorTreeNode[] = [];
4554

@@ -57,30 +66,35 @@ export class ResolutionPathComponent implements OnDestroy, AfterViewInit {
5766
}
5867
}
5968

60-
this.pathNode = injectorTreeNodes[0];
61-
this.injectorTree?.render?.(this.pathNode);
62-
}
69+
return injectorTreeNodes[0];
70+
});
6371

64-
ngOnInit(): void {
65-
this.injectorTree = new InjectorTreeVisualizer(
66-
this.svgContainer.nativeElement,
67-
this.g.nativeElement,
68-
{
69-
orientation: this.orientation,
70-
},
71-
);
72+
constructor() {
73+
afterNextRender({
74+
read: () => {
75+
this.injectorTree = new InjectorTreeVisualizer(
76+
this.svgContainer().nativeElement,
77+
this.g().nativeElement,
78+
{
79+
orientation: this.orientation(),
80+
},
81+
);
7282

73-
if (this.pathNode) {
74-
this.injectorTree.render(this.pathNode);
75-
}
83+
if (this.pathNode()) {
84+
this.injectorTree.render(this.pathNode());
85+
}
86+
87+
this.injectorTree.onNodeClick((_, node) => {
88+
this.injectorTree.snapToNode(node);
89+
});
7690

77-
this.injectorTree.onNodeClick((_, node) => {
78-
this.injectorTree.snapToNode(node);
91+
this.injectorTree.snapToRoot(0.7);
92+
},
7993
});
80-
}
8194

82-
ngAfterViewInit(): void {
83-
this.injectorTree.snapToRoot(0.7);
95+
effect(() => {
96+
this.injectorTree?.render?.(this.pathNode());
97+
});
8498
}
8599

86100
ngOnDestroy(): void {
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
<div class="meta-data-container">
2-
@if (viewEncapsulation) {
2+
@if (viewEncapsulation()) {
33
<a
44
mat-button
55
color="primary"
66
href="https://angular.dev/api/core/ViewEncapsulation"
77
target="_blank"
88
>
9-
View Encapsulation: <span class="meta-data">{{ viewEncapsulation }}</span>
9+
View Encapsulation: <span class="meta-data">{{ viewEncapsulation() }}</span>
1010
</a>
1111
}
12-
@if (changeDetectionStrategy) {
12+
@if (changeDetectionStrategy()) {
1313
<a
1414
mat-button
1515
color="primary"
1616
href="https://angular.dev/api/core/ChangeDetectionStrategy"
1717
target="_blank"
1818
>
19-
Change Detection Strategy: <span class="meta-data">{{ changeDetectionStrategy }}</span>
19+
Change Detection Strategy: <span class="meta-data">{{ changeDetectionStrategy() }}</span>
2020
</a>
2121
}
2222
</div>

devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/component-metadata.component.ts

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

9-
import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
9+
import {ChangeDetectionStrategy, Component, computed, inject, input} from '@angular/core';
1010
import {ComponentType} from 'protocol';
1111

12-
import {DirectivePropertyResolver} from '../property-resolver/directive-property-resolver';
1312
import {ElementPropertyResolver} from '../property-resolver/element-property-resolver';
1413
import {MatButton} from '@angular/material/button';
1514

@@ -22,29 +21,30 @@ import {MatButton} from '@angular/material/button';
2221
imports: [MatButton],
2322
})
2423
export class ComponentMetadataComponent {
25-
@Input({required: true}) currentSelectedComponent!: ComponentType;
24+
readonly currentSelectedComponent = input.required<ComponentType>();
2625

27-
constructor(private _nestedProps: ElementPropertyResolver) {}
26+
private _nestedProps = inject(ElementPropertyResolver);
2827

2928
viewEncapsulationModes = ['Emulated', 'Native', 'None', 'ShadowDom'];
3029

31-
get controller(): DirectivePropertyResolver | undefined {
32-
if (!this.currentSelectedComponent) {
30+
readonly controller = computed(() => {
31+
const comp = this.currentSelectedComponent();
32+
if (!comp) {
3333
return;
3434
}
35-
return this._nestedProps.getDirectiveController(this.currentSelectedComponent.name);
36-
}
35+
return this._nestedProps.getDirectiveController(comp.name);
36+
});
3737

38-
get viewEncapsulation(): string | undefined {
39-
const encapsulationIndex = this?.controller?.directiveViewEncapsulation;
38+
readonly viewEncapsulation = computed(() => {
39+
const encapsulationIndex = this.controller()?.directiveViewEncapsulation;
4040
if (encapsulationIndex !== undefined) {
4141
return this.viewEncapsulationModes[encapsulationIndex];
4242
}
4343
return undefined;
44-
}
44+
});
4545

46-
get changeDetectionStrategy(): string | undefined {
47-
const onPush = this?.controller?.directiveHasOnPushStrategy;
46+
readonly changeDetectionStrategy = computed(() => {
47+
const onPush = this.controller()?.directiveHasOnPushStrategy;
4848
return onPush ? 'OnPush' : onPush !== undefined ? 'Default' : undefined;
49-
}
49+
});
5050
}

devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header.component.html

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
1-
@if (currentSelectedElement) {
2-
@if (currentSelectedElement.component) {
1+
@if (currentSelectedElement()) {
2+
@let comp = currentSelectedElement().component;
3+
@if (comp) {
34
<mat-accordion class="property-tab-header">
45
<div>
56
<mat-expansion-panel [hideToggle]="true">
67
<mat-expansion-panel-header collapsedHeight="32px" expandedHeight="32px">
78
<mat-panel-title>
89
<div class="element-header">
9-
<div class="component-name">{{ currentSelectedElement.component.name }}</div>
10+
<div class="component-name">{{ comp.name }}</div>
1011
</div>
1112
</mat-panel-title>
1213
</mat-expansion-panel-header>
13-
<ng-component-metadata [currentSelectedComponent]="currentSelectedElement.component"></ng-component-metadata>
14+
<ng-component-metadata [currentSelectedComponent]="comp"></ng-component-metadata>
1415
</mat-expansion-panel>
1516
</div>
1617
</mat-accordion>
1718
} @else {
1819
<div class="element-header">
19-
<div class="element-name">{{ currentSelectedElement.element }}</div>
20+
<div class="element-name">{{ currentSelectedElement().element }}</div>
2021
</div>
2122
}
2223
} @else {

devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header.component.ts

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

9-
import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
9+
import {ChangeDetectionStrategy, Component, input} from '@angular/core';
1010

1111
import {IndexedNode} from '../directive-forest/index-forest';
1212
import {ComponentMetadataComponent} from './component-metadata.component';
@@ -21,6 +21,5 @@ import {MatExpansionModule} from '@angular/material/expansion';
2121
imports: [MatExpansionModule, ComponentMetadataComponent],
2222
})
2323
export class PropertyTabHeaderComponent {
24-
@Input({required: true}) currentSelectedElement!: IndexedNode;
25-
@Input() currentDirectives: string[] | undefined;
24+
currentSelectedElement = input.required<IndexedNode>();
2625
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<ng-property-tab-header [currentSelectedElement]="currentSelectedElement">
1+
<ng-property-tab-header [currentSelectedElement]="currentSelectedElement()">
22
</ng-property-tab-header>
3-
<ng-property-tab-body (inspect)="inspect.emit($event)" (viewSource)="viewSource.emit($event)" [currentSelectedElement]="currentSelectedElement">
3+
<ng-property-tab-body (inspect)="inspect.emit($event)" (viewSource)="viewSource.emit($event)" [currentSelectedElement]="currentSelectedElement()">
44
</ng-property-tab-body>

devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab.component.ts

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

9-
import {Component, EventEmitter, Input, Output} from '@angular/core';
9+
import {Component, input, output} from '@angular/core';
1010
import {DirectivePosition} from 'protocol';
1111

1212
import {IndexedNode} from '../directive-forest/index-forest';
@@ -21,7 +21,7 @@ import {PropertyTabHeaderComponent} from './property-tab-header.component';
2121
imports: [PropertyTabHeaderComponent, PropertyTabBodyComponent],
2222
})
2323
export class PropertyTabComponent {
24-
@Input({required: true}) currentSelectedElement!: IndexedNode;
25-
@Output() viewSource = new EventEmitter<string>();
26-
@Output() inspect = new EventEmitter<{node: FlatNode; directivePosition: DirectivePosition}>();
24+
readonly currentSelectedElement = input.required<IndexedNode>();
25+
readonly viewSource = output<string>();
26+
readonly inspect = output<{node: FlatNode; directivePosition: DirectivePosition}>();
2727
}

devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-providers.component.ts

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

9-
import {Component, computed, inject, Input, signal} from '@angular/core';
9+
import {Component, computed, inject, input, signal} from '@angular/core';
1010
import {MatOption} from '@angular/material/core';
1111
import {MatFormField, MatLabel} from '@angular/material/form-field';
1212
import {MatIcon} from '@angular/material/icon';
@@ -19,8 +19,8 @@ import {Events, MessageBus, SerializedInjector, SerializedProviderRecord} from '
1919
@Component({
2020
selector: 'ng-injector-providers',
2121
template: `
22-
<h1>Providers for {{ injector?.name }}</h1>
23-
@if (injector) {
22+
<h1>Providers for {{ injector()?.name }}</h1>
23+
@if (injector()) {
2424
<div class="injector-providers">
2525
<mat-form-field appearance="fill" class="form-field-spacer">
2626
<mat-label>Search by token</mat-label>
@@ -42,8 +42,8 @@ import {Events, MessageBus, SerializedInjector, SerializedProviderRecord} from '
4242
}
4343
</mat-select>
4444
</mat-form-field>
45-
@if (providers.length > 0) {
46-
<table mat-table [dataSource]="providers" class="mat-elevation-z4">
45+
@if (visibleProviders().length > 0) {
46+
<table mat-table [dataSource]="visibleProviders()" class="mat-elevation-z4">
4747
<ng-container matColumnDef="token">
4848
<th mat-header-cell *matHeaderCellDef><h3 class="column-title">Token</h3></th>
4949
<td mat-cell *matCellDef="let provider">{{ provider.token }}</td>
@@ -155,12 +155,12 @@ import {Events, MessageBus, SerializedInjector, SerializedProviderRecord} from '
155155
],
156156
})
157157
export class InjectorProvidersComponent {
158-
@Input({required: true}) injector!: SerializedInjector;
159-
@Input() providers: SerializedProviderRecord[] = [];
158+
readonly injector = input.required<SerializedInjector>();
159+
readonly providers = input<SerializedProviderRecord[]>([]);
160160

161-
searchToken = signal('');
162-
searchType = signal('');
163-
visibleProviders = computed(() => {
161+
readonly searchToken = signal('');
162+
readonly searchType = signal('');
163+
readonly visibleProviders = computed(() => {
164164
const searchToken = this.searchToken().toLowerCase();
165165
const searchType = this.searchType();
166166

@@ -169,7 +169,7 @@ export class InjectorProvidersComponent {
169169
predicates.push((provider) => provider.token.toLowerCase().includes(searchToken));
170170
searchType && predicates.push((provider) => provider.type === searchType);
171171

172-
return this.providers.filter((provider) =>
172+
return this.providers().filter((provider) =>
173173
predicates.every((predicate) => predicate(provider)),
174174
);
175175
});
@@ -184,21 +184,15 @@ export class InjectorProvidersComponent {
184184

185185
providerTypes = Object.keys(this.providerTypeToLabel);
186186

187-
messageBus = inject(MessageBus<Events>);
187+
messageBus = inject<MessageBus<Events>>(MessageBus);
188188

189189
select(row: SerializedProviderRecord) {
190-
this.messageBus.emit('logProvider', [
191-
{
192-
id: this.injector.id,
193-
type: this.injector.type,
194-
name: this.injector.name,
195-
},
196-
row,
197-
]);
190+
const {id, type, name} = this.injector();
191+
this.messageBus.emit('logProvider', [{id, type, name}, row]);
198192
}
199193

200194
get displayedColumns(): string[] {
201-
if (this.injector?.type === 'element') {
195+
if (this.injector()?.type === 'element') {
202196
return ['token', 'type', 'isViewProvider', 'log'];
203197
}
204198
return ['token', 'type', 'log'];

devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-tree.component.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
@if (!diDebugAPIsAvailable) {
1+
@if (!diDebugAPIsAvailable()) {
22
<p class="not-supported">
33
This feature is only available on version 17.0.0 or higher of Angular.
44
</p>
55
}
6-
<as-split [class.hidden]="!diDebugAPIsAvailable" unit="pixel" direction="vertical" [gutterSize]="9" [disabled]="true">
6+
<as-split [class.hidden]="!diDebugAPIsAvailable()" unit="pixel" direction="vertical" [gutterSize]="9" [disabled]="true">
77
<as-split-area size="50">
88
<mat-checkbox
99
(change)="toggleHideInjectorsWithNoProviders()"
@@ -52,9 +52,9 @@ <h2>
5252
</as-split-area>
5353
</as-split>
5454
</as-split-area>
55-
@if (selectedNode && providers.length > 0) {
55+
@if (selectedNode() && providers().length > 0) {
5656
<as-split-area size="40" minSize="25">
57-
<ng-injector-providers [injector]="selectedNode.data.injector" [providers]="providers"/>
57+
<ng-injector-providers [injector]="selectedNode()!.data.injector" [providers]="providers()"/>
5858
</as-split-area>
5959
}
6060
</as-split>

0 commit comments

Comments
 (0)