File

projects/common/lib/components/address/address.component.ts

Description

Note - This component REQUIRES that HttpClientModule is registered in your NgModule.

Extends

Base

Implements

OnInit OnChanges ControlValueAccessor

Metadata

providers { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AddressComponent) }
selector common-address
templateUrl ./address.component.html
viewProviders { provide: ControlContainer, useExisting: forwardRef(() => NgForm) }

Index

Properties
Methods
Inputs
Outputs
Accessors

Constructor

constructor()

Inputs

address
addressServiceUrl
Type : string
allowExtralines
Type : boolean
Default value : false

If true, adds a plus icon next to street and enables users to add a second address line. This value binds to address.addressLine2

bcOnly
Type : boolean
Default value : false
countryList
Type : CountryList[]
Default value : COUNTRY_LIST
defaultCountry
Type : string
Default value : CANADA
defaultProvince
Type : string
Default value : BRITISH_COLUMBIA
disabled
Type : boolean | ReadOnlyFields
Default value : false
disableGeocoder
Type : boolean
Default value : false
isRequired
Type : boolean
Default value : false
labels
Type : AddrLabelList
maxlengths
Type : Maxlengths
provinceList
Type : ProvinceList[]
Default value : PROVINCE_LIST

Outputs

addressChange
Type : EventEmitter<Address>

Methods

addLine
addLine(line: "2" | "3")
Parameters :
Name Type Optional Default value
line "2" | "3" No null
Returns : void
Private findCountryCode
findCountryCode(country: string)
Parameters :
Name Type Optional
country string No
Returns : string
Private findProvinceDescription
findProvinceDescription(prov: string)
Parameters :
Name Type Optional
prov string No
Returns : string
isCanada
isCanada()
Returns : boolean
ngOnChanges
ngOnChanges(changes)
Parameters :
Name Optional
changes No
Returns : void
ngOnInit
ngOnInit()
Returns : void
registerOnChange
registerOnChange(fn: any)
Parameters :
Name Type Optional
fn any No
Returns : void
registerOnTouched
registerOnTouched(fn: any)
Parameters :
Name Type Optional
fn any No
Returns : void
removeLine
removeLine(line: "2" | "3")
Parameters :
Name Type Optional
line "2" | "3" No
Returns : void
selectSuggestedAddress
selectSuggestedAddress(address: Address)
Parameters :
Name Type Optional
address Address No
Returns : void
setAddress
setAddress(data: GeoAddressResult)
Parameters :
Name Type Optional
data GeoAddressResult No
Returns : void
setCity
setCity(value: string)
Parameters :
Name Type Optional
value string No
Returns : void
setCountry
setCountry(value: string)

Set country province blank

Parameters :
Name Type Optional
value string No
Returns : void
Private setDefaultCountryAsOption
setDefaultCountryAsOption()

Set country to default Search uses country code or country name to find item is list.

Returns : string
Private setDefaultProvinceAsOption
setDefaultProvinceAsOption(country: string)

Sets the default province option value

Parameters :
Name Type Optional
country string No
Returns : string
setDisabledState
setDisabledState(isDisabled: boolean)
Parameters :
Name Type Optional
isDisabled boolean No
Returns : void
Private setLabels
setLabels()
Returns : void
Private setMaxlengths
setMaxlengths()
Returns : void
setPostalCode
setPostalCode(value: string)

Sets string after converted upper case

Parameters :
Name Type Optional
value string No
Returns : void
setProvince
setProvince(value: string)
Parameters :
Name Type Optional
value string No
Returns : void
Private setReadOnlyFields
setReadOnlyFields()
Returns : void
setStreetAddress
setStreetAddress(value: string)
Parameters :
Name Type Optional
value string No
Returns : void
Private truncateAddressLines
truncateAddressLines(address: Address)
Parameters :
Name Type Optional
address Address No
Returns : Address
Private updateProvList
updateProvList()

Updates the provList variable. Values must be stored in a variable and not accessed via function invocation for performance.

