angular Archives - Learn Smart Coding https://blogs.learnsmartcoding.com/tag/angular/ Everyone can code! Wed, 05 Jan 2022 01:50:49 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.5 209870635 How to implement time-based caching in Angular using HTTP Interceptor https://blogs.learnsmartcoding.com/2022/01/05/how-to-implement-time-based-caching-in-angular-using-http-interceptor/ https://blogs.learnsmartcoding.com/2022/01/05/how-to-implement-time-based-caching-in-angular-using-http-interceptor/#comments Wed, 05 Jan 2022 01:50:49 +0000 https://karthiktechblog.com/?p=1013 Introduction Implementing time-based caching in Angular using HTTP Interceptor is simple and easy. This post shows step by step process of creating simple caching logic for all outgoing HTTP GET API calls. Why implement caching? Angular is used to develop SPA applications. Most of the pages in an application will call external HTTP APIs (GET) […]

The post How to implement time-based caching in Angular using HTTP Interceptor appeared first on Learn Smart Coding.

]]>
Introduction

Implementing time-based caching in Angular using HTTP Interceptor is simple and easy. This post shows step by step process of creating simple caching logic for all outgoing HTTP GET API calls.

How to implement time-based caching in Angular using HTTP Interceptor

Why implement caching?

Angular is used to develop SPA applications. Most of the pages in an application will call external HTTP APIs (GET) to retrieve data in order to display it on the screen. Let’s say, a user navigates to the same page multiple times which is normal.

This will lead to calling the same GET API multiple times though the data is not changed. A typical example is to get a list of product categories on a page which won’t change so often or even if doesn’t change at all.

By implementing caching in the front end or we can say as client-side, we will avoid calling the same API often. This will save some network calls and since data is served from the local cache, the application will be faster and give a rich user experience.

Short video tutorial

What is HTTP Interceptor?

In simple words, HTTP Interceptor in Angular is used to intercept the outgoing HTTP API calls and perform some action. E.g. we can intercept the outgoing call and add or modify the request/response. HTTP Interceptors transform the outgoing request before passing it to the next interceptor in the chain, by calling next.handle(transformedReq).

To know more about HTTP Interceptor, refer here.

Implementation of Caching

First, we will create a service named “HttpCacheService” which does the below actions.

  1. Used to retrieve saved response of a particular URL.
  2. For any new outgoing request, used to save the URL and its response.
  3. Invalidate a particular URL response.
  4. Invalidate the entire cache.
import { Injectable } from '@angular/core';
import { HttpResponse } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class HttpCacheService {

  private requests: any = { };

  constructor() { }

  get(url: string): HttpResponse | undefined {
    return this.requests[url];
  }

  put(url: string, response: HttpResponse): void {
    this.requests[url] = response;
  }

  invalidateUrl(url: string): void {
    this.requests[url] = undefined;
  }

  invalidateCache(): void {
    this.requests = { };
  }

}

Implementing HTTP Interceptor

We will implement an HTTP interceptor named “CacheInterceptor

import { Injectable } from '@angular/core';
import {
  HttpEvent,
  HttpInterceptor,
  HttpHandler,
  HttpRequest,
  HttpResponse,
} from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { HttpCacheService } from '../services/http-cache.service';


@Injectable()
export class CacheInterceptor implements HttpInterceptor {
  constructor(
  private cacheService: HttpCacheService
  ) {}

  intercept(
    req: HttpRequest,
    next: HttpHandler
  ): Observable> {
    

    //check if the outgoing calls are GET and MRDH APIs
    if (req.method === 'GET' ) { 
      // attempt to retrieve a cached response
      const cachedResponse:
        | HttpResponse
        | undefined = this.cacheService.get(req.url);

      // return cached response
      if (cachedResponse) {
        console.log(`Returning a cached response: ${cachedResponse.url}`);
        console.log(cachedResponse);
        return of(cachedResponse);
      }

      // send request to server and add response to cache
      return next.handle(req).pipe(
        tap((event) => {
          if (event instanceof HttpResponse) {
            console.log(`Adding item to cache: ${req.url}`);
            this.cacheService.put(req.url, event);
          }
        })
      );
    }
    else {
        // pass along non-cacheable requests 
        return next.handle(req);
    }
  }
}

