File

src/app/app.component.ts

Description

This is the main angular component that all the other components branch off from. It is in charge of the header and drawer components who have many sub-components.

Extends

BaseApplicationComponent

Metadata

Index

Properties
Methods

Constructor

constructor()

Creates an instance of app component.

Methods

asMutable
asMutable(value: Immutable)
Type parameters :
  • T

Helper to cast immutable types to mutable

Parameters :
Name Type Optional
value Immutable<T> No
Returns : T
clearAllOrgans
clearAllOrgans()

Clear all organs

Returns : void
formatRange
formatRange(range: number[] | undefined, min: number, max: number)

Format a range of values

Parameters :
Name Type Optional
range number[] | undefined No
min number No
max number No
Returns : string
isItemSelected
isItemSelected(item: string)

Whether an item is selected

Parameters :
Name Type Optional
item string No
Returns : any
ontologySelected
ontologySelected(ontologySelection: OntologySelection[] | undefined, type: "anatomical-structures" | "cell-type" | "biomarkers")

Captures changes in the ontologySelection and uses them to update the results-browser label and the filter object in the data store.

Parameters :
Name Type Optional Description
ontologySelection OntologySelection[] | undefined No

the list of currently selected organ nodes

type "anatomical-structures" | "cell-type" | "biomarkers" No
Returns : void
selectAllOrgans
selectAllOrgans()

Select all organs

Returns : void
toggleSelection
toggleSelection(value: string[])

Toggle selection

Parameters :
Name Type Optional
value string[] No
Returns : void
unHighlightNode
unHighlightNode()

Unhighlight a node

Returns : void

Properties

Protected Readonly ageRangeValue
Type : unknown
Default value : computed(() => this.formatRange(this.filter().ageRange, DEFAULT_FILTER_AGE_LOW, DEFAULT_FILTER_AGE_HIGH), )

Formatted age range

Readonly baseHref$
Type : unknown
Default value : this.globalConfig.getOption('baseHref')

Base href option

Protected Readonly biomarkerTree
Type : unknown
Default value : this.store.selectSignal(DataStateSelectors.biomarkersTreeModel)

Biomarker tree

Protected Readonly bmiRangeValue
Type : unknown
Default value : computed(() => this.formatRange(this.filter().bmiRange, DEFAULT_FILTER_BMI_LOW, DEFAULT_FILTER_BMI_HIGH), )

Formatted bmi range

Protected Readonly cellTypeTree
Type : unknown
Default value : this.store.selectSignal(DataStateSelectors.cellTypesTreeModel)

Cell type tree

Protected Readonly consortiaOptions
Type : unknown
Default value : toSignal(this.data.consortiaFilterData$, { initialValue: [] })

Consortia options

Protected Readonly data
Type : unknown
Default value : inject(DataState)

Data state

Protected Readonly databaseReady
Type : unknown
Default value : this.store.selectSignal(DataStateSelectors.isDatabaseReady)

Database state

Protected Readonly debouncedHighlight
Type : unknown
Default value : debounce((id: string) => this.listResultsState.highlightNode(id), 100)

Node highlighting function with debounce

Protected Readonly filter
Type : unknown
Default value : toSignal(this.data.filter$, { requireSync: true })

Filter

Readonly filter$
Type : unknown
Default value : this.globalConfig.getOption('filter')

Filter option

Readonly header$
Type : unknown
Default value : this.globalConfig.getOption('header')

Header option

Readonly homeUrl$
Type : unknown
Default value : this.globalConfig.getOption('homeUrl')

Home url option

Readonly listResultsState
Type : unknown
Default value : inject(ListResultsState)

List results state

Readonly loginDisabled$
Type : unknown
Default value : this.globalConfig.getOption('loginDisabled')

Login disabled option

Readonly logoTooltip$
Type : unknown
Default value : this.globalConfig.getOption('logoTooltip')

Logo tooltip option

Readonly menuOptions
Type : string[]
Default value : ['Anatomical Structures', 'Cell Types', 'Biomarkers']

Menu options

