Angular Devs Beware: Are You Guilty of This Performance-Killing Practice?
Tags: webdev, angular, tutorial
Published: 2023-04-05 18:56:15

Hello, fellow Angular enthusiasts! Today, we'll dive into an essential Angular optimization technique that can make your application run smoother and faster. As an Angular aficionado, I've seen countless developers get tripped up by direct function calls in templates. But fear not, we'll unravel this enigma together and have a bit of fun along the way. So, grab your favorite beverage, get comfy, and let's get cracking!

#### The Problem: Direct Function Calls in Templates
_Picture this_: you're building an Angular app with a simple search box that filters a list of items. In your component's template, you call a function to filter the items based on the search input. It seems harmless at first, but little do you know that this decision will come back to haunt you.

> You see, direct function calls in Angular templates can lead to performance issues, especially in complex applications. When Angular runs change detection, it executes these functions every single time, even if nothing has changed. This can lead to a bloated, sluggish app that leaves your users scratching their heads in frustration.

That's where our hero comes in: properties and observables! These trusty sidekicks can help you manage your data more efficiently and give your app the performance boost it needs.

#### The Solution: Embrace Properties and Observables
We'll explore how to replace direct function calls in Angular templates with properties and observables. Along the way, we'll uncover some advanced techniques to keep your app running smoothly and efficiently.

### Step 1: Set up your component
First, let's create a component that displays a list of items and includes a search input box. To keep things simple, we'll use an array of strings as our data source.

```typescript
import { Component } from '@angular/core';

@Component({
  selector: 'app-search',
  templateUrl: './search.component.html',
  styleUrls: ['./search.component.scss']
})
export class SearchComponent {
  items = [
    'Apple',
    'Banana',
    'Orange',
    'Pineapple',
    'Strawberry',
    'Grape',
    'Mango',
    'Watermelon'
  ];

  searchText = '';
  filteredItems: string[] = [];

  constructor() {
    this.filteredItems = this.items;
  }
}
```
In this example, we've initialized the `filteredItems `array with the original items array. This will be our starting point for the filtering process.

### Step 2: Create a method to filter the items
Now that our component is set up, we need a way to filter the items based on the search input. To do this, let's create a method called `onSearchTextChange()` that updates the `filteredItems` array when the search text changes.

```typescript
onSearchTextChange() {
  const searchText = this.searchText.toLowerCase();
  this.filteredItems = this.items.filter(item =>
    item.toLowerCase().includes(searchText)
  );
}
```
This method takes the current `searchText`, converts it to lowercase, and then filters the items based on whether they contain the search text.

### Step 3: Update the template to avoid direct function calls
In our component's template, we'll start by binding the `searchText `property to the input box using `[(ngModel)]`. This two-way binding ensures that our `searchText `property stays in sync with the input value.

Next, we'll use the (input) event to call the `onSearchTextChange()` method we created earlier. This ensures that our method is only called when the search input actually changes, rather than on every change detection cycle.

Finally, we'll use the `*ngFor` directive to loop through the `filteredItems` array and display the results.

Here's the updated template:

```html
<input
  [(ngModel)]="searchText"
  (input)="onSearchTextChange()"
  placeholder="Search items"
/>

<ul>
  <li *ngFor="let item of filteredItems">
    {{ item }}
  </li>
</ul>
```

### Step 4: Embrace the power of observables
Now that we've set up our component to avoid direct function calls in the template, let's take it one step further and harness the power of observables. Observables are a fantastic tool in Angular's arsenal, enabling us to efficiently manage and react to data changes.

**First, we'll need to import Subject and Observable from the rxjs library:**

```typescript
import { Subject, Observable } from 'rxjs';
```
Next, create a Subject called `searchTextChanged$` that will emit a new value whenever the search text changes:

```
typescript
private searchTextChanged$ = new Subject<string>();
```
Now, update the `onSearchTextChange()` method to emit the current `searchText` value whenever it's called:

```typescript
onSearchTextChange() {
  this.searchTextChanged$.next(this.searchText);
}
```
Finally, we'll create an Observable called `filteredItems$` that listens for changes to `searchTextChanged$` and updates the `filteredItems` array accordingly. To do this, we'll use the `debounceTime `and `distinctUntilChanged `operators from `rxjs`, which help ensure that we're only processing new and unique search text values.

