Angular Pipes - aakash14goplani/FullStack GitHub Wiki

Topics Covered:
Introduction
Using Pipes
Passing parameters to Pipes
Chaining multiple Pipes
Custom Pipes
Passing parameters to Custom Pipes
Filters
Pure Pipes
Async Pipes

Introduction

  • Pipes are a feature built into Angular 2 which basically allows you to transform (synchronous and asynchronous) output in your template.

  • A basic example would be, you have a username property in your component, and you want to output that username in all uppercase but only when you output it.

    • So you don't want to change the property itself to uppercase because imagine you use that throughout your code and it should still be as you assigned it up there but you want to transform the way it is displayed once you render it to the screen.
    • Now for this, you could use a pipe, namely the uppercase pipe {{ username | uppercase }}, this actually is a built-in pipe that will uppercase or transform the output.

Using Pipes

  • To use a pipe, you can simply add the pipe symbol, hence the name pipes, beside the property specified in string-interpolation, e.g. {{ username | uppercase }}

  • Build-in Pipes


Passing parameters to Pipes

  • We can add a parameter to configure the pipe by adding a colon and this is the case for any pipe, you configure it, you pass a parameter to it by adding a colon.

  • E.g. the date pipe can take a parameter and we simply add it after colon: {{ server.started | date: 'dd MMMM, yyyy' }}

  • If you would have multiple parameters, you simply separate them with additional colons. {{ property | pipe_name: param_1: param_2: param_n }}


Chaining multiple Pipes

  • Now with our pipes being added and parameterized, it's also important to know that you can combine pipes in Angular. You can simply chain pipes by adding a pipe symbol after a pipe. e.g. {{ server.started | date: 'dd MMMM, yyyy' | lowercase }}

  • Now the order might be important, generally it will be parsed from left to right.

    • server.started property gets into this pipe.
    • the date pipe gets applied to our server.started property output.
    • the lowercase pipe then gets applied to the result of this operation
  • Now if I switch positions here and put the lowercase pipe first and then parse the date, we will get an error that this argument here is not correct for the lowercase pipe, it doesn't know how to parse it because it is a date and not a string!

  • So this is why it's important to watch the order, you should apply the pipes in the order you want to transform your input. Keep this order in mind, from left to right, this is how it gets executed and how pipes get applied.


Custom Pipes

  • Use Case: Create a custom pipe to camel-case the output.

  • Create a file (or use command ng g p Pipe_Name --skipTests=true) and the class should implement an interface PipeTransform from @angular/core.

  • This interface forces you to implement transform() method. transform(value: any, ...args: any) needs to receive the value which should get transformed. And then we would receive a list of arguments - here you can pass any number of arguments.

  • Our pipe here doesn't take any arguments, so I will omit any other arguments here in the transform() method, so we only received the value.

  • transform() always needs to return something because a pipe is just you put something in, you get something out & so you need to get something out otherwise it won't work.

  • Within transform(), we can write logic for converting string to camel-case

    transform(value: string) {
       const valueArray: string[] = value.split(' ');
       let tempValue = '';
       for (const currentValue of valueArray) {
          // value = aakash
          tempValue += currentValue.charAt(0).toUpperCase(); // tempValue = A
          tempValue += currentValue.substr(1); // tempValue = A + akash
          tempValue += ' '; // tempValue = A + akash + ' '
       }
       return tempValue.trim();
    }
  • One last step, we need to add a special decorator, the @Pipe decorator which is also imported from @angular/core. In the @Pipe decorator, you can specify the name for the pipe by simply adding name and then for example, camelcase.

    @Pipe({
     name: 'camelcase'
    })
    export class CamelCase implements PipeTransform { ... }
  • Now by setting it up like this, you can now go to your template and add this pipe against property you would like to transform, e.g., {{ server.name | camelcase }}

  • Summary:

    • Add this pipe decorator,
    • Make sure you have the transform() method possibly enforced by adding the PipeTransform interface
    • By adding your pipe to the declarations array in your app module (auto added by IDE if you create file using ng g p command).
    • With that, you're good to use your own pipe in template.

Passing parameters to Custom Pipes

  • Use Case: Modify camel-case pipe to accept one boolean parameter, if parameter is true return came-case value else return value as it is.

  • You can allow the user to pass a parameter to the pipe which you receive as second argument in the transform() method.

    transform(value: string, proceed: boolean) {
       if (proceed) {
           const valueArray: string[] = value.split(' ');
           let tempValue = '';
           for (const currentValue of valueArray) {
               // value = aakash
               tempValue += currentValue.charAt(0).toUpperCase(); // tempValue = A
               tempValue += currentValue.substr(1); // tempValue = A + akash
               tempValue += ' '; // tempValue = A + akash + ' '
           }
           return tempValue.trim();
       } else {
           return value;
       }
    }
  • To pass a parameter in template, add a colon and then the value i.e. {{ server.name | camelcase: true }}

  • You can of course add multiple parameters, you would add another parameter simply by adding another argument here. And then with that added, you could simply add another colon and your second parameter.

  • You could also chain custom pipes with built-in types.