Returns : void
writeValue
writeValue(value: Address)
Parameters :
Name Type Optional
value Address No
Returns : void

Properties

Private _cityComponent
Type : CityComponent
Decorators :
@ViewChild('city')
_onChange
Default value : () => {...}
_onTouched
Default value : () => {...}
addr
Type : Address
addrLabels
Type : AddrLabelList
Default value : { address1: 'Full street address, rural route, PO box or general delivery', address2: 'Address Line 2', address3: 'Address Line 3', city: 'City', province: 'Province or state', country: 'Jurisdiction', postalCode: 'Postal Code or Zip Code' }
fieldMaxLengths
Type : Maxlengths
Default value : { address: '25', city: '25', province: '25', country: '250', postalCode: '25' }
provList
Type : ProvinceList[]
readOnlyFields
Type : ReadOnlyFields
Default value : { address: false, city: false, province: false, country: false, postalCode: false, }
showLine2
Default value : false
showLine3
Default value : false
Public objectId
Type : string
Default value : UUID.UUID()
Inherited from Base
Defined in Base:11

An identifier for parents to keep track of components

Accessors

address
getaddress()
setaddress(val)
Parameters :
Name Optional
val No
Returns : void
useAddressValidator
getuseAddressValidator()

Design Guidelines

todo

import {
  Component,
  Input,
  Output,
  EventEmitter,
  OnChanges,
  forwardRef,
  OnInit,
  ViewChild
} from '@angular/core';
import { ControlContainer, ControlValueAccessor, NgForm, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Base } from '../../models/base';
import { GeoAddressResult } from '../../services/geocoder.service';
import { Address } from '../../models/address.model';
import { CountryList, CANADA, COUNTRY_LIST } from '../country/country.component';
import { ProvinceList, BRITISH_COLUMBIA, PROVINCE_LIST } from '../province/province.component';
import { CityComponent } from '../city/city.component';

export interface AddrLabelList {
  address1?: string;
  address2?: string;
  address3?: string;
  city?: string;
  province?: string;
  country?: string;
  postalCode?: string;
}

export interface Maxlengths {
  address?: string;
  city?: string;
  province?: string;
  country?: string;
  postalCode?: string;
}

export interface ReadOnlyFields {
  address?: boolean;
  city?: boolean;
  province?: boolean;
  country?: boolean;
  postalCode?: boolean;
}

/**
 *
 * Note - This component REQUIRES that `HttpClientModule` is registered in your NgModule.
 */