This interceptor will intercept all outgoing HTTP API calls. The Cache Interceptor will save the URL and its response detecting upon a GET API call. If we don’t find any saved response, we will let the call continue and save the response and its URL in our HttpCacheService.

To make this work we need to register this in the main NgModule provider.

Use this code to place it in the NgModule of the application.

providers: [
    { provide: HTTP_INTERCEPTORS, useClass: CacheInterceptor, multi: true }
  ]
.... other codes ignored

Great, that’s it. it’s as simple as that.

Wait wait. The title says time-based cache implementation. Right, let’s implement that next.

TimerService is used to start the timer when the first response is cached. After which the interceptor checks for a particular amount of time and invalidates the cache.

import { Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';

@Injectable({ providedIn: 'root' })
export class TimerService {
  private isTimerStarted = false;
  public dateNow = new Date();
  public dDay = new Date();
  milliSecondsInASecond = 1000;
  hoursInADay = 24;
  minutesInAnHour = 60;
  SecondsInAMinute = 60;

  public timeDifference: any;

  constructor() {}

  private getTimeDifference() {
    this.timeDifference = this.dDay.getTime() - new Date().getTime();    
  }
  
  startTimer() {
    if (!this.isTimerStarted) {
   
      this.dDay.setMinutes(
        this.dDay.getMinutes() + 30 //+environment.cacheTimeInMinutes // you can put this time in environment file to make it configurable.
      );
      this.getTimeDifference();
      this.isTimerStarted = true;
    }
  }

  resetTimer() {
    this.isTimerStarted = false;
    this.getTimeDifference();
  }

  getRemainingTime(): number {
    this.getTimeDifference();
    return this.timeDifference;
  }
}

Updated CacheInterceptor code which uses time service to track down the timing and invalidate the cache after a particular time.

import { Injectable } from '@angular/core';
import {
  HttpEvent,
  HttpInterceptor,
  HttpHandler,
  HttpRequest,
  HttpResponse,
} from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { HttpCacheService } from '../services/http-cache.service';
import { TimerService } from '../services/timer.service';


@Injectable()
export class CacheInterceptor implements HttpInterceptor {
  constructor(
  private cacheService: HttpCacheService,
  private timerService: TimerService
  ) {}

  intercept(
    req: HttpRequest,
    next: HttpHandler
  ): Observable> {
    
    this.timerService.startTimer();
    let remainingTimeInMilliseconds = this.timerService.getRemainingTime();
    
    if (remainingTimeInMilliseconds <= 0) {
      
      this.timerService.resetTimer();
      console.log(
        `Invalidating cache due to cache time limit: ${req.method} ${req.url}`
      );
      this.cacheService.invalidateCache();
    } 


    //check if the outgoing calls are GET and MRDH APIs
    if (req.method === 'GET' ) { 
      // attempt to retrieve a cached response
      const cachedResponse:
        | HttpResponse
        | undefined = this.cacheService.get(req.url);

      // return cached response
      if (cachedResponse) {
        console.log(`Returning a cached response: ${cachedResponse.url}`);
        console.log(cachedResponse);
        return of(cachedResponse);
      }

      // send request to server and add response to cache
      return next.handle(req).pipe(
        tap((event) => {
          if (event instanceof HttpResponse) {
            console.log(`Adding item to cache: ${req.url}`);
            this.cacheService.put(req.url, event);
          }
        })
      );
    }
    else {
        // pass along non-cacheable requests 
        return next.handle(req);
    }
  }
}

CacheInterceptor will monitor all the outgoing calls and start the triggers timer. After 30 minutes of time, the cache service will be ready to invalidate the cache. Interceptor invalidates the cache after 30 minutes of time. This is what will happen when the app initiates the first call.

Are you a beginner? Interested in learning Angular? check out this link

Summary

In this post, I showed you how to implement time-based caching in Angular using HTTP Interceptor. I hope you enjoyed this post and it is useful for you.

The post How to implement time-based caching in Angular using HTTP Interceptor appeared first on Learn Smart Coding.

]]>
https://blogs.learnsmartcoding.com/2022/01/05/how-to-implement-time-based-caching-in-angular-using-http-interceptor/feed/ 1 1013
Google place autocomplete integration with Angular 11 using the reactive form https://blogs.learnsmartcoding.com/2021/09/21/google-place-autocomplete-integration-with-angular-11-using-the-reactive-form/ https://blogs.learnsmartcoding.com/2021/09/21/google-place-autocomplete-integration-with-angular-11-using-the-reactive-form/#comments Tue, 21 Sep 2021 10:04:47 +0000 https://karthiktechblog.com/?p=812 Google place autocomplete integration with Angular 11 is now easy to build and use. Getting addresses from customers is so common these days in any application. Verifying the received address is going to be a challenge. We can overcome this by using Google place autocomplete to get the suggested address and even more accurate address. […]

