Wednesday, June 17, 2020

5 USAGE IDEAS FOR ANGULAR PIPES

5 usage ideas for Angular pipes thumbnail

Pipes are a useful feature in Angular. They are a simple way to transform values in an Angular template. There are some built in pipes, but you can also build your own pipes.

A pipe takes in a value or values and then returns a value. This is great for simple transformations on data but it can also be used in other unique ways. This post will highlight a few useful or unique use cases that we found for pipes.

The ratings on each of the points indicate how much I believe this particular use case abuses the pipe framework.

1. Return Default Values

The functionality behind a Default Pipe is pretty self explanatory—if a value is falsy, use a default value instead. The implementation is also very simple.

@Pipe({name: 'default', pure: true})
export class DefaultPipe {
   transform(value: any, defaultValue: any): any {
       return value || defaultValue;
   }
}

The default pipe uses Angular’s ability to pass multiple values into the pipe to get both the value and the default value.

An example of using the default pipe in Angular would be:

<lucid-icon [name]="folder.icon | default:'Folder'"></lucid-icon>

In the example above, either the folder has an icon that is used or the Folder icon is used as a default. You can play with the example code using the Default Pipe in the Plunker below.

Hackiness RatingDescription
This use is along the lines of what pipes are intended to do.

2. Debounce Input

Debounce Pipe is much more technically interesting than the Default Pipe. The basic premise of the idea is that the passed-in value can change frequently, but the actual value returned from the pipe will not change until the value has remained changed for a certain period of time. This can be very useful when listening to user input but not wanting to update the user interface until after the user has finished typing.

You can see the implementation below:

@Pipe({name: 'debounce', pure: false})
export class DebouncePipe {
   private currentValue: any = null;
   private transformValue: any = null;
   private timeoutHandle: number = -1;

   constructor(
       private changeDetector: ChangeDetectorRef,
       private zone: NgZone,
   ) {
   }

   transform(value: any, debounceTime?: number): any {
       if (this.currentValue == null) {
           this.currentValue = value;
           return value;
       }
       if (this.currentValue === value) {
           // there is no value that needs debouncing at this point
           clearTimeout(this.timeoutHandle);
           return value;
       }
       if (this.transformValue !== value) {
           // there is a new value that needs to be debounced
           this.transformValue = value;
           clearTimeout(this.timeoutHandle);
           this.timeoutHandle = setTimeout(() => {
               this.zone.run(() => {
                   this.currentValue = this.transformValue;
                   this.transformValue = null;
                   this.changeDetector.markForCheck();
               });
           }, typeof debounceTime == 'number' ? debounceTime : 500);
       }
       return this.currentValue;
   }
}

The Debounce Pipe takes in a value, and then if the value has changed since the last time the debounce completed (or it is null), it will wait either the time passed in as the second value or 500 ms if a time wasn’t provided, and then apply the new value.

An example of using it in Angular would be:

<div
    *ngIf="hasInputError(contentOption) | debounce"
    class="error-message"
>
    {{errorMessage(contentOption)}}
</div>

You can play with the example code using the Debounce Pipe in the Plunker below.

Hackiness RatingDescription
This pipe doesn’t generally follow the idea of taking in a value and returning a result value. Instead, it delays returning in the latest passed-in value, which means it has to be an impure pipe because it returns different values for the same input depending on the time. However, generally it works well and is not too abusive of the framework.

3. Get the Position of an Element

Angular has a handy feature that lets you assign an element or component to a variable and then reference that variable within that template. The Element Position Pipe takes advantage of that feature and allows you to pass an element to the pipe and have it return the position. This can be a useful feature for deciding where to position a pop-up or some other element.

Here is the implementation:

@Pipe({name: 'elementPosition', pure: true})
export class ElementPosition {
   transform(value: HTMLElement, xLerp: number, yLerp: number): Point|null {
       if (value != null) {
           const boundingRect = value.getBoundingClientRect();
           return {
               x: boundingRect.left + xLerp * boundingRect.width,
               y: boundingRect.top + yLerp * boundingRect.height,
           };
       } else {
           return null;
       }
   }
}