@Component({
  selector: 'common-address',
  templateUrl: './address.component.html',
  /* Re-use the same ngForm that it's parent is using. The component will show
   * up in its parents `this.form`, and will auto-update `this.form.valid`
   */
  viewProviders: [
    { provide: ControlContainer, useExisting: forwardRef(() => NgForm) }
  ],
  providers: [
    { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AddressComponent) }
  ]
})
export class AddressComponent extends Base
  implements OnInit, OnChanges, ControlValueAccessor {

  /* Disable all fields by sending in boolean,
   * disable specific fields by sending in ReadOnlyFields structure
   */
  @Input() disabled: boolean | ReadOnlyFields = false;
  @Input() isRequired: boolean = false;
  @Input() countryList: CountryList[] = COUNTRY_LIST;
  @Input() defaultCountry: string = CANADA;
  @Input() provinceList: ProvinceList[] = PROVINCE_LIST;
  @Input() defaultProvince: string = BRITISH_COLUMBIA;
  @Input() disableGeocoder: boolean = false; // This should eventually be refactored to `disableAddressValidator`. Leaving it for compatibility.
  @Input() labels: AddrLabelList;
  @Input() maxlengths: Maxlengths;
  @Input() bcOnly: boolean = false;
  @Input() addressServiceUrl: string;
  @ViewChild('city') private _cityComponent: CityComponent;

  @Input()
  set address(val: Address) {
    if (val) {
      this.addr = val;
    }
  }
  get address(): Address {
    return this.addr;
  }

  @Output() addressChange: EventEmitter<Address> = new EventEmitter<Address>();
  /**
   * If true, adds a plus icon next to street and enables users to add a second
   * address line.  This value binds to `address.addressLine2`
   */
  @Input() allowExtralines: boolean = false;

  addr: Address;
  provList: ProvinceList[];
  showLine2 = false;
  showLine3 = false;

  // Labels defaulted to MSP
  addrLabels: AddrLabelList =  {
    address1: 'Full street address, rural route, PO box or general delivery',
    address2: 'Address Line 2',
    address3: 'Address Line 3',
    city: 'City',
    province: 'Province or state',
    country: 'Jurisdiction',
    postalCode: 'Postal Code or Zip Code'
  };

  // Lengths defaulted to MSP
  fieldMaxLengths: Maxlengths = {
    address: '25',
    city: '25',
    province: '25',
    country: '250',
    postalCode: '25'
  };

  readOnlyFields: ReadOnlyFields = {
    address: false,
    city: false,
    province: false,
    country: false,
    postalCode: false,
  };

  _onChange = (_: any) => { };
  _onTouched = (_: any) => { };

  constructor() {
    super();
  }

  ngOnInit() {
    this.setLabels();
    this.setMaxlengths();
    this.setReadOnlyFields();

    if (this.addr) {

      if (!this.addr.country) {
        this.addr.country = this.setDefaultCountryAsOption();
      } else {
        // If string for country submitted, need to find code to display value in select box
        this.addr.country = this.findCountryCode( this.addr.country );
      }

      if (!this.addr.province) {
        this.addr.province = this.setDefaultProvinceAsOption(this.addr.country);
      }

      // Make sure addressLine2 is visible if there is data persisted to display there.
      if (this.allowExtralines) {
        if (this.addr.addressLine2) {
          this.addLine(2);
        }

        if (this.addr.addressLine3) {
          this.addLine(3);
        }
      }
    }

    this.updateProvList();
  }

  /**
   * Set country province blank
   * @param value
   */
  setCountry(value: string) {
    this.addr.province = this.setDefaultProvinceAsOption( value );
    this.addr.country = value;
    this.updateProvList();

   if ( this.isCanada() ) {
      // If Canada, clear postal code to display mask
      this.addr.postal = '';

      if (!this.disableGeocoder) {
        this.showLine2 = false;
        this.showLine3 = false;
        this.addr.addressLine2 = '';
        this.addr.addressLine3 = '';
      }
    }

    this._onChange(this.addr);
    this.addressChange.emit(this.addr);
    this._onTouched(this.addr);
  }

  setProvince(value: string) {
    this.addr.province = value;
    this._onChange(this.addr);
    this.addressChange.emit(this.addr);
    this._onTouched(this.addr);
  }

  setStreetAddress(value: string) {
    // When emptying a search result from address validator, clear the rest of the fields as well.
    if (value === '' && !this.disableGeocoder) {
      this.addr.addressLine1 = '';
      this.addr.addressLine2 = '';
      this.addr.addressLine3 = '';
      this.addr.city = '';
      this.addr.postal = '';
      if (!this.bcOnly) {
        this.addr.province = BRITISH_COLUMBIA;
        this.addr.country = CANADA;
      }
      if (this.allowExtralines) {
        this.showLine2 = false;
        this.showLine3 = false;
      }
    }
    this.addr.addressLine1 = value;
    this._onChange(this.addr);
    this.addressChange.emit(this.addr);
    this._onTouched(this.addr);
  }

  setCity(value: string) {
    this.addr.city = value;
    this._onChange(this.addr);
    this.addressChange.emit(this.addr);
    this._onTouched(this.addr);
  }

  /**
   * Sets string after converted upper case
   * @param text
   */
  setPostalCode(value: string) {
    this.addr.postal = value;
    this._onChange(this.addr);
    this.addressChange.emit(this.addr);
    this._onTouched(this.addr);
  }

  isCanada(): boolean {
    return this.addr && CANADA === this.addr.country;
  }

  ngOnChanges(changes) {
    if (changes['countryList'] && changes['countryList'].currentValue) {

      if (this.addr && !this.addr.country) {

        // Set defaults
        this.addr.country = this.setDefaultCountryAsOption();

        // Set defaults
        this.addr.province = this.setDefaultProvinceAsOption(this.addr.country);
      }
      this.updateProvList();
    }
    if (changes['provinceList'] && changes['provinceList'].currentValue) {
      if (this.addr && !this.addr.province) {
        // Set defaults
        this.addr.province = this.setDefaultProvinceAsOption(this.addr.country);
      }
      this.updateProvList();
    }
  }

  addLine(line: 2 | 3 = null) {

    // Add lines in order
    if (line === null) {
      if (!this.showLine2) {
        this.showLine2 = true;
      } else if (!this.showLine3) {
        this.showLine3 = true;
      }
    } else {
      // Add specific line number
      const lookup = `showLine${line}`;
      this[lookup] = true;
    }
  }

  removeLine(line: 2 | 3) {
    // We can remove lines in any order, depending on user input
    // Dynamically lookup variable based on line number input.
    const lookup = `showLine${line}`;
    this[lookup] = false;

    // TODO - Need to clear the data in the appropriate field, just null/undefined it out.
    const addrLookup = `addressLine${line}`;
    this.address[addrLookup] = undefined;
  }

  /**
   * Updates the provList variable. Values must be stored in a variable and not
   * accessed via function invocation for performance.
   */
  private updateProvList() {
    if (!this.provinceList) { return; } // When data is async and hasn't loaded
    this.provList = this.provinceList
      .map(prov => {
        if (prov.country === this.addr.country) {
          return prov;
        }
      })
      .filter(x => x);
  }

  /**
   * Sets the default province option value
   */
  private setDefaultProvinceAsOption(country: string): string {
    const provObj = !this.provinceList ? null : this.provinceList.find(
      val => (val.provinceCode === this.defaultProvince ||
        val.description === this.defaultProvince) &&
        val.country === country
    );
    return (provObj ? provObj.provinceCode : '');
  }

  private findProvinceDescription(prov: string): string {
    const provObj = !this.provinceList ? null : this.provinceList.find(
      val => val.provinceCode === prov ||
        val.description === prov
    );
    return (provObj ? provObj.description : null);
  }

  /**
   * Set country to default
   * Search uses country code or country name to find item is list.
   */
  private setDefaultCountryAsOption(): string {
    return this.findCountryCode( this.defaultCountry );
  }

  private findCountryCode( country: string ): string {
    const countryObj = !this.countryList
      ? null
      : this.countryList.find(
        val =>
          val.countryCode === country ||
          val.description === country
      );
    return countryObj ? countryObj.countryCode : null;
  }

  get useAddressValidator(): boolean {
    if (this.disableGeocoder) {
      return false;
    }
    return this.isCanada();
  }

  // Only BC addresses therefore no need to copy province into structure.
  setAddress(data: GeoAddressResult) {
    this.addr.addressLine1 = data.street;
    this.addr.city = data.city;
    this.addr.province = data.province;
    this.addr.country = data.country;
    this.addressChange.emit(this.addr);
  }

  selectSuggestedAddress(address: Address) {
    if (!address.street && !address.city && !address.postal) {
      return;
    }
    // Truncate long addresses and drop them down a line to addressLine2 etc.
    address = this.truncateAddressLines(address);

    if (this.bcOnly && address.province != BRITISH_COLUMBIA) {
      alert('Please select a valid BC address.');
      setTimeout(() => {
        this.addr.addressLine1 = '';
        this.addr.addressLine2 = '';
        this.addr.addressLine3 = '';
      }, 0);
      return;
    }
    // Placed in timeout to override ngx-bootstrap population.
    setTimeout(() => {
      this.addr.addressLine1 = address.addressLine1 || '';
    }, 0);
    if (this.allowExtralines) {
      this.addr.addressLine2 = address.addressLine2 || '';
      this.addr.addressLine3 = address.addressLine3 || '';
      
      if (address.addressLine2) {
        this.showLine2 = true;
      }
      if (address.addressLine3) {
        this.showLine3 = true;
      }
    }
    this.addr.city = address.city;
    if (this._cityComponent) {
      this._cityComponent.onValueChange(this.addr.city);
    }
    this.addr.postal = address.postal;
    if (!this.bcOnly) {
      this.addr.province = address.province;
    }
    this._onChange(this.addr);
    this.addressChange.emit(this.addr);
    this._onTouched(this.addr);
  }

  writeValue( value: Address) {
    if ( value ) {
      this.addr = value;
    }
  }

  registerOnChange(fn: any): void {
    this._onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this._onTouched = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
    this.setReadOnlyFields();
  }

  private setLabels() {
    if ( this.labels ) {
      Object.keys(this.labels).map( x => this.addrLabels[x] = this.labels[x] );
    }
  }

  private setMaxlengths() {
    if ( this.maxlengths ) {
      Object.keys(this.fieldMaxLengths).map( x => this.maxlengths[x] = this.fieldMaxLengths[x]);
    }
  }

  private setReadOnlyFields() {
    if ( typeof this.disabled === 'boolean' ) {
      Object.keys(this.readOnlyFields).map( x => this.readOnlyFields[x] = this.disabled );
    } else {
      Object.keys(this.disabled).map( x => this.readOnlyFields[x] = this.disabled[x] );
    }
  }

  private truncateAddressLines(address: Address): Address {
    const maxlength: number = parseInt(this.fieldMaxLengths.address, 10);
    const lines: Array<string> = [];
    const newLines: Array<string> = [];
    let lineIndex: number = 0;

    // console.log('Before truncation: ', address);

    // Create `lines` array of address lines.
    for (let i=1; i<=3; i++) {
      if (address['addressLine' + i]) {
        lines.push(address['addressLine' + i]);
      }
    }
    for (let i=0; i<lines.length; i++) {
      if (!lines[i]) {
        break;
      }
      newLines.push('');
      const words: Array<string> = lines[i].split(' ');

      if (lines[i].length > maxlength && words.length > 1) {
        // Iterate over words.
        while (words.length > 0) {
          newLines[lineIndex] += words[0] + ' ';
          words.splice(0, 1);

          if (words.length > 0) {
            const tempNextLine = newLines[lineIndex] + words[0];
            // Add next line in case words remain.
            if (tempNextLine.length > maxlength) {
              newLines[lineIndex] = newLines[lineIndex].trim();
              newLines.push('');
              lineIndex++;
            }
          }
        }
        newLines[lineIndex] = newLines[lineIndex].trim();
      } else {
        newLines[lineIndex] = lines[i];
      }
      lineIndex++;
    };

    for (let i=0; i<newLines.length; i++) {
      address['addressLine' + (i + 1)] = newLines[i];
    }
    // console.log('After truncatation: ', address);
    return address;
  }
}
<div class="form-group">
  <div class="row">
    <div [ngClass]="{'col-11': allowExtralines, 'col-12': !allowExtralines}">
      <common-street *ngIf="!useAddressValidator"
                     name="street_{{objectId}}"
                     label="{{addrLabels.address1}}"
                     [ngModel]="addr?.addressLine1"
                     (ngModelChange)="setStreetAddress($event)"
                     (selectEvent)="setAddress($event)"
                     [required]="isRequired"
                     [disabled]="readOnlyFields.address"
                     [maxlength]="fieldMaxLengths.address"
                     commonValidateStreet></common-street>
      <!-- Address Validator -->
      <common-address-validator *ngIf="useAddressValidator"
                                name="street_{{objectId}}"
                                label="{{addrLabels.address1}}"
                                [(ngModel)]="addr.addressLine1"
                                (ngModelChange)="setStreetAddress($event)"
                                [required]="isRequired"
                                [disabled]="readOnlyFields.address"
                                [maxlength]="fieldMaxLengths.address"
                                [serviceUrl]="addressServiceUrl"
                                (select)="selectSuggestedAddress($event)"
                                [populateAddressOnSelect]="true"
                                commonValidateStreet>
      </common-address-validator>
    </div>

    <div class="col-1"
         *ngIf='allowExtralines'>
      <div class="h-50"></div>
      <button class=" btn btn-transparent"
              *ngIf='!(showLine2 && showLine3)'
              aria-label="Add Additional Address Line (optional)"
              (click)="addLine()">
        <i class="fa fa-plus"></i>
      </button>
    </div>
  </div>
