Media Library Step 7: Coding the Frontend (Image Search)
Now let's build the image search component. Open the three main files in your editor:
- image-search.html
- image-search.ts
- image-search.css
HTML Template
Once again we'll start with the HTML. There are basically two elements, an input box and a button to click after entering the input. But we'll make sure the user can also click Enter from within the input box.
Then underneath we'll provide an instance of our thumbnail gallery. In the TypeScript code we'll populate that with the list of thumbnail IDs that match the search query entered.
That's all there really is; here's the entire HTML;
<div class="search-container">
<h2>Search by Tag</h2>
<div class="search-bar">
<!-- [(ngModel)] creates a two-way binding to the searchTag property -->
<input
type="text"
[(ngModel)]="searchTag"
placeholder="e.g., pets"
(keyup.enter)="onSearch()"
>
<button (click)="onSearch()">Search</button>
</div>
<!-- Pass the search results down to the gallery component -->
<app-thumbnail-gallery [ids]="searchResultIds"></app-thumbnail-gallery>
<!-- Show a message if a search was done but no results were found -->
@if (searchResultIds.length === 0 && searchPerformed) {
<p class="no-results-message">No results found for "{{ searchTag }}".</p>
}
Notice that in both cases of clicking the button, or pressing Enter inside the input box, we call the onSearch method, which we'll cover next in our TypeSCript.
TypeScript Code-behind
Here's the entire TypeScript:
import { Component, inject } from "@angular/core"
import { CommonModule } from "@angular/common"
import { HttpClient, HttpClientModule } from "@angular/common/http"
import { FormsModule } from "@angular/forms" // Import FormsModule for ngModel
import { ThumbnailGalleryComponent } from "../thumbnail-gallery/thumbnail-gallery" // Import the gallery component
@Component({
selector: "app-image-search",
standalone: true,
// Import everything this component needs to function
imports: [
CommonModule,
FormsModule,
HttpClientModule,
ThumbnailGalleryComponent,
],
templateUrl: "./image-search.html",
styleUrls: ["./image-search.css"],
})
export class ImageSearchComponent {
private http = inject(HttpClient)
private backendUrl = "http://localhost:3000"
// Holds the value from the text input box
public searchTag: string = ""
// Holds the array of IDs returned from the search API call
public searchResultIds: string[] = []
// Tracks the state to show messages to the user
public searchPerformed = false
/**
* Called when the user clicks the search button.
*/
public onSearch(): void {
this.searchPerformed = true
this.searchResultIds = [] // Clear previous results
if (!this.searchTag || !this.searchTag.trim()) {
console.log("Search tag is empty.")
return
}
const tag = this.searchTag.trim()
const apiUrl = `${this.backendUrl}/query/${tag}`
console.log(`Querying for tag: ${tag}`)
this.http.get<string[]>(apiUrl).subscribe({
next: (ids) => {
this.searchResultIds = ids
console.log(`Found ${ids.length} results for tag "${tag}".`)
},
error: (err) => {
console.error(`Error searching for tag "${tag}":`, err)
},
})
}
}
Again, this is mostly boilerplate except for the single function, onSearch. Let's go through that.
First we set a flag that a search has been performed, and we clear out the previous search, if any.
We then trim the entered search, and build a URL for the GET /query/:search route, adding in the typed search for the search parameter. Then we call the URL, subscribing to its response.
Then this is the key part: We get back a JSON list of IDs matching the search. But these aren't the actual images matching, but rather the thumbnails for those images. And we store those in the searchResultIds member.
But here's the key: If you look back at the HTML, the searchResultIds is what we're feeding into the thumbnail gallery. Thanks to Angular's magic powers, as soon as we update the [ids] attribute of the thumbnail component in our HTML, it will automatically update itself with this new set of thumbnails.
And that's it! That's the search.
Now let's put it all together in the main app component.
Head to Step 8.