Readonly ontologyTooltips
Type : Record<string | string>
Default value : { as: 'Parts of the body in defined locations and regions, including the surface, internal organs and tissues. These structures may be described by gross or microscopic morphology and include functional tissue units and highly organized cellular ecosystems (such as alveoli in the lungs).', ct: 'Mammalian cells are biological units with a defined function that typically have a nucleus and cytoplasm surrounded by a membrane. Each cell type may have broad common functions across organs and specialized functions or morphological or molecular features within each organ or region. For example, epithelial cells in the skin, lungs and kidneys may have shared and specialized functions according to tissue localization.', b: 'Molecular, histological, morphological, radiological, physiological or anatomical features that help to characterize the biological state of the body. Here we focus on the molecular markers that can be measured to characterize a cell type.', }

Tooltips

Protected Readonly ontologyTree
Type : unknown
Default value : this.store.selectSignal(DataStateSelectors.anatomicalStructuresTreeModel)

Ontology tree

Protected Readonly providerOptions
Type : unknown
Default value : toSignal(this.data.providerFilterData$, { initialValue: [] })

Provider options

Protected Readonly queryStatus
Type : unknown
Default value : toSignal(this.data.queryStatus$, { requireSync: true })

Query status

Readonly removeSpatialSearch
Type : unknown
Default value : actionAsFn(RemoveSearch)
Decorators :
@Dispatch()

Remove spatial searches

Readonly scene
Type : unknown
Default value : inject(SceneState)

Scene state

Readonly selectedOrgans$
Type : unknown
Default value : this.globalConfig.getOption('selectedOrgans')

Selected organs options

selectedToggleOptions
Type : string[]
Default value : []

Selected options

Readonly setSelectedSearches
Type : unknown
Default value : actionAsFn(SetSelectedSearches)
Decorators :
@Dispatch()

Set selected searches

Readonly snackbar
Type : unknown
Default value : inject(SnackbarService)

Snackbar service

Protected Readonly spatialFlowService
Type : unknown
Default value : inject(SpatialSearchFlowService)

Spatial flow service

Protected Readonly spatialSearchItems
Type : unknown
Default value : this.store.selectSignal(SpatialSearchFilterSelectors.items)

Spatial searches

Protected Readonly technologyOptions
Type : unknown
Default value : toSignal(this.data.technologyFilterData$, { initialValue: [] })

Technology options

import { Immutable } from '@angular-ru/cdk/typings';
import { OverlayContainer } from '@angular/cdk/overlay';
import { DOCUMENT } from '@angular/common';
import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { Filter } from '@hra-api/ng-client';
import { BaseApplicationComponent } from '@hra-ui/application';
import { SnackbarService } from '@hra-ui/design-system/snackbar';
import { Dispatch } from '@ngxs-labs/dispatch-decorator';
import { Store } from '@ngxs/store';
import { ALL_ORGANS, GlobalConfigState, OrganInfo } from 'ccf-shared';
import { JsonLd } from 'jsonld/jsonld-spec';
import { debounce } from 'lodash';
import { combineLatest } from 'rxjs';
import { OntologySelection } from './core/models/ontology-selection';
import { actionAsFn } from './core/store/action-as-fn';
import { DataStateSelectors } from './core/store/data/data.selectors';
import {
  DataState,
  DEFAULT_FILTER_AGE_HIGH,
  DEFAULT_FILTER_AGE_LOW,
  DEFAULT_FILTER_BMI_HIGH,
  DEFAULT_FILTER_BMI_LOW,
} from './core/store/data/data.state';
import { ListResultsState } from './core/store/list-results/list-results.state';
import { SceneState } from './core/store/scene/scene.state';
import { RemoveSearch, SetSelectedSearches } from './core/store/spatial-search-filter/spatial-search-filter.actions';
import { SpatialSearchFilterSelectors } from './core/store/spatial-search-filter/spatial-search-filter.selectors';
import { SpatialSearchFlowService } from './shared/services/spatial-search-flow.service';

/** App options */
interface AppOptions {
  /** A list of data sources (in n3, rdf, xml, owl, or jsonld format) */
  dataSources: (string | JsonLd)[];
  /** Service Token. */
  token?: string;
  /** Show header */
  header?: boolean;
  /** Home url */
  homeUrl?: string;
  /** Logo tooltip */
  logoTooltip?: string;
  /** Selected organs */
  selectedOrgans?: string[];
  /** Base href */
  baseHref?: string;
  /** Filter */
  filter?: Partial<Filter>;
  /** Login disabled */
  loginDisabled?: boolean;
}