The two numbers that are passed in with the element are used to decide where on the element the position should be. The first value is used in the x position and is multiplied by the width. So if you want to get the position of the right side of the element, you would pass in 1 for the first parameter, which would mean x + 1 * width. The second parameter is the same but for the y value and the height.

An example of using the Element Position Pipe in Angular would be:

<div
   #titleElement
   (click)="expanded = !expanded"
>
   {{title}}
</div>
<ng-container popup [visible]="expanded">
   <lucid-menu
       *popupContent
       [items]="menuOptions"
       [position]="titleElement | elementPosition:0:1"
   ></lucid-menu>
</ng-container>

 

In this simplified snippet, the menu is getting the bottom-left position of the `titleElement` passed into its `[position]` input.

Hackiness RatingDescription
This use is fairly hacky because it doesn’t work in Web Worker or service-side environments. It also returns a position that is relative to the browser window and not the nearest relative or absolute positioned ancestor.

4. Feign Natural Typing

A flashy feature that I have seen throughout the web is animating text as if it was being typed by a user. An intuitive way to implement such a feature in Angular would be using pipes.

Below is a simple implementation of a Natural Typing Pipe:

@Pipe({name: 'naturalType', pure: false})
export class NaturalType {
   private typed: string = '';
   private target: string = '';
   private currentIndex: number = -1;
   private timeoutHandle: number = -1;

   constructor(
       private changeDetector: ChangeDetectorRef,
       private zone: NgZone,
   ) {
   }

   transform(value: string, mintypingSpeed: number = 30): any {
      if (this.target !== value) {
       clearTimeout(this.timeoutHandle);
       this.typed = '';
       this.currentIndex = -1;
       this.target = value;
       this.typeNextCharacter(mintypingSpeed);
      }
      return this.typed;
   }
   
   private typeNextCharacter(mintypingSpeed: number) {
    this.currentIndex++;
    this.typed = this.target.substr(0, this.currentIndex);
    this.changeDetector.markForCheck();
    if (this.typed !== this.target) {
      const time = Math.round(Math.random() * 70) + mintypingSpeed;
      this.timeoutHandle = setTimeout(()=> {
        this.zone.run(() => this.typeNextCharacter(mintypingSpeed));
      },time);
    }
   }
}

Usage of the pipe is pleasantly simple—simply pass a string through the pipe.

{{value | naturalType}}

You can play with the example code using the Natural Typing Pipe in the Plunker below.

Hackiness RatingDescription
In my opinion, this is actually a fairly elegant way to implement this feature. However, it does have to be an impure pipe because the value that it returns is based on the input and time, not just the input value.

5. Track User Input

Analytics and recently used lists are two features that come to mind when I think of tracking user input. It seems possible to do that with a pipe. So why not?

First, we will need a tracking service.

@Injectable()
export class TrackingService {
  private wordsUsed: Set = new Set();
  
  public addWordUsed(word: string) {
    this.wordsUsed.add(word);
  }
  
  public getWords(): string[] {
    return Array.from(this.wordsUsed);
  }
 
}

Then we will need a pipe to feed the service.

@Pipe({name: 'track', pure: true})
export class TrackingPipe {
   constructor(
       private trackingService: TrackingService,
   ) {
   }

   transform(value: string): string {
       this.trackingService.addWordUsed(value);
       return value;
   }
}

Consuming the feed is then simple with Angular.

@Component({
  ...
  template: `
    ... 
    <div *ngFor="let word of getWords()">{{word}}</div>
  `,
})
export class AppComponent {
  ...
  constructor(private trackingService: TrackingService) {
    
  }
  
  public getWords(): string[] {
    return this.trackingService.getWords();
  }
}

