API Reference / Angular InstantSearch / ais-hierarchical-menu
Signature
<ais-hierarchical-menu
  [attributes]="string[]"

  // Optional parameters
  [limit]="number"
  separator="string"
  rootPath="string"
  [showParentLevel]="boolean"
  [sortBy]="string[]|function"
  [autoHideContainer]="boolean"
  [transformItems]="function"
></ais-hierarchical-menu>

About this widget # A

The ais-hierarchical-menu component displays a tree menu that lets the user browse attributes.

Requirements#

The objects to use in the hierarchical menu must follow this structure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
  {
    "objectID": "321432",
    "name": "lemon",
    "hierarchicalCategories.lvl0": "products",
    "hierarchicalCategories.lvl1": "products > fruits"
  },
  {
    "objectID": "8976987",
    "name": "orange",
    "hierarchicalCategories.lvl0": "products",
    "hierarchicalCategories.lvl1": "products > fruits"
  }
]

It’s also possible to provide more than one path for each level:

1
2
3
4
5
6
7
8
[
  {
    "objectID": "321432",
    "name": "lemon",
    "hierarchicalCategories.lvl0": ["products", "goods"],
    "hierarchicalCategories.lvl1": ["products > fruits", "goods > to eat"]
  }
]

The attributes passed to the attributes prop must be declared as Attributes for faceting on the Algolia dashboard or configured as attributesForFaceting with the Algolia API.

Examples # A

1
2
3
4
5
6
7
<ais-hierarchical-menu
  [attributes]="[
    'hierarchicalCategories.lvl0',
    'hierarchicalCategories.lvl1',
    'hierarchicalCategories.lvl2'
  ]"
></ais-hierarchical-menu>

Props # A

attributes #
type: string[]
Required

The name of the attributes to generate the menu with.

1
2
3
4
5
6
7
<ais-hierarchical-menu
  [attributes]="[
    'hierarchicalCategories.lvl0',
    'hierarchicalCategories.lvl1',
    'hierarchicalCategories.lvl2'
  ]"
></ais-hierarchical-menu>
limit #
type: number
default: 10
Optional

How many facet values to retrieve.

1
2
3
4
<ais-hierarchical-menu
  // ...
  [limit]="20"
></ais-hierarchical-menu>
separator #
type: string
default: >
Optional

The level separator used in the records.

1
2
3
4
<ais-hierarchical-menu
  // ...
  separator="-"
></ais-hierarchical-menu>
rootPath #
type: string
Optional

The path to use if the first level is not the root level.

1
2
3
4
<ais-hierarchical-menu
  // ...
  rootPath="Appliances"
></ais-hierarchical-menu>
showParentLevel #
type: string
default: true
Optional

Whether to show the siblings of the selected parent level of the current refined value.

1
2
3
4
<ais-hierarchical-menu
  // ...
  [showParentLevel]="false"
></ais-hierarchical-menu>
sortBy #
type: string[]|function
default: ["name:asc"]
Optional

How to sort refinements. Must be one or more of the following strings:

  • "count" (same as "count:desc")
  • "count:asc"
  • "count:desc"
  • "name" (same as "name:asc")
  • "name:asc"
  • "name:desc"
  • "isRefined" (same as "isRefined:asc")
  • "isRefined:asc"
  • "isRefined:desc"

It’s also possible to give a function, which must have the same signature than the JavaScript Array.sort function.

1
2
3
4
<ais-hierarchical-menu
  // ...
  [sortBy]="['isRefined', 'name:asc']"
></ais-hierarchical-menu>
autoHideContainer #
type: boolean
Optional

Hides the hierarchical menu if there’s no item to display.

1
2
3
4
<ais-hierarchical-menu
  // ...
  [autoHideContainer]="true"
></ais-hierarchical-menu>
transformItems #
type: function
default: items => items
Optional

Receives the items and is called before displaying them. Should return a new array with the same shape as the original array. Useful for transforming, removing, or reordering items.

In addition, the full results data is available, which includes all regular response parameters, as well as parameters from the helper (for example disjunctiveFacetsRefinements).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Component({
  template: `
    <ais-hierarchical-menu
      // ...
      [transformItems]="transformItems"
    ></ais-hierarchical-menu>
  `,
})
export class AppComponent {
  transformItems(items) {
    return items.map(item => ({
      ...item,
      label: item.label.toUpperCase(),
    }));
  },

  /* or, combined with results */
  transformItems(items, { results }) {
    return items.map(item => ({
      ...item,
      label: item.isRefined
        ? `${item.label} (page ${results.page + 1}/${results.nbPages})`
        : item.label,
    }));
  },
}

