File

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

Index

Properties

Properties

address1
address1: string
Type : string
Optional
address2
address2: string
Type : string
Optional
address3
address3: string
Type : string
Optional
city
city: string
Type : string
Optional
country
country: string
Type : string
Optional
postalCode
postalCode: string
Type : string
Optional
province
province: string
Type : string
Optional

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;
  }
}

result-matching ""

    No results matching ""