Table of Contents
When developing single-page applications, one of the most common patterns you encounter is cross-component communication. One specific case I recently had to deal with is this: the page has two components: One for displaying the search for and the other for showing the search result.
Here is how to application works:
As you can see, after the user enters the search term and clicks Search, the results are displayed as a list below the search box.
How to run the demo
If you want to start using the demo right away, here is how
Prepare the API
docker run -p 9090:9090 codingpuss/fake-cat-api
This will run the API and you can access it at http://localhost:9090.
Endpoints details are available here: https://github.com/datmt/Fake-Cat-API
Start the frontend app
Clone the frontend repository from here: https://github.com/datmt/Fake-Cat-Frontend
Then run npm install && npm start
.
You can try the app at http://localhost:4200.
The angular app’s structure
The following is the sequence diagram of API calling and updating.
In terms of components’ structure, the app component is the parent of Search and List components:
<h1>Cat search app</h1> <app-cat-search (catEmitter)="onCatArrive($event)"></app-cat-search> <app-cat-list-result [cats]="cats"> </app-cat-list-result>
The application flow
From the sequence diagram above, you can infer the full cycle from searching to displaying results:
- User enters search term
- User hit search button
- In search component,
CatService
is called CatService
sends http request to the API- API returns the result
- When the API results arrive, the Search component emit an event with updated data
- App component listens to that event and update the list of cats
- Since cats is an input of the List component, list of cats in the List component is updated
- New list of cats is renderred on the browser
Let’s follow the flow in code:
This is the Search component where we have the search box
<input type="text" [(ngModel)]="query" placeholder="search cat by name"> <button (click)="search()">Search</button>
When a user clicks on the search button, the search()
function is called.
Here is the search function:
export class CatSearchComponent implements OnInit { query: string = ''; constructor(private catService: CatService) { } @Output() catEmitter = new EventEmitter<Cat[]>(); ngOnInit(): void { } search() { this.catService.findByName(this.query).subscribe(t => this.catEmitter.emit(t)); } }
As you can see, the search function call the findByName
function in CatService
to retrieve the results from API.
Details of findByName
as follow:
export class CatService { private catUrl = 'http://localhost:9090/cats/'; constructor( private http: HttpClient ) { } getAll() : Observable<Cat[]> { return this.http.get<Cat[]>(this.catUrl + 'list') } findByName(query: string): Observable<Cat[]> { return this.http.get<Cat[]>(this.catUrl + 'search/' + query) } }
Go back to the search function in the search component, you can see that after the results from API arrives, the event catEmitter
will emit the results. As mentioned above, the App component listens to this event and the handler is onCatArrive
<app-cat-search (catEmitter)="onCatArrive($event)"></app-cat-search>
What onCatArrive
does is it updates the variable cats
to the value of the data of catEmitter
:
export class AppComponent { cats: Cat[] = []; title = 'fake-cat'; onCatArrive($event: Cat[]) { this.cats = $event; } }
Since the List components take cats
as an input, the value of cats inside it will be updated accordingly:
app.component.html
<app-cat-list-result [cats]="cats">
cat-list-result.component.ts
export class CatListResultComponent implements OnInit { @Input() cats!: Cat[]; constructor( private catService: CatService ) { } ngOnInit(): void { } }
cat-list-results.component.html
<ul *ngIf="cats.length > 0; else noCat"> <li *ngFor="let cat of cats">{{cat.name}}</li> </ul> <ng-template #noCat> <h3>No cat available</h3> </ng-template>
I build softwares that solve problems. I also love writing/documenting things I learn/want to learn.