How to Create own Searchable Component in Angular?
In my last article, I have implemented a search filter pipe in Angular. And I was curious to know that How to create my own Searchable Component in Angular.

If you want to see the filter pipe tutorial then catch it from the below article:
Demo
Let us see what we are going to create in Angular.

And the markup for the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<input [formControl]="searchControl"> <searchable-container [searchTerm]="searchControl.value"> <ul> <li searchable="Javascript" searchableHighlight></li> <li searchable="Angular" searchableHighlight></li> <li searchable="Typescript" searchableHighlight></li> <li searchable="RxJS" searchableHighlight></li> <li searchable="Akita" searchableHighlight></li> </ul> </searchable-container> |
Create the Searchable Container
We want to give our consumers the ability to add a searchable
directive anywhere they want in the template hierarchy.
To accomplish this goal, we’ve leveraged a powerful ability in Element Injector. As you may know, just like a service, we can ask Angular to provide us with a directive via dependency injection. We’ll use this feature to register each searchable
directive in our SearchableContainer
component.
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 |
@Component({ selector: 'searchable-container', template: ` <ng-content></ng-content> ` changeDetection: ChangeDetectionStrategy.OnPush }) export class SearchableContainerComponent { private searchables: SearchableDirective[] = []; private _term = ''; @Input('searchTerm') set term(searchTerm: string) { this._term = searchTerm || ''; this.search(this._term); } get term() { return this._term; } register(searchable: SearchableDirective) { this.searchables.push(searchable); } unregister(searchable: SearchableDirective) { this.searchables = this.searchables.filter(current => current !== searchable); } } |
We define an input
that takes the current search term and call the search()
method with it (we’ll demonstrate the implementation of this method shortly).
We also expose methods for register
and unregister
searchable directives. Let’s move on to the SearchableDirective
.
Create the Searchable Directive
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 |
@Directive({ selector: '[searchable]' }) export class SearchableDirective { token = ''; @Input() set searchable(value: string) { this.token = value; } constructor(@Optional() private container: SearchableContainerComponent, private host: ElementRef) { if (!container) { throw new Error(`Missing <dato-searchable-container> wrapper component`); } } ngOnInit() { this.container.register(this); } hide() { this.host.nativeElement.classList.add('hide'); } show() { this.host.nativeElement.classList.remove('hide'); } ngOnDestroy() { this.container.unregister(this); } } |
As stated before, we’re asking Angular to provide us with the SearchableContainer
and register the current instance. We’re expecting the SearchableContainer
component to be presented, so we’ll throw an error if it doesn’t.
We’ll also expose methods for hiding and showing the native host element. In this case, I decided to go with the CSS strategy rather than use structural directives because DOM creation operations are expensive, and I don’t want to create and remove DOM elements each time the user searched something.
Yes, I know I can use the ViewContainerRef insert()
method and save a reference to the view to preserve the elements but it seems like overkill for my needs.
If, for some reason you need this ability, you can always switch to structural directives.
Implement search() method
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
search(searchTerm: string) { this.handleSearchables(searchTerm); } private handleSearchables(searchTerm: string) { for (const searchable of this.searchables) { if (!searchTerm) { searchable.show(); } else { if (this.match(searchable)) { searchable.show(); } else { searchable.hide(); } } } } private match(searchable: SearchableDirective) { return searchable.token.toLowerCase().indexOf(this._term.toLowerCase()) > -1; } |
When we receive a new search term, we need to loop over the searchable
directives, check if we have a match and run the corresponding method accordingly.
At this point, we have a basic working search. But designers always want more. We need to highlight the matching term. We already have a dedicated pipe in our application that does precisely this, and it looks something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<input [formControl]="searchControl"> <searchable-container [searchTerm]="searchControl.value"> <ul> <li searchable="Javascript"> {{ Javascript | highlight: searchControl.value } </li> <li searchable="Angular"> {{ Angular | highlight: searchControl.value } </li> ... </ul> </searchable-container> |
Although it works, this solution isn’t my favorite. It adds verbosity to the template, and then I need to repeat myself and add the search value for each element.
Do you know how to fetch large data in efficient way in Angular?
Highlighter depends on a parent SearchContainer
and also on Searchable
that could be his parent or on the same host. So why not leverage the Angular element injector tree again and clean it up?
1 2 3 4 5 6 7 8 9 10 11 12 |
<searchable-container [searchTerm]="searchControl.value"> <ul> <li searchable="Javascript" searchableHighlight></li> <li searchable="Angular"> Learn <span searchableHighlight></span> // it can be anywhere </li> ... </ul> </searchable-container> |
Create the SearchableHighlight Directive
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 |
@Directive({ selector: '[searchableHighlight]' }) export class SearchableHighlightDirective { constructor(@Optional() private container: SearchableContainerComponent, @Optional() private searchable: SearchableDirective, private sanitizer: DomSanitizer, private host: ElementRef) { if (!searchable) { throw new Error(`Missing [searchable] directive`); } } ngOnInit() { this.container.register(this, { highlight: true }); } ngOnDestroy() { this.container.unregister(this, { highlight: true }); } get token() { return this.searchable.token; } highlight(token: string, searchTerm: string) { this.host.nativeElement.innerHTML = this.sanitizer.sanitize(SecurityContext.HTML, this.resolve(token, searchTerm)); } resolve(token: string, searchTerm: string) { ...removed for brevity, you can see it in the full demo } } |
As I mentioned before, this directive depends on a SearchableContainer
and a SearchableDirective
, so we’re asking Angular to provide us with both. We mark them as Optional()
and throw an error if we can’t find one of them because they are required.
We register the instance in the container, so we can control it exactly like we did with the SearchableDirective
.
The highlight()
method takes the searchable token and the current search term and sets the innerHTML
of the host element after sanitizing it for security reasons.
The resolve()
method returns the matches term wrapped with a span so we can apply the CSS style to it.
SearchableContainer
Changes
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 |
@Component({ selector: 'searchable-container', template: ` <ng-content></ng-content> ` }) export class SearchableContainerComponent { private searchables: SearchableDirective[] = []; private searchablesHighlight: SearchableHighlightDirective[] = []; ... search(searchTerm: string) { this.handleSearchables(searchTerm); this.handleHighlighters(searchTerm); } register(searchable, { highlight }) { // add the instance } unregister(searchable, { highlight }) { // remove the instance } ngAfterContentInit() { this.search(this.term); } private match(searchable: SearchableDirective) { return searchable.token.toLowerCase().indexOf(this._term.toLowerCase()) > -1; } private handleSearchables(searchTerm: string) { ..same } private handleHighlighters(searchTerm: string) { for (const searchableHighlight of this.searchablesHighlight) { searchableHighlight.highlight(searchableHighlight.token, searchTerm); } } } |
We’ve added an array to store the SearchableHighlight
directives. When we receive a new search term we loop over them and invoke the highlight()
method passing the token and the search term.
Exposing the Result Count
Let’s finish with adding the ability to view the result count. First, we’ll add a counter to our SearchableContainer
component:
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 |
@Component({...}) export class SearchableContainerComponent { private _count = 0; get count() { return this._count; } set count(count: number) { this._count = count; } ... private handleSearchables(searchTerm: string) { let count = 0; for (const searchable of this.searchables) { if (!searchTerm) { searchable.show(); count++; } else { if (this.match(searchable)) { searchable.show(); count++; } else { searchable.hide(); } } } this.count = count; } ... } |
Now we need to expose it to the view. We’re going to use an Angular feature that not everybody is familiar with — the exportAs
feature.
I already wrote a dedicated article for this topic, but long story short the exportAs
property allows us to expose a directive public API to the template.
1 2 3 4 5 6 7 8 9 |
@Component({ selector: 'searchable-container', template: ` <ng-content></ng-content> `, exportAs: 'searchableContainer', changeDetection: ChangeDetectionStrategy.OnPush }) export class SearchableContainerComponent { ... } |
Now we can access the SearchableContainer
instance in our template:
1 2 3 4 5 6 7 8 9 10 |
<searchable-container [searchTerm]="searchControl.value" #container="searchableContainer"> Result: {{container.count}} <ul> ... </ul> </searchable-container> |
We’re creating a local variable named container that’s a reference to the SearchableContainer
instance. The goal was to inspire you with the basic idea of creating searchable components in Angular.
Source code
Task for you

You have to add bootstrap badge in this searchable component. And if you want more then if article is greater than 10 then badge color should be green otherwise red.
Share your code link with us in comment section.
Do you think, You can accept this challenge?

