File

projects/storefrontlib/cms-components/myaccount/address-book/address-form/address-form.component.ts

Implements

OnInit OnDestroy

Metadata

changeDetection ChangeDetectionStrategy.OnPush
selector cx-address-form
templateUrl ./address-form.component.html

Index

Properties
Methods
Inputs
Outputs

Constructor

constructor(fb: FormBuilder, userService: UserService, userAddressService: UserAddressService, globalMessageService: GlobalMessageService, modalService: ModalService, translation: TranslationService)
Parameters :
Name Type Optional
fb FormBuilder No
userService UserService No
userAddressService UserAddressService No
globalMessageService GlobalMessageService No
modalService ModalService No
translation TranslationService No

Inputs

actionBtnLabel
Type : string
addressData
Type : Address
cancelBtnLabel
Type : string
setAsDefaultField
Type : boolean
Default value : true
showCancelBtn
Type : boolean
Default value : true
showTitleCode
Type : boolean

Outputs

backToAddress
Type : EventEmitter
submitAddress
Type : EventEmitter

Methods

back
back()
Returns : void
countrySelected
countrySelected(country: Country)
Parameters :
Name Type Optional
country Country No
Returns : void
getTitles
getTitles()
Returns : Observable<Title[]>
Protected handleAddressVerificationResults
handleAddressVerificationResults(results: AddressValidation)
Parameters :
Name Type Optional
results AddressValidation No
Returns : void
ngOnDestroy
ngOnDestroy()
Returns : void
ngOnInit
ngOnInit()
Returns : void
openSuggestedAddress
openSuggestedAddress(results: AddressValidation)
Parameters :
Name Type Optional
results AddressValidation No
Returns : void
regionSelected
regionSelected(region: Region)
Parameters :
Name Type Optional
region Region No
Returns : void
toggleDefaultAddress
toggleDefaultAddress()
Returns : void
verifyAddress
verifyAddress()
Returns : void

Properties

actionBtnLabel
Type : string
Decorators :
@Input()
addressData
Type : Address
Decorators :
@Input()
addresses$
Type : Observable<Address[]>
addressForm
Type : FormGroup
Default value : this.fb.group({ country: this.fb.group({ isocode: [null, Validators.required], }), titleCode: [''], firstName: ['', Validators.required], lastName: ['', Validators.required], line1: ['', Validators.required], line2: [''], town: ['', Validators.required], region: this.fb.group({ isocode: [null, Validators.required], }), postalCode: ['', Validators.required], phone: '', defaultAddress: [false], })
addressVerifySub
Type : Subscription
backToAddress
Default value : new EventEmitter<any>()
Decorators :
@Output()
cancelBtnLabel
Type : string
Decorators :
@Input()
countries$
Type : Observable<Country[]>
regions$
Type : Observable<Region[]>
regionsSub
Type : Subscription
selectedCountry$
Type : BehaviorSubject<string>
Default value : new BehaviorSubject<string>('')
setAsDefaultField
Default value : true
Decorators :
@Input()
showCancelBtn
Default value : true
Decorators :
@Input()
showTitleCode
Type : boolean
Decorators :
@Input()
submitAddress
Default value : new EventEmitter<any>()
Decorators :
@Output()
suggestedAddressModalRef
Type : ModalRef
titles$
Type : Observable<Title[]>
import {
  ChangeDetectionStrategy,
  Component,
  EventEmitter,
  Input,
  OnDestroy,
  OnInit,
  Output,
} from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import {
  Address,
  AddressValidation,
  Country,
  ErrorModel,
  GlobalMessageService,
  GlobalMessageType,
  Region,
  Title,
  TranslationService,
  UserAddressService,
  UserService,
} from '@spartacus/core';
import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs';
import { map, switchMap, take, tap } from 'rxjs/operators';
import {
  ModalRef,
  ModalService,
} from '../../../../shared/components/modal/index';
import { sortTitles } from '../../../../shared/utils/forms/title-utils';
import { SuggestedAddressDialogComponent } from './suggested-addresses-dialog/suggested-addresses-dialog.component';

