22

I am trying to achieve a filter with mat-autocomplete that is similar to the following example;

trade input example

So I am trying to achieve the functionality so that when a user begins to type in the trade they are looking for filters based on a partial string match anywhere in the string and highlight this in the option.

cheackatrade screenshot

I current have in my .html

<mat-form-field class="form-group special-input">
            <input type="text" placeholder="Select a trade" aria-label="Select a trade" matInput [formControl]="categoriesCtrl" [matAutocomplete]="auto">
            <mat-autocomplete #auto="matAutocomplete" md-menu-class="autocomplete">
                <mat-option *ngFor="let option of filteredOptions | async" [value]="option.name">
                    {{ option.name }} 
                </mat-option>
            </mat-autocomplete>
        </mat-form-field>

where my .ts is

categoriesCtrl: FormControl;

filteredOptions: Observable<ICategory[]>;
options: ICategory[];

categorySubscription: Subscription;

constructor(fb: FormBuilder, private router: Router, private service: SearchService, private http: Http) {

    this.categoriesCtrl = new FormControl();
}

ngOnInit() {

this.categorySubscription = this.service.getCategories().subscribe((categories: ICategory[]) => {

    this.options = categories;

    this.filteredOptions = this.categoriesCtrl.valueChanges
        .pipe(
        startWith(''),
        map(options => options ? this.filter(options) : this.options.slice())
        );
});    
}

ngOnDestroy() {
    this.categorySubscription.unsubscribe();
}

filter(val: string): ICategory[] {

    return this.options.filter(x =>
        x.name.toUpperCase().indexOf(val.toUpperCase()) !== -1);
}

ICategory is a basic interface.

export interface ICategory {
    value: number;
    name: string;  
}

And the service getCategories() just returns all the categories from an api.

The code is currently working and built per this example;

Angular Material mat-autocomplete example

I would like to add the effect of highlighting the term in the option string? Is this possible at all?

bugs
  • 14,631
  • 5
  • 48
  • 52
Matthew Flynn
  • 3,661
  • 7
  • 40
  • 98

2 Answers2

38

You can use a custom pipe to highlight the partial match whenever the user types in something in the filter.

@Pipe({ name: 'highlight' })
export class HighlightPipe implements PipeTransform {
  transform(text: string, search): string {
    const pattern = search
      .replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&")
      .split(' ')
      .filter(t => t.length > 0)
      .join('|');
    const regex = new RegExp(pattern, 'gi');

    return search ? text.replace(regex, match => `<b>${match}</b>`) : text;
  }
}

Demo

bugs
  • 14,631
  • 5
  • 48
  • 52
  • This a absolutely perfect, thanks so much for the code. As an angular newbie I wasn't aware of pipes. – Matthew Flynn Apr 05 '18 at 13:58
  • I am getting errors like cannot read property 'replace' of undefined. Can you help me with it? –  Feb 28 '20 at 14:08
2

To solve the undefined, you should just check for the present of the search string, sometimes you have none on the control:

export class HighlightPipe implements PipeTransform {
  transform(text: string, search): string {
  if (search && text && typeof search === 'string' && typeof text === 'string') {
      const pattern = search
        .replace(/[\-\[\]\/{}()*x+?.\\^$|]/g, '\\$&')
        .split(' ')
        .filter(t => t.length > 0)
        .join('|');
      const regex = new RegExp(pattern, 'gi');
      return search ? text.replace(regex, match => `<strong>${match}</strong>`) : text;
    }
    return text;
  }
}

Also note some of regex has been thinned out as some escapes were not necessary.

Finally in the HTML you should now use [innerHTML] rather than just piping the object text:

<mat-option [innerHTML]="optionText | highlight: searchValue"></mat-option>
PeterS
  • 2,818
  • 23
  • 36