UI libraries / Angular InstantSearch / Widgets
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>
Import
1
2
3
4
5
6
7
8
import { NgAisHierarchicalMenuModule } from 'angular-instantsearch';

@NgModule({
  imports: [
    NgAisHierarchicalMenuModule,
  ],
})
export class AppModule {}

1. Follow additional steps in Optimize build size to ensure your code is correctly bundled.
2. This imports all the widgets, even the ones you don’t use. Read the Getting started guide for more information.

About this widget

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",
    "categories.lvl0": "products",
    "categories.lvl1": "products > fruits"
  },
  {
    "objectID": "8976987",
    "name": "orange",
    "categories.lvl0": "products",
    "categories.lvl1": "products > fruits"
  }
]

You can also provide more than one path for each level:

1
2
3
4
5
6
7
8
[
  {
    "objectID": "321432",
    "name": "lemon",
    "categories.lvl0": ["products", "goods"],
    "categories.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

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

Props

attributes
type: string[]
Required

The names of the hierarchical attributes that you need to target, in ascending order.

1
2
3
4
5
6
7
<ais-hierarchical-menu
  [attributes]="[
    'categories.lvl0',
    'categories.lvl1',
    'categories.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"

You can also give a function (with the same signature as 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. This is helpful when 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

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

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: ['categories.lvl0', 'categories.lvl1', 'categories.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 users can click the “Show more” button.

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"

You can also 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: ['categories.lvl0', 'categories.lvl1', 'categories.lvl2'],
    });
    super.ngOnInit();
  }
}
Did you find this page helpful?