HTML output# A

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<div class="ais-HierarchicalMenu">
  <ul class="ais-HierarchicalMenu-list ais-HierarchicalMenu-list--lvl0">
    <li class="ais-HierarchicalMenu-item ais-HierarchicalMenu-item--parent ais-HierarchicalMenu-item--selected">
      <a class="ais-HierarchicalMenu-link" href="#">
        <span class="ais-HierarchicalMenu-label">Appliances</span>
        <span class="ais-HierarchicalMenu-count">4,306</span>
      </a>
      <ul class="ais-HierarchicalMenu-list ais-HierarchicalMenu-list--child ais-HierarchicalMenu-list--lvl1">
        <li class="ais-HierarchicalMenu-item ais-HierarchicalMenu-item--parent">
          <a class="ais-HierarchicalMenu-link" href="#">
            <span class="ais-HierarchicalMenu-label">Dishwashers</span>
            <span class="ais-HierarchicalMenu-count">181</span>
          </a>
        </li>
        <li class="ais-HierarchicalMenu-item">
          <a class="ais-HierarchicalMenu-link" href="#">
            <span class="ais-HierarchicalMenu-label">Fans</span>
            <span class="ais-HierarchicalMenu-count">91</span>
          </a>
        </li>
      </ul>
    </li>
    <li class="ais-HierarchicalMenu-item ais-HierarchicalMenu-item--parent">
      <a class="ais-HierarchicalMenu-link" href="#">
        <span class="ais-HierarchicalMenu-label">Audio</span>
        <span class="ais-HierarchicalMenu-count">1,570</span>
      </a>
    </li>
  </ul>
</div>

Customize the UI with connectHierarchicalMenu# A

If you want to create your own UI of the ais-hierarchical-menu widget, you can combine the connectHierarchicalMenu connector with the TypedBaseWidget class.

1. Extend the TypedBaseWidget class#

First of all, you will need to write some boilerplate code to initialize correctly the TypedBaseWidget class. This happens in the constructor() of your class extending the TypedBaseWidget class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Component, Inject, forwardRef, Optional } from '@angular/core';
import { TypedBaseWidget, NgAisInstantSearch, NgAisIndex } from 'angular-instantsearch';

@Component({
  selector: 'app-hierarchical-menu',
  template: '<p>It works!</p>'
})
export class HierarchicalMenu extends TypedBaseWidget {
  constructor(
    @Inject(forwardRef(() => NgAisIndex))
    @Optional()
    public parentIndex: NgAisIndex,
    @Inject(forwardRef(() => NgAisInstantSearch))
    public instantSearchInstance: NgAisInstantSearch
  ) {
    super('HierarchicalMenu');
  }
}

There are a couple of things happening in this boilerplate:

  • create a HierarchicalMenu class extending TypedBaseWidget
  • reference the <ais-instantsearch> parent component instance on the HierarchicalMenu widget class
  • set app-hierarchical-menu as a selector, so we can use our component as <app-hierarchical-menu></app-hierarchical-menu>

2. Connect your custom widget#

The TypedBaseWidget class has a method called createWidget() which takes two arguments: the connector to use and an object of options (instance options) for this connector. We call this method at ngOnInit. This component now implements OnInit.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { Component, Inject, forwardRef, Optional } from '@angular/core';
import { TypedBaseWidget, NgAisInstantSearch, NgAisIndex } from 'angular-instantsearch';

import connectHierarchicalMenu, {
  HierarchicalMenuWidgetDescription,
  HierarchicalMenuConnectorParams
} from 'instantsearch.js/es/connectors/hierarchical-menu/connectHierarchicalMenu';

@Component({
  selector: 'app-hierarchical-menu',
  template: '<p>It works!</p>'
})
export class HierarchicalMenu extends TypedBaseWidget<HierarchicalMenuWidgetDescription, HierarchicalMenuConnectorParams> {
  public state: HierarchicalMenuWidgetDescription['renderState']; // Rendering options
  constructor(
    @Inject(forwardRef(() => NgAisIndex))
    @Optional()
    public parentIndex: NgAisIndex,
    @Inject(forwardRef(() => NgAisInstantSearch))
    public instantSearchInstance: NgAisInstantSearch
  ) {
    super('HierarchicalMenu');
  }
  ngOnInit() {
    this.createWidget(connectHierarchicalMenu, {
      // instance options
      attributes: ['hierarchicalCategories.lvl0', 'hierarchicalCategories.lvl1', 'hierarchicalCategories.lvl2'],
    });
    super.ngOnInit();
  }
}

3. Render from the state#

Your component instance has access to a this.state property which holds the rendering options of the widget.