</div>


<div class="form-group"
     *ngIf='showLine2'>
  <div class="row">

    <div class="col-11">
      <common-street label="{{addrLabels.address2}}"
                     name="street_line_2_{{objectId}}"
                     [(ngModel)]='addr.addressLine2'
                     [maxlength]="fieldMaxLengths.address"
                     [disabled]="readOnlyFields.address"
                     commonValidateStreet></common-street>
    </div>
    <div class="col-1">
      <div class="h-50"></div>
      <button class="btn btn-transparent"
              *ngIf="!showLine3"
              aria-label='Remove Address Line 2'
              (click)="removeLine(2)">
        <i class="fa fa-minus"></i>
      </button>
    </div>
  </div>
</div>

<div class="form-group"
     *ngIf='showLine3'>
  <div class="row">


    <div class="col-11">
      <common-street label="{{addrLabels.address3}}"
                     name="street_line_3_{{objectId}}"
                     [(ngModel)]='addr.addressLine3'
                     [maxlength]="fieldMaxLengths.address"
                     [disabled]="readOnlyFields.address"
                     commonValidateStreet></common-street>
    </div>
    <div class="col-1">
      <div class="h-50"></div>
      <button class=" btn btn-transparent"
              aria-label='Remove Address Line 3 '
              (click)="removeLine(3)">
        <i class="fa fa-minus"></i>
      </button>
    </div>
  </div>
