API Reference / Angular InstantSearch Widgets / ais-hierarchical-menu
Aug. 23, 2019

ais-hierarchical-menu

You are reading the documentation for Angular InstantSearch v3, which is in beta. You can find the v2 documentation here.

Widget 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

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

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

Props

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

The minimum number of items to diplayed.

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:asc"
  • "count:desc"
  • "name:asc"
  • "name:desc"
  • "isRefined"

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: x => x
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 mapping over the items to transform, and remove or reorder them.

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

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 - connectHierarchicalMenu

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

1. Extend the BaseWidget class

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

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

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

There are a couple of things happening in this boilerplate:

  • we create a HierarchicalMenu class extending BaseWidget
  • we reference the <ais-instantsearch> parent component instance on the HierarchicalMenu widget class
  • we 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 BaseWidget 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
import { Component, Inject, forwardRef } from '@angular/core';
import { BaseWidget, NgAisInstantSearch } from 'angular-instantsearch';
import { connectHierarchicalMenu } from 'instantsearch.js/es/connectors';

@Component({
  selector: 'app-hierarchical-menu',
  template: '<p>It works!</p>'
})
export class HierarchicalMenu extends BaseWidget {
  public state: {
    // render options
  };
  constructor(
    @Inject(forwardRef(() => NgAisInstantSearch))
    public instantSearchParent
  ) {
    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: {
  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 or not the item is selected
  • data: object[]|null: the list of children for the current item
isShowingMore
type: boolean

Whether or not the list is expanded.

canToggleShowMore
type: boolean

Whether or not 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

The minimum number of facet values to retrieve.

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 mapping over the items to transform, and remove or reorder them.

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
44
import { Component, Inject, forwardRef } from '@angular/core';
import { BaseWidget, NgAisInstantSearch } from 'angular-instantsearch';
import { connectHierarchicalMenu } from 'instantsearch.js/es/connectors';

@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 BaseWidget {
  public state: {
     items: object[];
     isShowingMore: boolean;
     canToggleShowMore: boolean;
     refine: Function;
     toggleShowMore: Function;
     createURL: Function;
     widgetParams: object;
  };
  constructor(
    @Inject(forwardRef(() => NgAisInstantSearch))
    public instantSearchParent
  ) {
    super('HierarchicalMenu');
  }
  ngOnInit() {
    this.createWidget(connectHierarchicalMenu, {
      // instance options
      attributes: ['hierarchicalCategories.lvl0', 'hierarchicalCategories.lvl1', 'hierarchicalCategories.lvl2'],
    });
    super.ngOnInit();
  }
}

Did you find this page helpful?