public state: HierarchicalMenuWidgetDescription['renderState'];
// {
//   items: object[];
//   isShowingMore: boolean;
//   canToggleShowMore: boolean;
//   refine: Function;
//   toggleShowMore: Function;
//   createURL: Function;
//   widgetParams: object;
// }
1
2
3
4
5
6
7
8
9
10
11
<div *ngFor="let item of state.items">
  <!-- level 0 -->
  <label>
    <input type="checkbox"
           (click)="state.refine(item.value)"
           [checked]="item.isRefined" > {{ item.label }} ({{ item.count }})
  </label>
  <div *ngFor="let subitem of item.data">
    <!-- level 1 ... -->
  </div>
</div>

If SEO is critical to your search page, your custom HTML markup needs to be parsable:

  • use plain <a> tags with href attributes for search engines bots to follow them,
  • use semantic markup with structured data when relevant, and test it.

Refer to our SEO checklist for building SEO-ready search experiences.

Rendering options #

items #
type: object[]

The list of available items, with each item:

  • label: string: the label of the item
  • value: string: the value of the item
  • count: number: the number results matching this value
  • isRefined: boolean: whether the item is selected
  • data: object[]|null: the list of children for the current item
isShowingMore #
type: boolean

Whether the list is expanded.

canToggleShowMore #
type: boolean

Whether the “Show more” button can be clicked.

refine #
type: function

Sets the path of the hierarchical filter and triggers a new search.

toggleShowMore #
type: function

Toggles the number of displayed values between limit and showMoreLimit.

createURL #
type: function

Generates a URL for the next state.

widgetParams #
type: object

All original widget options forwarded to the render function.

Instance options #

attributes #
type: string[]
Required

The name of the attributes to generate the menu with.

limit #
type: number
default: 10
Optional

How many facet values to retrieve. When isShowingMore is false, this is the number of facet values displayed before clicking the “Show more” button.

showMoreLimit #
type: number
Optional

The maximum number of displayed items (only used when the showMore feature is implemented).

separator #
type: string
default: >
Optional

The level separator used in the records.

rootPath #
type: string
default: null
Optional

The prefix path to use if the first level is not the root level.

showParentLevel #
type: boolean
default: true
Optional

Whether to show the siblings of the selected parent level of the current refined value.

sortBy #
type: string[]|function
default: ["name:asc"]
Optional

How to sort refinements. Must be one or more of the following strings:

  • "count:asc"
  • "count:desc"
  • "name:asc"
  • "name:desc"
  • "isRefined"

It’s also possible to give a function, which receives items two by two, like JavaScript’s Array.sort.

transformItems #
type: function
default: items => items
Optional

Receives the items and is called before displaying them. Should return a new array with the same shape as the original array. Useful for transforming, removing, or reordering items.

In addition, the full results data is available, which includes all regular response parameters, as well as parameters from the helper (for example disjunctiveFacetsRefinements).

Full example#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import { Component, Inject, forwardRef, Optional } from '@angular/core';
import { TypedBaseWidget, NgAisInstantSearch, NgAisIndex } from 'angular-instantsearch';

import connectHierarchicalMenu, {
  HierarchicalMenuWidgetDescription,
  HierarchicalMenuConnectorParams
} from 'instantsearch.js/es/connectors/hierarchical-menu/connectHierarchicalMenu';

@Component({
  selector: 'app-hierarchical-menu',
  template: `
<div *ngFor="let item of state.items">
  <!-- level 0 -->
  <label>
    <input type="checkbox"
           (click)="state.refine(item.value)"
           [checked]="item.isRefined" > {{ item.label }} ({{ item.count }})
  </label>
  <div *ngFor="let subitem of item.data">
    <!-- level 1 ... -->
  </div>
</div>
`
})
export class HierarchicalMenu extends TypedBaseWidget<HierarchicalMenuWidgetDescription, HierarchicalMenuConnectorParams> {
  public state: HierarchicalMenuWidgetDescription['renderState']; // Rendering options
  constructor(
    @Inject(forwardRef(() => NgAisIndex))
    @Optional()
    public parentIndex: NgAisIndex,
    @Inject(forwardRef(() => NgAisInstantSearch))
    public instantSearchInstance: NgAisInstantSearch
  ) {
    super('HierarchicalMenu');
  }
  ngOnInit() {
    this.createWidget(connectHierarchicalMenu, {
      // instance options
      attributes: ['hierarchicalCategories.lvl0', 'hierarchicalCategories.lvl1', 'hierarchicalCategories.lvl2'],
    });
    super.ngOnInit();
  }
}
Did you find this page helpful?