After running, it doesn’t render the list immediately. Checking the console we have this error: ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. This error makes sense. When the change detector runs, it transforms the value using the tracking pipe which modifies the list. Since the value changed during change detection, the change detection is then out of date and has to run again. If it changes during change detection … ∞. Since we were smart ?, we put the debounce pipe that we built earlier in front of the tracker pipe ({{inputValue | debounce:200 | track}}), so we know that the value isn’t going to actually change every change detection and cause an infinite loop. So we can just make our logging run after an async callback, and then everything should work.

@Pipe({name: 'track', pure: true})
export class TrackingPipe {
   constructor(
       private trackingService: TrackingService,
       private changeDetector: ChangeDetectorRef,
       private zone: NgZone,
   ) {
   }

   transform(value: string): string {
       Promise.resolve().then(() => {
        this.trackingService.addWordUsed(value);
        this.zone.run(() => this.changeDetector.markForCheck()); 
       });
       return value;
   }
}

If we wanted to, we could even log the values over the network and then we wouldn’t have to fake the async! ?

You can play with the example code using the Tracking Pipe in the Plunker below.

Hackiness RatingDescription
This pipe doesn’t actually do any transformations on the value, which breaks the whole paradigm of a pipe. It has to pretend to log values in an asynchronous way in order to not modify component state during change detection. It is stateful. There is no benefit over just injecting the service into the component and logging the values there. But … it was possible and we did it ?.

Angular How-to: Implement Feature Flags



 how to turn features on and off via configuration or from a database. This may include hiding and disabling UI elements, changing code flow, or preventing routing to components.

In an agile development environment, there may be partially completed features or features with external dependencies that are not ready. Instead of relying on multiple code branches, instead you may opt for deploying code with a feature turned off. Later, that feature should be turned on via a configuration or database change. This post provides sample code that you can use to implement feature flags in your Angular app.

Option 1: Define features in a config file

One option for storing feature flags is in a JSON configuration file. Refer to this post for an explanation on consuming an editable configuration file.

config.dev.json

{
  . . .
  "features": {
    "feature1": true,
    "feature2": false
  }
}

app-config.model.ts

export interface IAppConfig {
  . . .
  features: {
    [name: string]: boolean;
  };
}

Create a feature flag service

Centralize the management of determining whether a feature is turned on or off into an Angular service.

feature-flag.service.ts

import { Injectable } from '@angular/core';
import { AppConfig } from '../../app.config';
 
@Injectable()
export class FeatureFlagService {
 
  featureOff(featureName: string) {
    // Read the value from the config service
    if (AppConfig.settings.features.hasOwnProperty(featureName)) {
        return !AppConfig.settings.features[featureName];
    }
    return true; // if feature not found, default to turned off
  }
 
  featureOn(featureName: string) {
    return !this.featureOff(featureName);
  }
}

Option 2: Use an API instead of a config file to get feature flags

Instead of reading feature flags from a config file, another option is to retrieve the features from the server via an API that could read these values from a database. The feature-flag service could be adapted to fetch the feature flag values as soon as the app starts.

feature-flag.service.ts

export class FeatureFlagService {
 
  private _featureFlags: Array<string> = []; // A list of all features turned ON
  private _initialized = false;
 
  constructor(private featureFlagDataService: FeatureFlagDataService) { }
 
  featureOff(featureName: string) {
    return !this.featureOn(featureName);
  }
 
  featureOn(featureName: string) {
    if (!featureName) {
      return true;
    }
    // Find the feature flag that is turned on
    return this._featureFlags && !!this._featureFlags.find(feature => {
      return feature === featureName;
    });
    // if feature not found, default to turned off
  }
 
  get initialized() {
    return this._initialized;
  }
 