The post Google place autocomplete integration with Angular 11 using the reactive form appeared first on Learn Smart Coding.

]]>
Google place autocomplete integration with Angular 11 is now easy to build and use.

Getting addresses from customers is so common these days in any application. Verifying the received address is going to be a challenge. We can overcome this by using Google place autocomplete to get the suggested address and even more accurate address.

What is Google Place Autocomplete

According to Google documentation, The Place Autocomplete service is a web service that returns place predictions in response to an HTTP request.

The request specifies a textual search string and optional geographic bounds. The service can be used to provide autocomplete functionality for text-based geographic searches, by returning places such as businesses, addresses, and points of interest as user types.

To know more about the documentation, visit the place autocomplete

Build Address component using Place Autocomplete

In this example, I will create the following to build the final address component that uses the Google Place Autocomplete feature.

  1. A service named “google-address” provides methods to exact the google data.
  2. Address component that will be used to collect address from the user or autoselect suggested address from autocomplete. This is built using Reactive Forms.

Steps to build Place Autocomplete feature.

Step 1:

Before you start using the Places API, you need a project with a billing account and the Places API enabled.

once you have the Key, use the below script in your HTML to load the library from google.


<script src="https://maps.googleapis.com/maps/api/js?libraries=places&key=<yourkey>"></script>

Step 2:

Install package “@types/googlemaps” of version ^3.43.3. Your package will look like “@types/googlemaps”: “^3.43.3”.

In the below code, you can specify what type of addresses you are allowing the user to find in the search result. I have not specified any type of address type so I get to see the address using Entity name, direct address as well.


const autocomplete = new google.maps.places.Autocomplete(
      this.addresstext.nativeElement,
      {
        componentRestrictions: { country: 'US' },
        types: [this.addressType]  // 'establishment' / 'address' / 'geocode' // we are checking all types
      }
    );

Address Component HTML

<nav aria-label="breadcrumb">
    <ol class="breadcrumb">
        <li class="breadcrumb-item"><a href="#">Home</a></li>
        <li class="breadcrumb-item" aria-current="page">Google Address</li>
        <li class="breadcrumb-item active" aria-current="page"> Address type: <b>{{addressType}}</b></li>
    </ol>
</nav>

