项目中没有维护这个对应关系的API,所以我google到了Google map的API,只需要发get请求发送邮编即可。api地址是
http://maps.googleapis.com/maps/api/geocode/json?address=xxx,这里的address就是接受的邮编。
所以现在需求是:用户输入邮政编码后请求该API得到对应的州和city,然后渲染到对应的字段中。
create-client.component.html
<!-- client form -->
<div animated fadeIn>
<form class="client-form" class="form-horizontal" [formGroup]="orgForm">
<div class="row">
<div class="col-md-10">
<div class="card">
<div class="card-header"><strong>Account Information</strong></div>
<div class="card-body">
<div class="row">
<label for="country"
class="col-md-3 form-control-label required-label"><span class="asterisk">*</span>Country:</label>
<div class="col-md-9">
<select style="height: 34px;" class="col-md-5 form-control" id="country" name="country" formControlName="country">
<option *ngFor="let country of countries"
[value]="country">
{{ country }}
</option>
</select>
</div>
</div>
<div class="row">
<label for="postal"
class="col-md-3 form-control-label required-label"><span class="asterisk">*</span>Postal Code:</label>
<div class="col-md-9">
<input id="postal" name="postal"
class="col-md-5 form-control"
formControlName="postal"
placeholder="Enter Client Postal Code" (change)="onPostalChange($event)" >
<span class="col-md-5 login-error-alert" *ngIf="this.isFormInputInvalid('postal') &&
this.orgForm.get('postal').hasError('required')">Postal Code is required</span>
<span class="col-md-5 login-error-alert" *ngIf="this.isFormInputInvalid('postal') &&
!this.orgForm.get('postal').hasError('required') &&
this.orgForm.get('postal').hasError('pattern')">Postal Code must be a 5 digit number</span>
<span class="col-md-5 login-error-alert" *ngIf="this.isPostalCodeInvalid &&
!this.orgForm.get('postal').hasError('pattern') &&
!this.orgForm.get('postal').hasError('required')">Postal Code is not available</span>
</div>
</div>
<div class="row">
<label for="city"
class="col-md-3 form-control-label required-label"><span class="asterisk">*</span>City:</label>
<div class="col-md-9">
<input id="city" name="city" [(ngModel)]="cityName"
class="col-md-5 form-control"
formControlName="city"
placeholder="Enter Client City">
<span class="col-md-5 login-error-alert" *ngIf="this.isFormInputInvalid('city') &&
this.orgForm.get('city').hasError('required')">City is required</span>
<span class="col-md-5 login-error-alert" *ngIf="this.isFormInputInvalid('city') &&
this.orgForm.get('city').hasError('pattern')">City cannot exceed 40 characters</span>
</div>
</div>
<div class="row">
<label for="state"
class="col-md-3 form-control-label required-label"><span class="asterisk">*</span>State:</label>
<div class="col-md-9">
<select style="height: 34px;" class="col-md-5 form-control" id="state" formControlName="state"
[(ngModel)]="stateAbbreviation" (ngModelChange)="onStateChange($event)">
<option *ngFor="let state of stateItems"
[value]="state">
{{ state }}
</option>
</select>
<div *ngIf="orgForm.get('state').touched || orgForm.get('state').dirty">
<span class="col-md-5 login-error-alert" *ngIf="this.isFormInputInvalid('state') &&
this.orgForm.get('state').hasError('required')">State is required</span>
<span class="col-md-5 login-error-alert" *ngIf="this.isStateValueMismatchWithPostalCode"> Postal Code and State Mismatch</span>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer col-md-10">
<div class="col-md-9">
<div class="col-md-5">
<button type="button" class="btn btn-dark-rs6 col-md-5" id="reset-button-modal"
(click)="orgForm.reset()" style="margin-left: -24px">Reset
</button>
<button type="submit" class="btn btn-green-rs6 col-md-5" id="submit-button-modal"
[disabled]="!orgForm.valid || this.isStateValueMismatchWithPostalCode || isPostalCodeInvalid" (click)="onSubmit()"
style="float: right; margin-right: -15px">Create
</button>
</div>
</div>
</div>
</form>
</div>
子类:create-client.component.ts
import { Component } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { Location } from '@angular/common';
import { Account } from '../../model/account.model';
import { PaymentTermList } from '../../model/payment-term-list.model';
import { PricingPackage } from '../../model/pricing-package.model';
import { Processor } from '../../model/processor.model';
import { RequestBilling } from '../../model/request-billing.model';
import { User } from '../../model/user.model';
import { DialogService } from '../../service/dialog.service';
import { OrganizationService } from '../../service/organization.service';
import { CreateOrganizationComponent } from '../create-organization/create-organization.component';
import {Regex} from '../../model/regex.model';
import { OrganizationType } from '../../model/organization-type.model';
import {UtilService} from '../../service/util.service';
import {Constants} from '../../utility/constants';
import {Utility} from '../../utility/utility';
@Component({
selector: 'app-create-client',
templateUrl: 'create-client.component.html'
})
export class CreateClientComponent extends CreateOrganizationComponent {
account: Account = {
soldBy: '', ownedBy: '', address: {
address1: '', address2: '', city: '', state: '', country: '', postal: ''
}, emailAddress: '', locale: {language: {cultureName: ''}},
processorList: [{name: ''}]
};
countries: string[] = [];
stateItems: string[] = [];
constructor(private fb: FormBuilder, route: ActivatedRoute, dialogService: DialogService,
organizationService: OrganizationService, utilService: UtilService,
router: Router, location: Location, regex: Regex) {
super(route, dialogService, organizationService, router, location, regex, utilService);
this.createForm();
this.title = 'Client Creation';
this.route.queryParams.subscribe((params: Params) => {
this.clientId = params['clientId'];
this.parentOrganizationId = params['parentOrganizationId'];
});
this.initProcessorList();
this.organizationService.getPaymentTerms().subscribe(
(paymentTermList: PaymentTermList) => this.paymentTermList = paymentTermList);
this.utilService.getCountries().subscribe(
(countries: string[] ) => this.countries = countries);
this.utilService.getStates().subscribe(
(states: string[] ) => this.stateItems = states);
}
onSubmit() {
this.updateModel();
super.onSubmit(OrganizationType.Client);
}
updateModel() {
this.model.account.address.city = this.orgForm.get('city').value;
this.model.account.address.state = this.orgForm.get('state').value;
this.model.account.address.postal = this.orgForm.get('postal').value;
this.model.account.address.country = Utility.convertSpecificCountryNameInShort(this.orgForm.get('country').value);
this.model.account.processorList = [];
for (const processor of this.processors) {
if (processor['checked']) {
const p: Processor = {name: processor['name']};
this.model.account.processorList.push(p);
}
}
}
onPostalChange($event) {
this.onPostalChanges(this.orgForm.get('postal').value);
}
private createForm() {
this.orgForm = this.fb.group({
state: ['', [Validators.required, Validators.pattern(this.regex.state)]],
city: ['', [Validators.required, Validators.pattern(this.regex.city)]],
country: ['', [Validators.required]],
postal: ['', [Validators.required, Validators.pattern(this.regex.postalCode)]],
);
}
}
父类:create-organization.component.ts
import { HttpErrorResponse, HttpResponse, HttpResponseBase } from '@angular/common/http';
import { Component, ViewChild, ElementRef } from '@angular/core';
import { AbstractControl, FormGroup } from '@angular/forms';
import { ValidationErrors } from '@angular/forms/src/directives/validators';
import { ActivatedRoute, Params, Router} from '@angular/router';
import { Location } from '@angular/common';
import { Observable } from 'rxjs/Observable';
import { Organization } from '../../model/organization.model';
import { Regex } from '../../model/regex.model';
import { DialogService } from '../../service/dialog.service';
import { OrganizationService } from '../../service/organization.service';
import { Utility } from '../../utility/utility';
import { OrganizationType } from '../../model/organization-type.model';
import { UtilService } from '../../service/util.service';
@Component({
selector: 'app-create-organization',
templateUrl: 'create-organization.component.html'
})
export class CreateOrganizationComponent {
parentOrganizationUserName = '';
parentOrganizationId: number;
orgForm: FormGroup;
stateAbbreviationFirstValue = '';
isFirstTimeToChoose = true;
stateAbbreviation = '';
isPostalCodeInvalid = false;
isStateValueMismatchWithPostalCode = false;
cityName = '';
constructor(public route: ActivatedRoute, public dialogService: DialogService, public organizationService: OrganizationService,
private router: Router, public location: Location, public regex: Regex, public utilService: UtilService) {
}
onSubmit(orgType: any) {
let parentOrgId = '';
if (this.parentOrganizationId) {
parentOrgId = this.parentOrganizationId.toString();
}
this.organizationService.createChildOrganization(this.parentOrganizationUserName, parentOrgId, this.model)
.subscribe((response: HttpResponseBase) => {
if (response instanceof HttpResponse) {
this.createdOrgId = response.body['organizationId'];
this.resResult = !response.body['customMessage'];
this.openDialog(this.resResult, orgType, response.body['customMessage']);
} else if (response instanceof HttpErrorResponse) {
this.resErrorResult = response.error['text'];
this.openDialog(false, orgType, this.resErrorResult);
}
}, (error) => {
this.openDialog(false, orgType, '');
});
}
isFormInputInvalid(input: string): boolean {
return Utility.isFormInputInvalid(input, this.orgForm);
}
isUserNameAvailable(control: AbstractControl): Observable<ValidationErrors | null> {
return this.organizationService.getUserNameAvailability(control.value).map((res: any) => res.isUserNameAvailable ? null : res);
}
/**
* Get the state abbreviation with the postal code from google API and judged whether is postal code is valid or not.
* @param postalCode postal code.
*/
onPostalChanges(postalCode: string) {
this.isFirstTimeToChoose = true;
if (postalCode) {
this.utilService.getAddressInfoWithPostalCode(postalCode).subscribe(res => {
const address = Utility.filterStateWithPostalCodeFromGoogleApi(res);
if (address && address.state) {
this.isPostalCodeInvalid = false;
this.stateAbbreviation = address.state;
this.cityName = address.city;
this.isStateValueMismatchWithPostalCode = false;
} else {
this.isPostalCodeInvalid = true;
}
});
}
}
/**
* When changed the state select value then will call this method,it used to confirm whether
* the state value is match with the postal code or not.
* @param stateAbbreviationFirstValue the abbreviation of the firstly selected state value.
* @param stateAbbreviationNewValue the abbreviation of the now selected state value.
*/
isStateAndPostalCodeValid(stateAbbreviationNewValue: string, stateAbbreviationFirstValue: string) {
if (stateAbbreviationNewValue !== undefined && stateAbbreviationFirstValue !== undefined) {
if (stateAbbreviationFirstValue.match(stateAbbreviationNewValue)) {
this.isStateValueMismatchWithPostalCode = false;
} else {
this.isStateValueMismatchWithPostalCode = true;
}
}
}
/**
* change event on state field
* save the first value of state field,and judge whether the postal code and state is match or not.
*/
onStateChange($event) {
if (this.isFirstTimeToChoose === true) {
this.stateAbbreviationFirstValue = this.stateAbbreviation;
this.isFirstTimeToChoose = false;
}
this.isStateAndPostalCodeValid(this.stateAbbreviation, this.stateAbbreviationFirstValue);
}
}
Utility.ts,解析谷歌api返回的json
/**
* Filter the results of requesting google api, retain the state's abbreviation and the city name.
* @param res the results from calling the google API.
* @returns Address including the abbreviation of State and the city name.
*/
static filterStateWithPostalCodeFromGoogleApi(res: any): Address {
const address: Address = {};
if (res['results'].length === 0) {
return res[0];
} else {
const stateNames = res['results'][0]['address_components'].filter(resp => resp['types'][0] === 'administrative_area_level_1')
.map(re => re['short_name']);
const cityNames = res['results'][0]['address_components'].filter(resp => resp['types'][0] === 'locality')
.map(re => re['long_name']);
address.state = stateNames[0];
address.city = cityNames[0];
return address;
}
}
测试点击事件ngModelChange
it('should go from model to change event', async(() => {
const fixture = TestBed.createComponent(CreateMerchantComponent);
const comp = fixture.componentInstance;
spyOn(comp, 'onStateChange');
comp.stateItems = states;
comp.stateAbbreviation = states[1];
fixture.detectChanges();
const select = fixture.debugElement.query(By.css('#state'));
fixture.whenStable().then(() => {
select.nativeElement.dispatchEvent(new Event('change'));
fixture.detectChanges();
expect(comp.onStateChange).toHaveBeenCalledWith('AK');
});
}));