  // This method is called once and a list of features is stored in memory
  initialize() {
    this._featureFlags = [];
    return new Promise((resolve, reject) => {
      // Call API to retrieve the list of features and their state
      // In this case, the method returns a Promise,
      // but it could have been implemented as an Observable
      this.featureFlagDataService.getFeatureFlags()
        .then(featureFlags => {
          this._featureFlags = featureFlags;
          this._initialized = true;
          resolve();
        })
        .catch((e) => {
          resolve();
        });
  });
}

Create attribute directives to hide and disable elements

To hide an element based on whether a feature is turned on or off, use the following code to create a directive. This will enable the Angular templates to use this syntax:

<div [myRemoveIfFeatureOff]="'feature1'">
<div [myRemoveIfFeatureOn]="'feature2'">

remove-if-feature-off.directive.ts

import { Directive, ElementRef, Input, OnInit } from '@angular/core';
import { FeatureFlagService } from '../../services/feature-flag.service';
 
@Directive({
  selector: '[myRemoveIfFeatureOff]'
})
export class MyRemoveIfFeatureOffDirective implements OnInit {
  @Input('myRemoveIfFeatureOff') featureName: string;
 
  constructor(private el: ElementRef,
              private featureFlagService: FeatureFlagService) {
  }
 
  ngOnInit() {
    if (this.featureFlagService.featureOff(this.featureName)) {
      this.el.nativeElement.parentNode.removeChild(this.el.nativeElement);
    }
  }
}

remove-if-feature-on.directive.ts

. . .
@Directive({
  selector: '[myRemoveIfFeatureOn]'
})
export class MyRemoveIfFeatureOnDirective implements OnInit {
  @Input('myRemoveIfFeatureOn') featureName: string;
  // Same as myRemoveIfFeatureOff except call featureOn()
}

Call the feature flag service elsewhere in the app

In addition to the attribute directives, the feature flag service can be called throughout the app. For instance, a menu service could use the featureOn method to hide menu items if the user does not have the required permission.

menu.service.ts

private showMenuItem(featureName: string) {
  return this.featureFlagService.featureOn(featureName);
}

Prevent routing to component

If access to a component should be prevented when a feature is turned off, this can be accomplished by using the canActivate option available in Angular’s router.

my-routing.module.ts

{
  path: 'my-component',
  component: MyComponent,
  canActivate: [FeatureFlagGuardService],
  data: { featureFlag: 'feature1' }
},

feature-flag-guard.service.ts

@Injectable()
export class FeatureFlagGuardService implements CanActivate {
 
  constructor(private featureFlagService: FeatureFlagService) { }
 
  canActivate(route: ActivatedRouteSnapshot): Promise<boolean> | boolean {
    // Get the name of the feature flag from the route data provided
    const featureFlag = route.data['featureFlag'];
    if (this.featureFlagService.initialized) {
      if (featureFlag) {
        return this.featureFlagService.featureOn(featureFlag);
      }
      return true; // no feature flag supplied for this route
    } else {
      const promise = new Promise<boolean>((resolve, reject) => {
        this.featureFlagService.initialize()
          .then(() => {
            if (featureFlag) {
              resolve(this.featureFlagService.featureOn(featureFlag));
            } else {
              resolve(true);
            }
          }).catch(() => {
            resolve(false);
          });
      });
      return promise;
    }
  }
}

This implementation of feature flags has proven to be helpful on my current project and has allowed us to proceed with the development of new features without the risk of introducing new functionality before QA is ready. I’ve introduced a few enhancements along the way, including:

  • addition of a scope for the feature – meaning the feature is available only for certain criteria
  • an option for the route guard to check for any of an array of features’ being turned on
if (this.featureFlagService.anyFeatureOn(['feature1', 'feature2', 'feature3']))
 
{  canActivate: [FeatureFlagGuardService],
   data: { anyFeatureFlags: ['feature1', 'feature2', 'feature3'] }
}

Also, it is worth highlighting that if a feature name is misspelled either in the configuration or in the code, then it is considered turned off. The effect of this approach is that a feature will not accidentally be turned on.

Free hosting web sites and features -2024

  Interesting  summary about hosting and their offers. I still host my web site https://talash.azurewebsites.net with zero cost on Azure as ...