Wednesday, November 28, 2018

Directive In Angular



Directive In Angular

In this article, we will understand what is directive in angular and why we need of directive, and how to create custom directives.

So firstly I try to explain what is directive in Angular:

Angular Directive is a TypeScript class which is declared as a @directive decorator.
The directives allow you to attach behavior to DOM elements and the @directive decorator provide you an additional metadata that determines how directives should be processed, instantiated, and used at run-time.

Angular Directive is basically a class with a @Directive decorator. You might be wondering what are decorators? Decorators are functions that modify JavaScript classes. Decorators are used for attaching metadata to classes, it knows the configuration of those classes and how they should work.

3 types of Directives available in Angular


Component Directives

These form the main class having details of how the component should be processed, instantiated and used at runtime.

Attribute Directives

Attribute directives deal with changing the look and behavior of the dom element.

Structural Directives

A structure directive basically deals with manipulating the dom elements. Structural directives have a * sign before the directive. For example, *ngIf and *ngFor.

Why we need directives in Angular


If you want similar functionality across all components, for example, fade in – fade out or need some input type, which provide auto-suggestive Google places (explain later in detail), You can take two approaches, you can write code in all components or like in programming language you can create a function and that function will provide the same functionality across all components where you will use this function.
Similarly, in Directive, You can write a directive and add behavior inside it, Then whenever you need that behaviour, you can import the directive.

Now move with Custom Directive

Before we begin…

To use Google places autocomplete API, we need an API key. To obtain an API key, please go to the Google maps platform and create a new project.



 After that, install Package with following command,
npm i --save-dev @types/googlemaps
In you index page Copy that script in your head section with your secret key,

<script src="https://maps.googleapis.com/maps/api/js?key=YOUR SECRET KEY&libraries=places" async defer></script>

Create a directory in your app root folder with the following command,
ng g d google-places.directive

Now edit your google-places.directive.ts.


/// <reference types="@types/googlemaps" />
import { Directive, ElementRef, OnInit, Output, EventEmitter } from '@angular/core';

@Directive({
  // tslint:disable-next-line:directive-selector
  selector: '[google-place]'
})
export class GooglePlacesDirective implements OnInit {
  // tslint:disable-next-line:no-output-on-prefix
  @Output() onSelect: EventEmitter<any> = new EventEmitter();
  private element: HTMLInputElement;

  constructor(private elRef: ElementRef) {
    // elRef will get a reference to the element where
    // the directive is placed
    this.element = elRef.nativeElement;
  }

  ngOnInit() {
    const autocomplete = new google.maps.places.Autocomplete(this.element);

    // Event listener to monitor place changes in the input
    google.maps.event.addListener(autocomplete, 'place_changed', () => {
      // Emit the new address object for the updated place
      this.onSelect.emit(this.getFormattedAddress(autocomplete.getPlace()));
    });
  }

  /// <Summary>
  /// return formated address from google places
  getFormattedAddress(place) {
    // @params: place - Google Autocomplete place object
    // @returns: location_obj - An address object in human readable format
    const location_obj = {};
    // tslint:disable-next-line:forin
    for (const _value in  place.address_components) {
      const item = place.address_components[_value];
      location_obj['formatted_address'] = place.formatted_address;
      if (item['types'].indexOf('locality') > -1) {
        location_obj['locality'] = item['long_name'];
      }  else if (item['types'].indexOf('administrative_area_level_1') > -1) {
        location_obj['admin_area_l1'] = item['short_name'];
      } else if (item['types'].indexOf('street_number') > -1) {
        location_obj['street_number'] = item['short_name'];
      } else if (item['types'].indexOf('route') > -1) {
        location_obj['route'] = item['long_name'];
      } else if (item['types'].indexOf('country') > -1) {
        location_obj['country'] = item['long_name'];
      } else if (item['types'].indexOf('postal_code') > -1) {
        location_obj['postal_code'] = item['short_name'];
      }
    }
    return location_obj;
  }

}



Code Explanation

we have done things right here,
We created a characteristic getFormattedAddress which takes a google place object as an argument and parses it to create a useful deal with an item.
We introduced an event listener to the autocomplete API, so as to fire each time the places are changed, and we are the use of this event listener to emit our own event to be able to parse the place object the use of the above getFormattedAddress and send the resulting address with object back to the input element.
We have uncovered this event via an onSelect event emitter. Now allow’s use this event emitter in our input.

In your app.component.html,


<div style="text-align:center">
  <h1>
    Welcome to {{ title }}!
  </h1>
</div>
<div class="input-container">
  <input
    type="text"
    class="google-place-input"
    google-place
    (onSelect)="setAddress($event)"
    placeholder="Type to search..">
    <!-- Displaying the contents of the address object received from our event emitter -->
    <ul style="list-style-type: none;">
      <li *ngFor="let key of addrKeys">
        <span style="font-weight: bold">{{key}}</span>: <span>{{addr[key]}}</span>
      </li>
    </ul>
</div>


In your app.component.ts


import { Component, NgZone } from '@angular/core';

@Component({
  selector: 'app-google-place-search',
  templateUrl: './google-place-search.component.html',
  styleUrls: ['./google-place-search.component.css']
})
export class AppComponent
 {
  public title = 'Places';
  public addrKeys: string[];
  public addr: object;

  // Method to be invoked everytime we receive a new instance
  // of the address object from the onSelect event emitter.
  setAddress(addrObj) {
    // We are wrapping this in a NgZone to reflect the changes
    // to the object in the DOM.
    this.zone.run(() => {
      this.addr = addrObj;
      this.addrKeys = Object.keys(addrObj);
    });
  }

  constructor(private zone: NgZone) {}

}


Add in .css file of your appcomponent


.input-container {
    text-align: center;
    width: 100%;
}

.google-place-input {
    width: 50%;
    min-width: 300px;
    height: 1.5rem;
    font-size: 1.5rem;
    padding: 0.5rem;
}


 Now run ng serve, You will get the output: