Repository: angular/angular
File: devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree/router-tree.component.ts (TypeScript)

Code before:
  linkedSignal,
  signal,
  viewChild,
} from '@angular/core';
import {TreeVisualizerComponent} from '../../shared/tree-visualizer/tree-visualizer.component';
import {MatIconModule} from '@angular/material/icon';
import {MatSnackBar, MatSnackBarModule} from '@angular/material/snack-bar';
import {ApplicationOperations} from '../../application-operations/index';
import {RouteDetailsRowComponent} from './route-details-row.component';
import {FrameManager} from '../../application-services/frame_manager';
import {Events, MessageBus, Route} from '../../../../../protocol';
import {SvgD3Node, TreeVisualizerConfig} from '../../shared/tree-visualizer/tree-visualizer';
import {
  RouterTreeD3Node,
  transformRoutesIntoVisTree,
  RouterTreeNode,
  findNodesByLabel,
  RouterTreeVisualizer,
} from './router-tree-fns';
import {ButtonComponent} from '../../shared/button/button.component';
import {SplitComponent} from '../../shared/split/split.component';
import {SplitAreaDirective} from '../../shared/split/splitArea.directive';
import {Debouncer} from '../../shared/utils/debouncer';

const SEARCH_DEBOUNCE = 250;
const RUN_GUARDS_AND_RESOLVERS_OPTIONS = [
  'pathParamsChange',
  'pathParamsOrQueryParamsChange',
  'always',
  'paramsChange',
  'paramsOrQueryParamsChange',
];

@Component({
  selector: 'ng-router-tree',
  templateUrl: './router-tree.component.html',
  styleUrls: ['./router-tree.component.scss'],
  imports: [
    TreeVisualizerComponent,
    SplitComponent,
    SplitAreaDirective,
    MatIconModule,
    MatSnackBarModule,
    RouteDetailsRowComponent,
    ButtonComponent,
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RouterTreeComponent {
  private readonly searchInput = viewChild.required<ElementRef>('searchInput');
  private readonly routerTree = viewChild.required<RouterTreeVisualizer>('routerTree');

Review comment (suggestion):
Let's add at least some level of type safety here. Something like:

```typescript
type OnlyLiterals<T> = T extends string
  ? (string extends T ? never : T)
  : never;
type RunGuardsAndResolvers = OnlyLiterals<Route['runGuardsAndResolvers']>;

const RUN_GUARDS_AND_RESOLVERS_OPTIONS: RunGuardsAndResolvers[]  = [
// ...
```

Code after:
  linkedSignal,
  signal,
  viewChild,
} from '@angular/core';
import {TreeVisualizerComponent} from '../../shared/tree-visualizer/tree-visualizer.component';
import {MatIconModule} from '@angular/material/icon';
import {MatSnackBar, MatSnackBarModule} from '@angular/material/snack-bar';
import {ApplicationOperations} from '../../application-operations/index';
import {RouteDetailsRowComponent} from './route-details-row.component';
import {FrameManager} from '../../application-services/frame_manager';
import {Events, MessageBus, Route, RunGuardsAndResolvers} from '../../../../../protocol';
import {SvgD3Node, TreeVisualizerConfig} from '../../shared/tree-visualizer/tree-visualizer';
import {
  RouterTreeD3Node,
  transformRoutesIntoVisTree,
  RouterTreeNode,
  findNodesByLabel,
  RouterTreeVisualizer,
} from './router-tree-fns';
import {ButtonComponent} from '../../shared/button/button.component';
import {SplitComponent} from '../../shared/split/split.component';
import {SplitAreaDirective} from '../../shared/split/splitArea.directive';
import {Debouncer} from '../../shared/utils/debouncer';

const SEARCH_DEBOUNCE = 250;
const RUN_GUARDS_AND_RESOLVERS_OPTIONS: RunGuardsAndResolvers[] = [
  'pathParamsChange',
  'pathParamsOrQueryParamsChange',
  'always',
  'paramsChange',
  'paramsOrQueryParamsChange',
];

@Component({
  selector: 'ng-router-tree',
  templateUrl: './router-tree.component.html',
  styleUrls: ['./router-tree.component.scss'],
  imports: [
    TreeVisualizerComponent,
    SplitComponent,
    SplitAreaDirective,
    MatIconModule,
    MatSnackBarModule,
    RouteDetailsRowComponent,
    ButtonComponent,
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RouterTreeComponent {
  private readonly searchInput = viewChild.required<ElementRef>('searchInput');
  private readonly routerTree = viewChild.required<RouterTreeVisualizer>('routerTree');