</div>


<div class="form-group col-sm-11 p-sm-0">
  <common-city #city
               name="city_{{objectId}}"
               label="{{addrLabels.city}}"
               [ngModel]="addr?.city"
               (ngModelChange)="setCity($event)"
               [required]="isRequired"
               [disabled]="readOnlyFields.city"
               [maxlength]="fieldMaxLengths.city"
               commonValidateCity></common-city>
</div>

<div class="form-group col-sm-11 p-sm-0">
  <common-province name="province_{{objectId}}"
                   label="{{addrLabels.province}}"
                   [ngModel]="addr?.province"
                   (ngModelChange)="setProvince($event)"
                   [provinceList]="provList"
                   [required]="isRequired"
                   [disabled]="readOnlyFields.province"
                   placeholder="{{ isCanada()? 'Select a Province': 'Select a State' }}"
                   [useDropDownList]="isCanada()"
                   [maxlength]="fieldMaxLengths.province"
                   commonValidateRegion></common-province>
</div>

<div class="form-group col-sm-11 p-sm-0">
  <common-country name="country_{{objectId}}"
                  label="{{addrLabels.country}}"
                  [countryList]="countryList"
                  [ngModel]="addr?.country"
                  (ngModelChange)="setCountry($event)"
                  [required]="isRequired"
                  [disabled]="readOnlyFields.country"
                  [maxlen]="fieldMaxLengths.country"></common-country>
</div>

<div class="form-group col-sm-4 p-sm-0">
  <common-postal-code name="pc_{{objectId}}"
                      label="{{addrLabels.postalCode}}"
                      [displayMask]="isCanada()"
                      [disabled]="readOnlyFields.postalCode"
                      [ngModel]="addr?.postal"
                      (ngModelChange)="setPostalCode($event)"
                      [required]="isRequired"
                      [maxlen]="fieldMaxLengths.postalCode"
                      commonValidatePostalcode
                      [hasMask]="isCanada()"
                      [bcOnly]="bcOnly"></common-postal-code>
</div>
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""