/**
 * This is the main angular component that all the other components branch off from.
 * It is in charge of the header and drawer components who have many sub-components.
 */
@Component({
  selector: 'ccf-root',
  standalone: false,
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss',
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: {
    class: 'hra-app',
  },
})
export class AppComponent extends BaseApplicationComponent {
  /** Set selected searches */
  @Dispatch()
  readonly setSelectedSearches = actionAsFn(SetSelectedSearches);

  /** Remove spatial searches */
  @Dispatch()
  readonly removeSpatialSearch = actionAsFn(RemoveSearch);

  /** Menu options */
  readonly menuOptions: string[] = ['Anatomical Structures', 'Cell Types', 'Biomarkers'];
  /** Tooltips */
  readonly ontologyTooltips: Record<string, string> = {
    as: 'Parts of the body in defined locations and regions, including the surface, internal organs and tissues. These structures may be described by gross or microscopic morphology and include functional tissue units and highly organized cellular ecosystems (such as alveoli in the lungs).',
    ct: 'Mammalian cells are biological units with a defined function that typically have a nucleus and cytoplasm surrounded by a membrane. Each cell type may have broad common functions across organs and specialized functions or morphological or molecular features within each organ or region. For example, epithelial cells in the skin, lungs and kidneys may have shared and specialized functions according to tissue localization.',
    b: 'Molecular, histological, morphological, radiological, physiological or anatomical features that help to characterize the biological state of the body. Here we focus on the molecular markers that can be measured to characterize a cell type.',
  };

  /** Selected options */
  selectedToggleOptions: string[] = [];

  /** State reference */
  private readonly store = inject(Store);
  /** Data state */
  protected readonly data = inject(DataState);
  /** Spatial flow service */
  protected readonly spatialFlowService = inject(SpatialSearchFlowService);
  /** Scene state */
  readonly scene = inject(SceneState);
  /** List results state */
  readonly listResultsState = inject(ListResultsState);
  /** Snackbar service */
  readonly snackbar = inject(SnackbarService);
  /** Global config */
  private readonly globalConfig = inject<GlobalConfigState<AppOptions>>(GlobalConfigState);

  /** Filter */
  protected readonly filter = toSignal(this.data.filter$, { requireSync: true });
  /** Technology options */
  protected readonly technologyOptions = toSignal(this.data.technologyFilterData$, { initialValue: [] });
  /** Provider options */
  protected readonly providerOptions = toSignal(this.data.providerFilterData$, { initialValue: [] });

  /** Consortia options */
  protected readonly consortiaOptions = toSignal(this.data.consortiaFilterData$, { initialValue: [] });
  /** Spatial searches */
  protected readonly spatialSearchItems = this.store.selectSignal(SpatialSearchFilterSelectors.items);
  /** Database state */
  protected readonly databaseReady = this.store.selectSignal(DataStateSelectors.isDatabaseReady);
  /** Query status */
  protected readonly queryStatus = toSignal(this.data.queryStatus$, { requireSync: true });
  /** Cell type tree */
  protected readonly cellTypeTree = this.store.selectSignal(DataStateSelectors.cellTypesTreeModel);
  /** Ontology tree */
  protected readonly ontologyTree = this.store.selectSignal(DataStateSelectors.anatomicalStructuresTreeModel);
  /** Biomarker tree */
  protected readonly biomarkerTree = this.store.selectSignal(DataStateSelectors.biomarkersTreeModel);

  /** Formatted age range */
  protected readonly ageRangeValue = computed(() =>
    this.formatRange(this.filter().ageRange, DEFAULT_FILTER_AGE_LOW, DEFAULT_FILTER_AGE_HIGH),
  );
  /** Formatted bmi range */
  protected readonly bmiRangeValue = computed(() =>
    this.formatRange(this.filter().bmiRange, DEFAULT_FILTER_BMI_LOW, DEFAULT_FILTER_BMI_HIGH),
  );

  /** Header option */
  readonly header$ = this.globalConfig.getOption('header');
  /** Home url option */
  readonly homeUrl$ = this.globalConfig.getOption('homeUrl');
  /** Logo tooltip option */
  readonly logoTooltip$ = this.globalConfig.getOption('logoTooltip');
  /** Login disabled option */
  readonly loginDisabled$ = this.globalConfig.getOption('loginDisabled');
  /** Filter option */
  readonly filter$ = this.globalConfig.getOption('filter');
  /** Selected organs options */
  readonly selectedOrgans$ = this.globalConfig.getOption('selectedOrgans');
  /** Base href option */
  readonly baseHref$ = this.globalConfig.getOption('baseHref');

