Skip to content

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.