Displaying Returned Data

Open search-header.component.ts, which was generated for you earlier, along with its template search-header.component.html. In the template, you can see that the input element has a [formControl] attribute. You’ll learn more about FormControl and the related services in Chapter 7, Building Reactive Forms in Angular. For now, all we need to know is that it’s an observable that emits the current value of the search bar whenever the value of the input element changes. Your first task is to connect the input element to the component. Import the FormControl class from @angular/forms and add the following property declaration to your header component.

 import​ { FormControl } ​from​ ​'@angular/forms'​;
 import​ { PhotosService } ​from​ ​'../photos.service'​;
 
 export​ ​class​ SearchHeaderComponent ​implements​ OnInit {
searchQuery = ​new​ FormControl();
constructor​(​private​ photos: PhotosService) { }

This declares the searchQuery property on the class and sets the value to a new instance of FormControl.

Finally, the component needs to know how to fetch the photos. Import the PhotosService and inject it into the component in the constructor method so the component can request new photos on every search change.

Now that the component can subscribe to changes in the search bar, it’s time to trigger a new search every time the input changes, much like the typeahead example from Chapter 4, Advanced Async. Add the following to the ngOnInit method:

 ngOnInit() {
this​.searchQuery.valueChanges
  .pipe(
debounceTime(333),
  switchMap(query =>
this​.photos.searchPhotos(query)
  )
  )
  .subscribe(photoList =>
  console.log(​'New search results:'​, photoList)
  );
 }

This observable emits each time the user changes the value in the search bar.

To avoid making too many AJAX requests in a short time, debounceTime is added, waiting 1/3 of a second before doing anything. If another event is emitted in that time, the timer is reset.

When the user pauses typing, tell the photo-fetching service to initiate another API request.

The page will auto-refresh with your changes. Type some gibberish in the search box; if you can see the logs in the console, the search is triggering correctly. Two parts down, one to go.

The final piece of the puzzle is results-list.component.ts. Open that file and import the photo search service like before (don’t forget to add it to the constructor as well). The component now has access to photo search service, but all the photo search service can do is search. The subscription lies in the header component—there’s no way to access it in the results list. Instead, the photo search service needs to be upgraded using a subject to allow it to share new results with any component that wants to subscribe. In this case, the photo search service uses a BehaviorSubject.

A BehaviorSubject is a simplified version of the ReplaySubject you used back in Chapter 5, Multiplexing Observables. Whereas the ReplaySubject stored an arbitrary number of events, the BehaviorSubject only records the value of the latest event. Whenever a BehaviorSubject records a new subscription, it emits the latest value to the subscriber as well as any new values that are passed in. The BehaviorSubject is useful when dealing with single units of state, such as configuration options, or in this example, the latest photos returned from the API. The application never needs to know previous states, just the latest photos to show.

Go back to photos.service.ts and make the following modifications (you’ll also need to import BehaviorSubject, by importing either all of RxJS or just BehaviorSubject specifically):

 @Injectable({
  providedIn: ​'root'
 })
 export​ ​class​ PhotosService {
  latestPhotos = ​new​ BehaviorSubject([]);
 constructor​(​private​ http: HttpClient) { }
 
  searchPhotos(searchQuery: string) {
 return​ ​this​.http.​get​<IPhoto[]>(​'http://localhost:4567/photos/search?q='
  + searchQuery)
  .subscribe(photos => ​this​.latestPhotos.next(photos));
  }
 }

BehaviorSubject (like any subject) is an observer as well as an observable; it has a next method you can use to alert all listeners that there’s new data. While searchPhotos could technically pass this.latestPhotos directly into the subscribe call, doing that would mean that latestPhotos will pick up on the completion of the AJAX call and call the done method. Since latestPhotos needs to be active through the entire life cycle of the program, the code ensures that only the next method is called.

Why are there just pictures of cats?

images/aside-icons/info.png It’s no fun embarking on a programming project and then realizing the internet has gone out. The local server is smart enough to detect when problems happen and responds with pictures of cats, so you can keep on learning.

This is a simple web app, so sharing a subject in a service suffices for now (there’s a race condition—can you find it?). In Chapter 8, Advanced Angular, you’ll learn about ngrx, a tool that will help you create apps with more scalable state management.

Now that the photos service is sharing the results of each search, you need to display those results on the page with the results-list component you generated earlier. First, go to app.module.html and add the results-list component to the page under app-search-header:

 <results-list></results-list>

Next, update results-list.component.ts with the following changes, importing where needed:

 export​ ​class​ ResultsListComponent ​implements​ OnInit {
  photos: IPhoto[];
 constructor​(​private​ photosService: PhotosService) { }
 
  ngOnInit() {
 this​.photosService.latestPhotos
  .subscribe(photos => {
 this​.photos = photos;
  });
  }
 }

This acts like every other call to subscribe that you’ve seen in the book. However, there’s a problem here—since the subject we’re listening in on lives for the entire life cycle of the program, so will this subscription. Every time the user loads this view, another subscription will be added to latestPhotos, slowing down the application and resulting in some very grumpy users. One could monkey around by adding an ngOnDestroy method that unsubscribes, but there’s a simpler way to do this: the async pipe.

Remove all of the contents of ngOnInit in the result list component, as well as the declaration of the photos property. The async pipe will handle all of the manual subscription management. Open the view and add the pipe to the *ngFor loop:

 <div ​*​ngFor=​"let photo of photosService.latestPhotos | async"​>

The async pipe lets you treat observables as synchronous data—imagine it as a superpowered subscribe call that can only be used in the view. It also knows when it’s no longer needed and cleans up any stray subscriptions so the app stays lean and fast, even after extended use. At this point, the basic search life cycle in your app should be working, but only to search photos. The next step is to use the HttpClient to send new data back to the server.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.135.249