  /** Node highlighting function with debounce */
  protected readonly debouncedHighlight = debounce((id: string) => this.listResultsState.highlightNode(id), 100);

  /**
   * Creates an instance of app component.
   */
  constructor() {
    super({ screenSizeNotice: { width: 1280, height: 832 } });

    this.data.tissueBlockData$.subscribe();
    this.data.aggregateData$.subscribe();
    this.data.ontologyTermOccurencesData$.subscribe();
    this.data.cellTypeTermOccurencesData$.subscribe();
    this.data.biomarkerTermOccurencesData$.subscribe();
    this.data.sceneData$.subscribe();
    this.filter$.subscribe((filter = {}) => this.data.updateFilter(filter));
    this.baseHref$.subscribe((ref) => this.globalConfig.patchState({ baseHref: ref ?? '' }));

    combineLatest([this.scene.referenceOrgans$, this.selectedOrgans$]).subscribe(([refOrgans, selected]) => {
      this.scene.setSelectedReferenceOrgansWithDefaults(refOrgans as OrganInfo[], selected ?? []);
    });

    this.selectedToggleOptions = this.menuOptions;

    const overlay = inject(OverlayContainer);
    const overlayEl = overlay.getContainerElement();
    if (!overlayEl.isConnected) {
      inject(DOCUMENT).body.appendChild(overlayEl);
    }
  }

  /** Format a range of values */
  formatRange(range: number[] | undefined, min: number, max: number): string {
    return `${range?.[0] ?? min}-${range?.[1] ?? max}`;
  }

  /**
   * Captures changes in the ontologySelection and uses them to update the results-browser label
   * and the filter object in the data store.
   *
   * @param ontologySelection the list of currently selected organ nodes
   */
  ontologySelected(
    ontologySelection: OntologySelection[] | undefined,
    type: 'anatomical-structures' | 'cell-type' | 'biomarkers',
  ): void {
    if (ontologySelection) {
      if (type === 'anatomical-structures') {
        this.data.updateFilter({ ontologyTerms: ontologySelection.map((selection) => selection.id) });
      } else if (type === 'cell-type') {
        this.data.updateFilter({ cellTypeTerms: ontologySelection.map((selection) => selection.id) });
      } else if (type === 'biomarkers') {
        this.data.updateFilter({ biomarkerTerms: ontologySelection.map((selection) => selection.id) });
      }
      return;
    }

    this.data.updateFilter({ ontologyTerms: [], cellTypeTerms: [], biomarkerTerms: [] });
  }

  /** Whether an item is selected */
  isItemSelected(item: string) {
    return this.selectedToggleOptions.includes(item);
  }

  /** Toggle selection */
  toggleSelection(value: string[]) {
    this.selectedToggleOptions = value;
  }

  /** Helper to cast immutable types to mutable */
  asMutable<T>(value: Immutable<T>): T {
    return value as T;
  }

  /** Select all organs */
  selectAllOrgans() {
    this.scene.setSelectedReferenceOrgans(ALL_ORGANS);
  }

  /** Clear all organs */
  clearAllOrgans() {
    this.scene.setSelectedReferenceOrgans([]);
  }