<div class="container">
    <h1> Google Places Autocomplete </h1>
    <form [formGroup]="addressForm">
        <div class="row">
            <div class="col-md-6">
                <div class="form-group">
                    <label for="addressLine1">Street line 1</label>
                    <input type="text" #addresstext required="required" formControlName="addressLine1"
                        autocomplete="off" class="form-control" id="addressLine1" aria-describedby="emailHelp"
                        placeholder="Enter street line 1">
                    <small id="addressLine1" class="form-text text-muted">Start typing address for auto complete</small>
                </div>
                <div class="form-group">
                    <label for="city">City</label>
                    <input type="text" class="form-control" id="addressLine2" aria-describedby="city"
                        formControlName="city" placeholder="Enter city">
                </div>
                <div class="form-group">
                    <label for="city">State</label>
                    <select class="form-control" formControlName="state" id="state">
                        <option value="">- - Select - -</option>
                        <option value="AL">Alabama</option>
                        <option value="NJ">New Jersey</option>
                        <option value="NY">New York</option>
                    </select>
                </div>
            </div>
            <div class="col-md-6">
                <div class="form-group">
                    <label for="addressLine2">Street line 2</label>
                    <input type="text" class="form-control" id="addressLine2" aria-describedby="addressLine2"
                        formControlName="addressLine2" placeholder="Enter street line 2">
                </div>
                <br>
                <div class="form-group">
                    <label for="city">Postal Code</label>
                    <input type="text" class="form-control" id="postalCode" aria-describedby="postalCode"
                        formControlName="postalCode" placeholder="Enter postal code">
                </div>
                <div class="form-group">
                    <label for="city">Country</label>
                    <select class="form-control" formControlName="country" id="country">
                        <option value="">- - Select - -</option>
                        <option value="US">United States of America</option>
                    </select>
                </div>
            </div>
        </div>
    </form>

    <div *ngIf="showDetails">
        <div class="card">    
         
            <div class="card-body">
                <div class="panel-heading"><h2>Google Response Details</h2></div>   <br>
                <h5>Formatted Address</h5>
                <p>
                    {{formattedAddress}}
                </p>
                <br>
                <h5>Google entire response object</h5>
                <pre>
                    {{place | json}}
                </pre>
            </div>

        </div>

    </div>
</div>

Address Component TS file

In this component, to retrieve the specific address data, I have built a custom service that exacts the information.

import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { GoogleAddress } from '../model/address';
import { GoogleAddressService } from '../service/google-address.service';

@Component({
  selector: 'app-address',
  templateUrl: './address.component.html',
  styleUrls: ['./address.component.scss']
})
export class AddressComponent implements OnInit {
  showDetails = false;
  @Input() addressType: string;
  place!: object;
  @ViewChild('addresstext') addresstext: any;
  
  establishmentAddress: Object;

  formattedAddress: string;
  formattedEstablishmentAddress: string;

  addressForm!: FormGroup;
  googleAddress!: GoogleAddress;

  constructor(private googleAddressService: GoogleAddressService,
    private formBuilder: FormBuilder) { }

  ngOnInit(): void {
    this.initializeAddressForm();
  }

  initializeAddressForm() {
    const initialAddress : GoogleAddress =  {
      addressLine1: '', addressLine2: '', city: '' , state: '', country: '', postalCode: ''
    };
    this.googleAddress = initialAddress;

    this.addressForm = this.formBuilder.group({
      addressLine1: new FormControl(this.googleAddress.addressLine1, [Validators.required,
      Validators.maxLength(200)]),
      addressLine2: new FormControl(this.googleAddress.addressLine2),
      city: new FormControl(this.googleAddress.city, [Validators.required,
      Validators.maxLength(100)]),
      state: new FormControl(this.googleAddress?.state, [Validators.required,
      Validators.maxLength(50)]),
      postalCode: new FormControl(this.googleAddress.postalCode, [Validators.required,
      Validators.maxLength(15)]),
      country: new FormControl(this.googleAddress?.country, [Validators.required,
      Validators.maxLength(50)])
    });
  }

  ngAfterViewInit() {
    this.getPlaceAutocomplete();
  }