@Component({
  selector: 'cx-address-form',
  templateUrl: './address-form.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AddressFormComponent implements OnInit, OnDestroy {
  countries$: Observable<Country[]>;
  titles$: Observable<Title[]>;
  regions$: Observable<Region[]>;
  selectedCountry$: BehaviorSubject<string> = new BehaviorSubject<string>('');
  addresses$: Observable<Address[]>;

  @Input()
  addressData: Address;

  @Input()
  actionBtnLabel: string;

  @Input()
  cancelBtnLabel: string;

  @Input()
  setAsDefaultField = true;

  @Input()
  showTitleCode: boolean;

  @Input()
  showCancelBtn = true;

  @Output()
  submitAddress = new EventEmitter<any>();

  @Output()
  backToAddress = new EventEmitter<any>();

  addressVerifySub: Subscription;
  regionsSub: Subscription;
  suggestedAddressModalRef: ModalRef;

  addressForm: FormGroup = this.fb.group({
    country: this.fb.group({
      isocode: [null, Validators.required],
    }),
    titleCode: [''],
    firstName: ['', Validators.required],
    lastName: ['', Validators.required],
    line1: ['', Validators.required],
    line2: [''],
    town: ['', Validators.required],
    region: this.fb.group({
      isocode: [null, Validators.required],
    }),
    postalCode: ['', Validators.required],
    phone: '',
    defaultAddress: [false],
  });

  constructor(
    protected fb: FormBuilder,
    protected userService: UserService,
    protected userAddressService: UserAddressService,
    protected globalMessageService: GlobalMessageService,
    protected modalService: ModalService,
    protected translation: TranslationService
  ) {}

  ngOnInit() {
    // Fetching countries
    this.countries$ = this.userAddressService.getDeliveryCountries().pipe(
      tap((countries: Country[]) => {
        if (Object.keys(countries).length === 0) {
          this.userAddressService.loadDeliveryCountries();
        }
      })
    );

    // Fetching titles
    this.titles$ = this.getTitles();

    // Fetching regions
    this.regions$ = this.selectedCountry$.pipe(
      switchMap((country) => this.userAddressService.getRegions(country)),
      tap((regions: Region[]) => {
        const regionControl = this.addressForm.get('region.isocode');
        if (regions && regions.length > 0) {
          regionControl.enable();
        } else {
          regionControl.disable();
        }
      })
    );

    if (this.addressData && Object.keys(this.addressData).length !== 0) {
      this.addressForm.patchValue(this.addressData);

      this.countrySelected(this.addressData.country);
      if (this.addressData.region) {
        this.regionSelected(this.addressData.region);
      }
    }

    this.addresses$ = this.userAddressService.getAddresses();
  }

  getTitles(): Observable<Title[]> {
    return combineLatest([
      this.translation.translate('addressForm.defaultTitle'),
      this.userService.getTitles(),
    ]).pipe(
      map(([noneTitleText, titles]) => {
        const noneTitle = { code: '', name: noneTitleText };
        titles.sort(sortTitles);
        return [noneTitle, ...titles];
      })
    );
  }

  protected handleAddressVerificationResults(results: AddressValidation) {
    if (results.decision === 'ACCEPT') {
      this.submitAddress.emit(this.addressForm.value);
    } else if (results.decision === 'REJECT') {
      // TODO: Workaround: allow server for decide is titleCode mandatory (if yes, provide personalized message)
      if (
        results.errors.errors.some(
          (error: ErrorModel) => error.subject === 'titleCode'
        )
      ) {
        this.globalMessageService.add(
          { key: 'addressForm.titleRequired' },
          GlobalMessageType.MSG_TYPE_ERROR
        );
      } else {
        this.globalMessageService.add(
          { key: 'addressForm.invalidAddress' },
          GlobalMessageType.MSG_TYPE_ERROR
        );
      }
    } else if (results.decision === 'REVIEW') {
      this.openSuggestedAddress(results);
    }
  }

  countrySelected(country: Country): void {
    this.addressForm.get('country')?.get('isocode')?.setValue(country.isocode);
    this.selectedCountry$.next(country.isocode);
  }

  regionSelected(region: Region): void {
    this.addressForm.get('region')?.get('isocode')?.setValue(region.isocode);
  }

  toggleDefaultAddress(): void {
    this.addressForm['controls'].defaultAddress.setValue(
      this.addressForm.value.defaultAddress
    );
  }

  back(): void {
    this.backToAddress.emit();
  }

  verifyAddress(): void {
    if (this.addressForm.valid) {
      if (this.addressForm.get('region').value.isocode) {
        this.regionsSub = this.regions$.pipe(take(1)).subscribe((regions) => {
          const obj = regions.find(
            (region) =>
              region.isocode ===
              this.addressForm.controls['region'].value.isocode
          );
          Object.assign(this.addressForm.value.region, {
            isocodeShort: obj.isocodeShort,
          });
        });
      }

      if (this.addressForm.dirty) {
        this.userAddressService
          .verifyAddress(this.addressForm.value)
          .subscribe((result) => {
            this.handleAddressVerificationResults(result);
          });
      } else {
        // address form value not changed
        // ignore duplicate address
        this.submitAddress.emit(undefined);
      }
    } else {
      this.addressForm.markAllAsTouched();
    }
  }

  openSuggestedAddress(results: AddressValidation): void {
    if (!this.suggestedAddressModalRef) {
      this.suggestedAddressModalRef = this.modalService.open(
        SuggestedAddressDialogComponent,
        { centered: true, size: 'lg' }
      );
      this.suggestedAddressModalRef.componentInstance.enteredAddress =
        this.addressForm.value;
      this.suggestedAddressModalRef.componentInstance.suggestedAddresses =
        results.suggestedAddresses;
      this.suggestedAddressModalRef.result
        .then((address) => {
          if (address) {
            address = Object.assign(
              {
                titleCode: this.addressForm.value.titleCode,
                phone: this.addressForm.value.phone,
                selected: true,
              },
              address
            );
            this.submitAddress.emit(address);
          }
          this.suggestedAddressModalRef = null;
        })
        .catch(() => {
          // this  callback is called when modal is closed with Esc key or clicking backdrop
          const address = Object.assign(
            {
              selected: true,
            },
            this.addressForm.value
          );
          this.submitAddress.emit(address);
          this.suggestedAddressModalRef = null;
        });
    }
  }

  ngOnDestroy() {
    if (this.addressVerifySub) {
      this.addressVerifySub.unsubscribe();
    }

    if (this.regionsSub) {
      this.regionsSub.unsubscribe();
    }
  }
}
<form (ngSubmit)="verifyAddress()" [formGroup]="addressForm">
  <div class="row">
    <div class="col-md-12 col-lg-9">
      <div class="form-group" formGroupName="country">
        <ng-container *ngIf="countries$ | async as countries">
          <div *ngIf="countries.length !== 0">
            <label>
              <span class="label-content required">{{
                'addressForm.country' | cxTranslate
              }}</span>
              <ng-select
                aria-required="true"
                class="country-select"
                formControlName="isocode"
                [searchable]="true"
                [clearable]="false"
                [items]="countries"
                bindLabel="name"
                bindValue="isocode"
                placeholder="{{ 'addressForm.selectOne' | cxTranslate }}"
                (change)="countrySelected($event)"
              >
              </ng-select>
              <cx-form-errors
                aria-live="assertive"
                aria-atomic="true"
                [control]="addressForm.get('country.isocode')"
              ></cx-form-errors>
            </label>
          </div>
        </ng-container>
      </div>
      <div class="form-group" *ngIf="showTitleCode">
        <ng-container *ngIf="titles$ | async as titles">
          <div *ngIf="titles.length !== 0">
            <label>
              <span class="label-content required">{{
                'addressForm.title' | cxTranslate
              }}</span>
              <ng-select
                formControlName="titleCode"
                [searchable]="true"
                [clearable]="false"
                [items]="titles"
                bindLabel="name"
                bindValue="code"
                [placeholder]="'addressForm.title' | cxTranslate"
              >
              </ng-select>
            </label>
          </div>
        </ng-container>
      </div>
      <div class="form-group">
        <label>
          <span class="label-content required">{{
            'addressForm.firstName.label' | cxTranslate
          }}</span>
          <input
            aria-required="true"
            class="form-control"
            type="text"
            placeholder="{{
              'addressForm.firstName.placeholder' | cxTranslate
            }}"
            formControlName="firstName"
          />
          <cx-form-errors
            aria-live="assertive"
            aria-atomic="true"
            [control]="addressForm.get('firstName')"
          ></cx-form-errors>
        </label>
      </div>
      <div class="form-group">
        <label>
          <span class="label-content required">{{
            'addressForm.lastName.label' | cxTranslate
          }}</span>
          <input
            aria-required="true"
            type="text"
            class="form-control"
            placeholder="{{ 'addressForm.lastName.placeholder' | cxTranslate }}"
            formControlName="lastName"
          />
          <cx-form-errors
            aria-live="assertive"
            aria-atomic="true"
            [control]="addressForm.get('lastName')"
          ></cx-form-errors>
        </label>
      </div>
      <div class="form-group">
        <label>
          <span class="label-content required">{{
            'addressForm.address1' | cxTranslate
          }}</span>
          <input
            aria-required="true"
            type="text"
            class="form-control"
            placeholder="{{ 'addressForm.streetAddress' | cxTranslate }}"
            formControlName="line1"
          />
          <cx-form-errors
            aria-live="assertive"
            aria-atomic="true"
            [control]="addressForm.get('line1')"
          ></cx-form-errors>
        </label>
      </div>
      <div class="form-group">
        <label>
          <span class="label-content">{{
            'addressForm.address2' | cxTranslate
          }}</span>
          <input
            type="text"
            class="form-control"
            placeholder="{{ 'addressForm.aptSuite' | cxTranslate }}"
            formControlName="line2"
          />
        </label>
      </div>
      <div class="row">
        <div class="form-group col-md-6">
          <label>
            <span class="label-content required">{{
              'addressForm.city.label' | cxTranslate
            }}</span>
            <input
              aria-required="true"
              type="text"
              class="form-control"
              placeholder="{{ 'addressForm.city.placeholder' | cxTranslate }}"
              formControlName="town"
            />
            <cx-form-errors
              aria-live="assertive"
              aria-atomic="true"
              [control]="addressForm.get('town')"
            ></cx-form-errors>
          </label>
        </div>
        <div class="form-group col-md-6">
          <label>
            <span class="label-content required">{{
              'addressForm.zipCode.label' | cxTranslate
            }}</span>
            <input
              aria-required="true"
              type="text"
              class="form-control"
              placeholder="{{
                'addressForm.zipCode.placeholder' | cxTranslate
              }}"
              formControlName="postalCode"
            />
            <cx-form-errors
              aria-live="assertive"
              aria-atomic="true"
              [control]="addressForm.get('postalCode')"
            ></cx-form-errors>
          </label>
        </div>
        <ng-container
          *ngIf="regions$ | async as regions"
          formGroupName="region"
        >
          <ng-container *ngIf="regions.length !== 0">
            <div class="form-group col-md-6">
              <label>
                <span class="label-content required">{{
                  'addressForm.state' | cxTranslate
                }}</span>
                <ng-select
                  aria-required="true"
                  class="region-select"
                  formControlName="isocode"
                  [searchable]="true"
                  [clearable]="false"
                  [items]="regions"
                  bindLabel="{{ regions[0].name ? 'name' : 'isocode' }}"
                  bindValue="{{ regions[0].name ? 'isocode' : 'region' }}"
                  placeholder="{{ 'addressForm.selectOne' | cxTranslate }}"
                >
                </ng-select>
                <cx-form-errors
                  aria-live="assertive"
                  aria-atomic="true"
                  [control]="addressForm.get('region.isocode')"
                ></cx-form-errors>
              </label>
            </div>
          </ng-container>
        </ng-container>
      </div>
      <div class="form-group">
        <label>
          <span class="label-content">{{
            'addressForm.phoneNumber.label' | cxTranslate
          }}</span>
          <input
            type="tel"
            class="form-control"
            placeholder="{{
              'addressForm.phoneNumber.placeholder' | cxTranslate
            }}"
            formControlName="phone"
          />
        </label>
      </div>
      <div
        class="form-group"
        *ngIf="(addresses$ | async).length && setAsDefaultField"
      >
        <div class="form-check">
          <label>
            <input
              type="checkbox"
              class="form-check-input"
              formControlName="defaultAddress"
              (change)="toggleDefaultAddress()"
            />
            <span class="form-check-label">{{
              'addressForm.setAsDefault' | cxTranslate
            }}</span>
          </label>
        </div>
      </div>
    </div>
  </div>
  <div class="cx-address-form-btns row">
    <div class="col-md-12 col-lg-6" *ngIf="showCancelBtn">
      <button class="btn btn-block btn-action" (click)="back()">
        {{ cancelBtnLabel || ('addressForm.chooseAddress' | cxTranslate) }}
      </button>
    </div>
    <div class="col-md-12 col-lg-6">
      <button class="btn btn-block btn-primary" type="submit">
        {{ actionBtnLabel || ('common.continue' | cxTranslate) }}
      </button>
    </div>
  </div>
</form>
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""