7
votes

I am trying to group the autocomplete suggestions and would like to render them in primeng.

How we can add a custom template in primeng?

my data

data = [{"id":"m1","name":"menu1","val":"D","items":[{"id":"d1","name":"datanested1","val":"D","items":[{"id":"1","name":"direct Data","val":"E"},{"id":"2","name":"test","val":"E"}]}]},{"id":"d2","name":"menu2","val":"D","items":[{"id":"21","name":"test21","val":"E"},{"id":"22","name":"test23","val":"E"}]},{"id":"d3","name":"menu3","val":"D","items":[{"id":"31","name":"test data 3","val":"E"},{"id":"32","name":"test data 4","val":"E"}]}]

Is there any other libraries available in angular 8 which support this?

I would like to achieve something like this when users start searching in autocomplete...

Menu1 - header
 datanested1 -subheader
  direct Data -values
   test        -values

Menu2 - header
 test21-values
 test23-values

Menu3 - header
 test data 3-values
 test data 4-values


1. if the user types "direct" in the input box...

Menu1 - header
 datanested1 -subheader
  direct Data -values

2. if the user types "data" in the input box...

Menu3 - header
 test data 3-values
 test data 4-values

3. if the user types "menu" in the input box...
Menu1 - header
 datanested1 -subheader
  direct Data -values
  test        -values

Menu2 - header
 test21-values
 test23-values

Menu3 - header
 test data 3-values
 test data 4-values

enter image description here

I have tried the following example in stackblitz.

https://stackblitz.com/edit/primeng-7-1-2-qtsnpm

3
How do you decide what group would be for each item. Does your item have some GroupNumber or GroupName property?ShayD
An alternative library would be angular material. I would actually suggest angular material over primeng in most scenarios tbh. I think it integrates much better (duh) and also has some great components such as this one. material.angular.io/components/autocomplete/examplesJB17

3 Answers

1
votes

Patel , Yes you can add Group Header in NgPrime

.ts

adminentrylistSearch = [
{
  Grp_Header:'THIS IS Header 1' ,
  cdsid: "0121",
  firstname: "FirstName1",
  lastname: "LastName1",
  fullname: "LastName1, FirstName1"
},
{
  cdsid: "0122",
  firstname: "FirstName1",
  lastname: "LastName2",
  fullname: "LastName2, FirstName2"
},
{
  cdsid: "0123",
  firstname: "FirstName3",
  lastname: "LastName3",
  fullname: "LastName3, FirstName3"
},
{
  Grp_Header:'THIS IS Header 2',
  cdsid: "0124",
  firstname: "FirstName4",
  lastname: "LastName4",
  fullname: "LastName4, FirstName4"
},
{
  cdsid: "0125",
  firstname: "FirstName5",
  lastname: "LastName5",
  fullname: "LastName5, FirstName5"
},
{
  cdsid: "0126",
  firstname: "FirstName6",
  lastname: "LastName6",
  fullname: "LastName6, FirstName6"
},
{
  cdsid: "0127",
  firstname: "FirstName7",
  lastname: "LastName7",
  fullname: "LastName7, FirstName7"
}

];

.html

<p-autoComplete [(ngModel)]="cdsidvalue" [suggestions]="filteredCountriesSingle" [dropdown]="true"
  (completeMethod)="filterCountrySingle($event)" field="firstname" [size]="16" placeholder="CDSID">
  <ng-template let-adminentrylistSearch [ngIf]="adminentrylistSearch.index" pTemplate="text">
    <div class='unclickable-header'>
      <span [style.font-weight]="adminentrylistSearch.Grp_Header ? 'bold' : null"> {{adminentrylistSearch.Grp_Header ? adminentrylistSearch.Grp_Header : adminentrylistSearch.firstname}} </span>
    </div>
  </ng-template>
</p-autoComplete> 

Check Result here... Demo

1
votes

In the below approach we will reduce the array to a simple structure

[
  {
    "id": "m1",
    "name": "menu1",
    "val": "D",
    "search": ["m1", "d1", "1", "2", "menu1", "datanested1", "direct Data", "test"],
    "depth": 2
  },
  {
    "id": "d1",
    "name": "datanested1",
    "val": "D",
    "search": ["d1", "1", "2", "datanested1", "direct Data", "test"],
    "depth": 1
  },
  {
    "id": "1",
    "name": "direct Data",
    "val": "E",
    "search": ["1", "direct Data"
    ],
    "depth": 0
  },
 ...

]

The idea is this, we will use the depth to format out text while the search for searching.

  maxDepth = 0;
  reducedArray = (arr, depth = 0, parentSearch = []) => {
    this.maxDepth = Math.max(this.maxDepth, depth);
    if (!arr) {
      return [];
    }

    return arr.reduce((prev, { items, ...otherProps }) => {
      // const depth = this.findDepth({ items, ...otherProps });
      const search = [
        ...this.getProps({ items, ...otherProps }, "id"),
        ...this.getProps({ items, ...otherProps }, "name")
      ];
      const newParentSearch = [...parentSearch, otherProps.name, otherProps.id];
      return [
        ...prev,
        { ...otherProps, search, depth, parentSearch },
        ...this.reducedArray(items, depth + 1, newParentSearch)
      ];
    }, []);
  };

  getProps = (item, prop) => {
    if (!item.items) {
      return [item[prop]];
    } else {
      return [
        item[prop],
        ...item.items.map(x => this.getProps(x, prop))
      ].flat();
    }
  };

We can apply styles like below

  getStyle(depth) {
    return {
      color: depth === 0 ? "darkblue" : depth === 1 ? "green" : "black",
      paddingLeft: depth * 7 + "px",
      fontWeight: 800 - depth * 200
    };
  }

I will use reactive programming so I will convert the object to an Observable usinf of operator

  data$ = of(this.reducedArray(this.data));
  filteredData$ = combineLatest([this.data$, this.filterString$]).pipe(
    map(([data, filterString]) =>
      data.filter(
        ({ search, parentSearch }) =>
          !![...search, ...parentSearch].find(x => x.includes(filterString))
      )
    )
  );

We now done, the remaining is to update html

<p-autoComplete [(ngModel)]="cdsidvalue" [suggestions]="filteredData$ | async"
  (completeMethod)="filterString$.next($event.query)" field="name" [size]="16" placeholder="Menu" [minLength]="1">
  <ng-template let-menu pTemplate="item">
    <span 
      [ngStyle]="getStyle(menu.depth)" >{{ menu.name }}</span>
  </ng-template>

</p-autoComplete>

Demo Here

Update

Since we are using reactive programming, refactoring to accept http requests is quite easy, simply replace the observable with an http request

data$ = this.http.get<any[]>("my/api/url");
  filteredData$ = combineLatest([this.data$, this.filterString$]).pipe(
    map(([data, filterString]) =>
      this.reducedArray(data).filter(
        ({ search, parentSearch }) =>
          !![...search, ...parentSearch].find(x => x.includes(filterString))
      )
    )
  );

See this update in action

I have also updated the html to add loading message, we do not want to display a form without data

0
votes

In your primeng version (7.1.2) grouped autocomplete is not supported. But it's supported in latest version(11.3.0). Please see below image and follow https://www.primefaces.org/primeng/showcase/#/autocomplete

enter image description here