  private getPlaceAutocomplete() {
    const autocomplete = new google.maps.places.Autocomplete(
      this.addresstext.nativeElement,
      {
        componentRestrictions: { country: 'US' },
        types: [this.addressType]  // 'establishment' / 'address' / 'geocode' // we are checking all types
      }
    );
    google.maps.event.addListener(autocomplete, 'place_changed', () => {
      this.place = autocomplete.getPlace();
      this.formattedAddress = this.googleAddressService.getFormattedAddress(this.place);
      this.patchGoogleAddress();
      this.showDetails = true;
    });
  }

  patchGoogleAddress() {
    const streetNo = this.googleAddressService.getStreetNumber(this.place);
    const street = this.googleAddressService.getStreet(this.place);
    let googleAddress: GoogleAddress = {
      addressLine1: `${streetNo === undefined ? '' : streetNo} ${street === undefined ? '' : street
        }`,
      addressLine2: '',
      postalCode: this.googleAddressService.getPostCode(this.place),
      city: this.googleAddressService.getLocality(this.place),
      state: this.googleAddressService.getState(this.place),
      country: this.googleAddressService.getCountryShort(this.place),
    };
    this.addressForm.markAllAsTouched();
    this.patchAddress(googleAddress);
  }

  patchAddress(address: GoogleAddress) {
    if (this.addressForm !== undefined) {
      this.addressForm
        .get('addressLine1')!
        .patchValue(address.addressLine1);
      this.addressForm
        .get('addressLine2')!
        .patchValue(address.addressLine2);
      this.addressForm.get('postalCode')!.patchValue(address.postalCode);
      this.addressForm.get('city')!.patchValue(address.city);
      this.addressForm.get('state')!.patchValue(address.state);
      this.addressForm.get('country')!.patchValue(address.country);
    }
  }
}

Please note that I have filled the states and Country with few values. you can fill the dropdown with all possible states and countries.

Step 3:

Building custom google address service for ease of use.

GoogleAddressService

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class GoogleAddressService {

  constructor() { }

  getPlaceId(place: object){
    return place['place_id'];
}

getFormattedAddress(place: object){
  return place['formatted_address'];
}

getAddrComponent(place, componentTemplate) {
  let result;

  for (let i = 0; i < place.address_components.length; i++) {
    const addressType = place.address_components[i].types[0];
    if (componentTemplate[addressType]) {
      result = place.address_components[i][componentTemplate[addressType]];        
      return result;
    }
  }
  return;
}

getStreetNumber(place) {
  const COMPONENT_TEMPLATE = { street_number: 'short_name' },
    streetNumber = this.getAddrComponent(place, COMPONENT_TEMPLATE);

  return streetNumber===undefined?'':streetNumber;
}

getStreet(place) {
  const COMPONENT_TEMPLATE = { route: 'long_name' },
    street = this.getAddrComponent(place, COMPONENT_TEMPLATE);
  return street;
}

getLocality(place) {
  const COMPONENT_TEMPLATE = { locality: 'long_name' },
    city = this.getAddrComponent(place, COMPONENT_TEMPLATE);
  return city;
}

getState(place) {
  const COMPONENT_TEMPLATE = { administrative_area_level_1: 'short_name' },
    state = this.getAddrComponent(place, COMPONENT_TEMPLATE);
  return state;
}

getDistrict(place) {
  const COMPONENT_TEMPLATE = { administrative_area_level_2: 'short_name' },
    state = this.getAddrComponent(place, COMPONENT_TEMPLATE);
  return state;
}

getCountryShort(place) {
  const COMPONENT_TEMPLATE = { country: 'short_name' },
    countryShort = this.getAddrComponent(place, COMPONENT_TEMPLATE);
  return countryShort;
}

getCountry(place:any) {
  const COMPONENT_TEMPLATE = { country: 'long_name' },
    country = this.getAddrComponent(place, COMPONENT_TEMPLATE);
  return country;
}

getPostCode(place) {
  const COMPONENT_TEMPLATE = { postal_code: 'long_name' },
    postCode = this.getAddrComponent(place, COMPONENT_TEMPLATE);
  return postCode;
}

getPhone(place) {
  const COMPONENT_TEMPLATE = { formatted_phone_number: 'formatted_phone_number' },
    phone = this.getAddrComponent(place, COMPONENT_TEMPLATE);
  return phone;
}
}

