projects/storefrontlib/cms-components/myaccount/address-book/address-form/address-form.component.ts
| changeDetection | ChangeDetectionStrategy.OnPush |
| selector | cx-address-form |
| templateUrl | ./address-form.component.html |
Properties |
Methods |
Inputs |
Outputs |
constructor(fb: FormBuilder, userService: UserService, userAddressService: UserAddressService, globalMessageService: GlobalMessageService, modalService: ModalService, translation: TranslationService)
|
|||||||||||||||||||||
|
Parameters :
|
| actionBtnLabel | |
Type : string
|
|
| addressData | |
Type : Address
|
|
| cancelBtnLabel | |
Type : string
|
|
| setAsDefaultField | |
Type : boolean
|
|
Default value : true
|
|
| showCancelBtn | |
Type : boolean
|
|
Default value : true
|
|
| showTitleCode | |
Type : boolean
|
|
| backToAddress | |
Type : EventEmitter
|
|
| submitAddress | |
Type : EventEmitter
|
|
| back |
back()
|
|
Returns :
void
|
| countrySelected | ||||||
countrySelected(country: Country)
|
||||||
|
Parameters :
Returns :
void
|
| getTitles |
getTitles()
|
|
Returns :
Observable<Title[]>
|
| Protected handleAddressVerificationResults | ||||||
handleAddressVerificationResults(results: AddressValidation)
|
||||||
|
Parameters :
Returns :
void
|
| ngOnDestroy |
ngOnDestroy()
|
|
Returns :
void
|
| ngOnInit |
ngOnInit()
|
|
Returns :
void
|
| openSuggestedAddress | ||||||
openSuggestedAddress(results: AddressValidation)
|
||||||
|
Parameters :
Returns :
void
|
| regionSelected | ||||||
regionSelected(region: Region)
|
||||||
|
Parameters :
Returns :
void
|
| toggleDefaultAddress |
toggleDefaultAddress()
|
|
Returns :
void
|
| verifyAddress |
verifyAddress()
|
|
Returns :
void
|
| 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>