```typescript
filteredItems$: Observable<string[]>;

constructor() {
  this.filteredItems$ = this.searchTextChanged$.pipe(
    debounceTime(300),
    distinctUntilChanged(),
    map(searchText =>
      this.items.filter(item =>
        item.toLowerCase().includes(searchText.toLowerCase())
      )
    )
  );
}
```

And that's it! With these changes, our search component now avoids direct function calls in the template, and efficiently updates the `filteredItems` array using observables.

### Step 5: Use the `async pipe` for cleaner templates
While our search component works well, we can make our template even cleaner by using the `async pipe`. The `async pipe` subscribes to an observable and automatically updates the view whenever new data arrives. It also takes care of unsubscribing from the observable when the component is destroyed, preventing memory leaks.

Let's update our template to use the `async pip`e with the `filteredItems$` observable:

```html
<input
  [(ngModel)]="searchText"
  (input)="onSearchTextChange()"
  placeholder="Search items"
/>

<ul>
  <li *ngFor="let item of filteredItems$ | async">
    {{ item }}
  </li>
</ul>
```
Now our template is even cleaner and more readable!

### Comparision of `onSearchTextChange()` for the direct and observable based approach

> **Direct function call in the template with onSearchTextChange()**: When using a direct function call in the template, Angular runs the function during each change detection cycle. Even though the function is bound to the input event, Angular will still check the function call for changes each time it runs change detection. This can lead to poor performance, especially if the function is computationally expensive [in this case it is filtering in every change detection] or the application has frequent updates.
> **Observable-based onSearchTextChange()**: In this case, the function `onSearchTextChange(`) is called only when the input event is emitted, thanks to event binding. The function itself doesn't perform any filtering logic. Instead, it emits a new value to the `searchTextChanged$` subject, which triggers the observable pipeline to perform the filtering. This ensures that filtering only occurs when the input value changes, leading to better performance compared to direct function calls in templates.

## Now, let's explore some advanced techniques and best practices to make our Angular apps even more efficient and performant.

---

### Step 6: Pure pipes for better performance
Angular provides a number of built-in pipes for transforming data in templates. By default, these pipes are "pure," meaning they only recompute their output when their input values change. This can lead to significant performance improvements, as Angular won't need to re-evaluate the pipe's output during every change detection cycle.

Consider creating custom pure pipes when you have a data transformation that is computationally expensive, but its output is solely determined by its input. Here's an example of a custom pure pipe that filters a list of items based on a search term:

```typescript
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'filterItems',
  pure: true,
})
export class FilterItemsPipe implements PipeTransform {
  transform(items: string[], searchText: string): string[] {
    return items.filter(item =>
      item.toLowerCase().includes(searchText.toLowerCase())
    );
  }
}
```

To use our custom pipe in the template, simply replace the *ngFor directive with the following:

```html
<li *ngFor="let item of items | filterItems: searchText">
  {{ item }}
</li>
```

Now our filtering logic is encapsulated within a custom, pure pipe, making our component even more efficient!

### Now, let's compare the observable-based onSearchTextChange() with the custom pure pipe solution.

> **Observable-based onSearchTextChange():** In this case, the function is called when the input event is emitted. The function itself doesn't perform any filtering logic. Instead, it emits a new value to the `searchTextChanged$` subject, which triggers the observable pipeline to perform the filtering. This ensures that filtering only occurs when the input value changes, leading to better performance compared to direct function calls in templates.
> **Custom pure pipe solution**: In this case, we use a custom pure pipe to handle the filtering logic. Pure pipes are more efficient because Angular only recomputes their output when their input values change. This means that the filtering logic is only executed when the input value or the list of items change. This approach eliminates the need for a separate method like `onSearchTextChange()` and encapsulates the filtering logic within the pipe.


### Wrapping up
Congratulations! You've completed the article on avoiding direct function calls in Angular templates. We've covered everything from the basics of property binding and event handling to advanced techniques with observables and pure pipes. By implementing these best practices, you're well on your way to building high-performance Angular applications.

Remember, Angular is a vast and powerful framework with endless possibilities. Keep exploring, learning, and sharing your knowledge with the community. As a fellow Angular enthusiast, I can't wait to see what you create! Happy coding!