The formatted address property is the address that you will see in google suggestions on the dropdown list.

Google Place Autocomplete Demo

Complete Application Code

You may download the entire working code from GitHub repository

Tips and Troubleshooting

After placing all the above code if the auto-complete feature is not working then you need to verify the below settings and fix them in order for it to work.

  1. go to tsconfig.json file and add ""node_modules/@types"  under "typeRoots"
tsconfig file for google place autocomplete

Conclusion

In this post, you learned how to use Google place autocomplete integration with Angular 11 and more tips to make it work with the latest Angular and typescript code.

The post Google place autocomplete integration with Angular 11 using the reactive form appeared first on Learn Smart Coding.

]]>
https://blogs.learnsmartcoding.com/2021/09/21/google-place-autocomplete-integration-with-angular-11-using-the-reactive-form/feed/ 2 812
How To Fix Your Angular App To Make it work in IE11 https://blogs.learnsmartcoding.com/2020/03/04/how-to-fix-your-angular-app-to-make-it-work-in-ie11/ https://blogs.learnsmartcoding.com/2020/03/04/how-to-fix-your-angular-app-to-make-it-work-in-ie11/#comments Wed, 04 Mar 2020 18:42:58 +0000 https://karthiktechblog.com/?p=412 Introduction In this short post, I will show How To Fix Your Angular App To Make it work in IE11. In Angular CLI version 8 and higher, applications are built using differential loading, a strategy where the CLI builds two separate bundles as part of your deployed application. The first bundle contains modern ES2015 syntax, takes […]

The post How To Fix Your Angular App To Make it work in IE11 appeared first on Learn Smart Coding.

]]>
Introduction

In this short post, I will show How To Fix Your Angular App To Make it work in IE11.

In Angular CLI version 8 and higher, applications are built using differential loading, a strategy where the CLI builds two separate bundles as part of your deployed application.

  • The first bundle contains modern ES2015 syntax, takes advantage of built-in support in modern browsers, ships less polyfills, and results in a smaller bundle size.
  • The second bundle contains code in the old ES5 syntax, along with all necessary polyfills. This results in a larger bundle size, but supports older browsers

Problem Statement

After Angular version 2, many things have changed. You app with version 2 or 4 might have worked in IE and when you upgraded your app to 6 or 8 then the IE stopped working. You might have come across many website stating this is because of Polyfills. That’s not the case always

Polyfills

Angular is built on the latest standards of the web platform. Targeting such a wide range of browsers is challenging because they do not support all features of modern browsers. You compensate by loading polyfill scripts (“polyfills”) for the browsers that you must support.

Angular.IO

Read about browser-support

So for specific use case, Polyfill might work. However, for supporting IE, you need two change to your configuration. Let’s see what are those to make this IE work both when debugging and after deploying to production.

Solution

Add a property  "es5BrowserSupport": true in angular.json file

How To Fix Your Angular App To Make it work in IE11
IE 11 Support

Now, change your target as "es5" in tsconfig.json file

support IE11 for angular app
IE 11 support

Also see other common issues encountered by developers.

Entity Framework Core dbcontextoptionsbuilder does not contain a definition for usesqlserver – Solved

Conclusion

Thanks for reading this piece. As you can see, to support IE 11, you need to add es5BrowserSupport property in angular.json file and change the target as es5 in tsconfig.json file that solved the problem. Let me know in the comments if you have questions.

The post How To Fix Your Angular App To Make it work in IE11 appeared first on Learn Smart Coding.

]]>
https://blogs.learnsmartcoding.com/2020/03/04/how-to-fix-your-angular-app-to-make-it-work-in-ie11/feed/ 1 412