Filters

  • Use Case: I want to allow the user to filter servers on status = stable

  • I need input text field and I want to use two-way data binding to bind it, so ngModel and bind this to filterInput

    <div class="form-group">
       <label for="filterInput">Filter Input</label>
       <input type="text" class="form-control" [(ngModel)]="filterInput">
    </div>
    filterInput: string = '';
  • Now I want to build a pipe which allows me to then only view the servers which do indeed fulfill the requirements here.

  • Create a new pipe and implement transform() method from PipeTransform interface. So in here, I want to allow the user to filter and I will indeed get an argument:

    • first argument is the value of field on which we apply this pipe
    • second argument is string value that user entered in text-box
    • third argument is property-name of array on which we want to apply this filter to
  • I want to implement logic which allows me to only return the elements of the array which fulfill this filter string here where the status of the server matches this filter:

    transform(value: any, filterString: string, propertyName: string): any {
     if (filterString && propertyName) {
        const resultArray = [];
        for (const server of value) {
          if (server[propertyName] === filterString) {
            resultArray.push(server);
          }
        }
        return resultArray;
      } else {
        return value;
      }
    }
  • With this in place, we apply it here in the ngFor loop. And this might sound strange because before we only used it in string interpolation but keep in mind that pipes transform your output and an ngFor loop is simply part of your output.

    <li class="list-group-item" *ngFor="let server of servers | filter: filterInput: 'status'">
       <span class="badge">{{ server.status }}</span>
       <strong>{{ server.name | camelcase: true }}</strong> | 
       {{ server.instanceType | uppercase }} | 
       {{ server.started | date: 'dd MMMM, yyyy' | lowercase }}
    </li>

Pure Pipes

  • In the last section, we created filter pipe which allows us to filter our servers for their state. We do have a certain issue with it though, if we allow the user to add a new server, you don't see it getting added here but it is there, you can see if I remove the filter altogether.

  • This is actually not a bug. The reason for this behavior is that Angular is not rerunning our pipe on the data whenever this data changes. So as soon as we change what we enter here in input text and even if we would only add a blank space and then remove it, we would update our pipe again.

  • So adding the input or changing the input of the pipe will trigger a recalculation, i.e. trigger the pipe being applied to the data again but changing the data won't trigger this and this is a good behavior because otherwise, Angular would have to run this pipe or rerun the pipe whenever any data on the page changes, this would be really bad because that would cost a lot of performance

  • If you want to enforce it being updated even if you are in filter mode- you can do this by adding a second property to the pipe decorator: pure and you can set it to false. By default, this is true and doesn't need to be added.

    @Pipe({
       name: 'filter',
       pure: false
    })
    export class FilterPipe implements PipeTransform { ... }

Async Pipes

  • async pipe - is an build-in pipe that helps us with handling asynchronous data.

  • Use Case: We need to display status (online/offline) of our application by checking our database. Now this is going to take some time and we can use async here.

  • I'll create an Promise to mimic this behavior:

    status: string[] = ['Online', 'Offline'];
    bgStyle: {} = {color: 'black'};
    appStatus = new Promise((resolve, reject) => {
      setTimeout(() => {
        const item = Math.random() > 0.5 ? 1 : 0;
        if (item === 0) {
          this.bgStyle = {color: 'green'};
        } else {
          this.bgStyle = {color: 'red'};
        }
        resolve(this.status[item]);
      }, 2000);
    });
    <h4>Application Status: <span [ngStyle]="bgStyle">{{ appStatus | async }}</span></h4>
  • With this in place, In output screen, there's nothing there at the beginning but after two seconds, you see online/offline and this is what async does, it recognizes that this is a promise/observables, it will simply recognize that something changed and it will print this data to the screen.

From Deborah

  • We access an observable directly in a template using the async pipe.
  • The async pipe automatically subscribes to the observable when the component is initialized.
  • It returns each emitted value from that observable.
  • When a new item is emitted, the component is marked to be checked for changes and runs change detection, modifying the UI as needed.
  • The async pipe automatically unsubscribes when the component is destroyed to avoid potential memory leaks.
  • In example below, we pipe data through the async pipe to access its emitted values.
    • This automatically subscribes for us and handles unsubscribing.
    • We'll use the as clause to assign each emitted item to a variable we use in other places in the template.
    • So products$ is the observable, and products is the array of products emitted from that observable. We use the products variable here in ngfor as we iterate through, displaying each product in the template.
    products$: Observable<Product[]>;
    ngOnInit(): void {
       this.products$ = this.productService.getProducts();
    }
    <table class="table mb-0" *ngIf="products$ | async as products">
    ...
    <tbody *ngFor="let product of products">
    ...
⚠️ **GitHub.com Fallback** ⚠️