  /** Unhighlight a node */
  unHighlightNode(): void {
    if (this.listResultsState.getState().highlightedNodeId) {
      this.listResultsState.unHighlightNode();
    }
  }
}
<mat-sidenav-container hraFeature="eui" class="sidenav-container" autosize>
  <mat-sidenav hraFeature="data-filters" class="sidenav-content" mode="over" #sidenav>
    <div class="sidenav-header">
      <span class="sidenav-title"> Data Filters </span>
      <div style="flex-grow: 1"></div>
      <button
        hraFeature="close-filters"
        hraClickEvent
        mat-icon-button
        hraIconButtonSize="large"
        disableRipple
        (click)="sidenav.toggle()"
      >
        <mat-icon>close</mat-icon>
      </button>
    </div>

    <ccf-filters-content
      [filter]="filter()"
      [technologyOptions]="technologyOptions()"
      [providerOptions]="providerOptions()"
      [consortiaOptions]="consortiaOptions()"
      [spatialSearchItems]="spatialSearchItems()"
      (filterChange)="data.updateFilter($event); sidenav.toggle()"
      (spatialSearchSelectionChange)="setSelectedSearches($event)"
      (spatialSearchRemoved)="removeSpatialSearch($event)"
    />
  </mat-sidenav>

  <div class="page-content">
    @if (!databaseReady() || queryStatus() === 'running') {
      <mat-progress-bar mode="indeterminate" />
    }

    <hra-nav-header-buttons
      app="product:eui"
      appTitle="Exploration User Interface"
      [appLink]="(homeUrl$ | async) ?? 'https://apps.humanatlas.io/eui/'"
      [brandmark]="true"
      [hraTooltip]="(logoTooltip$ | async) ?? 'Human Reference Atlas Portal'"
    />

    <div hraFeature="left-panel" class="left-panel">
      <div class="filter-data">
        <div class="filter-button-container" hraPlainTooltip="Select filters">
          <div class="filter-button-label">Filters</div>
          <button
            hraFeature="open-filters"
            hraClickEvent
            mat-icon-button
            hraIconButtonSize="large"
            disableRipple
            (click)="sidenav.toggle()"
          >
            <mat-icon>filter_list</mat-icon>
          </button>
        </div>
        <div hraFeature="filter-text" hraHoverEvent class="filter-text" hraPlainTooltip="Current filters">
          <div class="sex filter-tag">Sex: {{ filter().sex }}</div>
          <div class="bmi filter-tag">BMI: {{ bmiRangeValue() }}</div>
          <div class="age filter-tag">Age: {{ ageRangeValue() }}</div>
        </div>
      </div>

      <div class="toggle-settings">
        <mat-button-toggle-group
          hraFeature="asctb-toggle"
          multiple
          name="multiSelect"
          aria-label="Multi Select"
          hraButtonToggleSize="small"
          hideMultipleSelectionIndicator
          (change)="toggleSelection($event.value)"
        >
          @for (option of menuOptions; track option) {
            <mat-button-toggle
              hraClickEvent
              checked
              [hraFeature]="option | slugify"
              [value]="option"
              [hraPlainTooltip]="(isItemSelected(option) ? 'Hide ' : 'Show ') + option.toLowerCase()"
              >{{ option }}</mat-button-toggle
            >
          }
        </mat-button-toggle-group>
      </div>
      <div hraFeature="asctb-trees" class="ontologies">
        @if (isItemSelected('Anatomical Structures')) {
          <ccf-ontology-selection
            hraFeature="anatomical-structures"
            class="ontology-selection"
            [treeModel]="ontologyTree()"
            [termData]="(data.ontologyTermsFullData$ | async) ?? {}"
            [occurenceData]="(data.ontologyTermOccurencesData$ | async) ?? {}"
            [tooltip]="ontologyTooltips['as']"
            (ontologySelection)="ontologySelected($any($event), 'anatomical-structures')"
          />
        }
        @if (isItemSelected('Cell Types')) {
          <ccf-ontology-selection
            hraFeature="cell-types"
            class="cell-type-selection"
            [treeModel]="cellTypeTree()"
            [termData]="(data.cellTypeTermsFullData$ | async) ?? {}"
            [occurenceData]="(data.cellTypeTermOccurencesData$ | async) ?? {}"
            [tooltip]="ontologyTooltips['ct']"
            (ontologySelection)="ontologySelected($any($event), 'cell-type')"
          />
        }
        @if (isItemSelected('Biomarkers')) {
          <ccf-ontology-selection
            hraFeature="biomarkers"
            class="biomarker-selection"
            [treeModel]="biomarkerTree()"
            [termData]="(data.biomarkerTermsFullData$ | async) ?? {}"
            [occurenceData]="(data.biomarkerTermOccurencesData$ | async) ?? {}"
            [tooltip]="ontologyTooltips['b']"
            (ontologySelection)="ontologySelected($any($event), 'biomarkers')"
          />
        }

        @if (selectedToggleOptions.length === 0) {
          <div class="no-selection-notice">
            No anatomical structures, cell types, or biomarkers selected. Use the above buttons to view the registered
            listings.
          </div>
        }
      </div>
    </div>

    <div hraFeature="body-ui-controls" class="vis-panel">
      <span class="tagline">Body Visualization</span>
      <button
        hraFeature="menu-open"
        hraClickEvent
        mat-icon-button
        hraIconButtonSize="large"
        aria-label="Icon to open nested menu"
        hraPlainTooltip="More"
        [matMenuTriggerFor]="menu"
      >
        <mat-icon>more_vert</mat-icon>
      </button>
      <mat-menu hraFeature="menu" #menu="matMenu">
        <button hraFeature="info" hraClickEvent hraHoverEvent mat-menu-item [matMenuTriggerFor]="submenu">
          <mat-icon>info</mat-icon>
          Info
          <mat-icon class="expand-arrow">arrow_right</mat-icon>
        </button>
        <button hraFeature="visibility" hraClickEvent hraHoverEvent mat-menu-item [matMenuTriggerFor]="submenu2">
          <mat-icon>visibility</mat-icon>
          Organ visibility
          <mat-icon class="expand-arrow">arrow_right</mat-icon>
        </button>
        <mat-divider class="menu-divider" />
        <button hraFeature="reset-settings" hraClickEvent mat-menu-item (click)="bodyUI.resetView()">
          <mat-icon class="reset icon">reset_settings</mat-icon>
          Reset visualization view
        </button>
      </mat-menu>
      <mat-menu #submenu="matMenu">
        <button hraFeature="info-text" hraClickEvent hraHoverEvent mat-menu-item>
          All Human Reference Atlas organs may be viewed in the Body Visualization section. Show/hide organs with the
          organ search feature. Hiding and showing organs does not filter data results.
        </button>
      </mat-menu>
      <mat-menu #submenu2="matMenu">
        <button hraFeature="show-all-organs" hraClickEvent mat-menu-item (click)="selectAllOrgans()">
          <mat-icon>visibility</mat-icon>
          Show all organs
        </button>
        <button hraFeature="hide-all-organs" hraClickEvent mat-menu-item (click)="clearAllOrgans()">
          <mat-icon>visibility_off</mat-icon>
          Hide all organs
        </button>
        <mat-divider class="menu-divider" />
        <button hraFeature="reset-organ-view" hraClickEvent mat-menu-item (click)="scene.setDefaultOrgans()">
          <mat-icon class="reset icon">reset_settings</mat-icon>
          Reset to default organ view
        </button>
      </mat-menu>

      <div class="filler"></div>

      <ccf-organ-select hraPlainTooltip="Select organs to show/hide" />
    </div>

    <div hraFeature="body-ui" class="content">
      <button
        hraFeature="spatial-search-button"
        hraClickEvent
        class="spatial-search-button"
        mat-flat-button
        hraPlainTooltip="Filter experimental data spatially"
        (click)="spatialFlowService.startSpatialSearchFlow(true)"
      >
        Spatial Search
      </button>
      <hra-body-ui
        hraFeature="scene"
        hraClickEvent
        hraClickEventTriggerOn="none"
        hraHoverEvent
        hraHoverEventTriggerOn="none"
        class="stage-content"
        [scene]="$any(scene.scene$ | async)"
        [bounds]="{ x: 2, y: 2, z: 0.4 }"
        (nodeClick)="scene.sceneNodeClicked($event); sceneEvent.logEvent('click')"
        (nodeHoverStart)="scene.sceneNodeHovered($event); sceneHoverEvent.logEvent('mouseenter')"
        (nodeHoverStop)="scene.sceneNodeUnhover()"
        #bodyUI
        #sceneEvent="hraClickEvent"
        #sceneHoverEvent="hraHoverEvent"
      />
    </div>

    <div hraFeature="right-panel" class="right-panel">
      <ccf-results-browser
        [listResults]="(listResultsState.listResults$ | async) ?? []"
        [aggregateData]="(data.aggregateData$ | async) ?? []"
        [highlighted]="(listResultsState.highlightedNodeId$ | async) ?? ''"
        [header]="(header$ | async) ?? false"
        (listResultSelected)="listResultsState.selectListResult(asMutable($event))"
        (listResultDeselected)="listResultsState.deselectListResult(asMutable($event))"
        (listResultExpansionChange)="listResultsState.changeExpansion(asMutable($event))"
        (itemHovered)="debouncedHighlight($event)"
        (itemUnhovered)="unHighlightNode()"
      />
    </div>
  </div>
</mat-sidenav-container>
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""