【问题标题】:Angular 2 Reactive form + directive validationAngular 2 反应式表单 + 指令验证
【发布时间】:2017-04-12 09:43:33
【问题描述】:

我正试图解决以下问题:

我有一个“google-place-autocomplete”指令,可以将自动完成功能添加到输入字段。

现在我还希望它能够强制进行谷歌地点选择,并且只有在用户选择了地点时才“有效”。

例如:

@Directive({
    selector: '[googlePlace][formControlName], [googlePlace][ngModel]',
    providers: [{provide: NG_VALIDATORS, useExisting: GooglePlaceDirective, multi: true}]
})
export class GooglePlaceDirective implements Validator, OnChanges {

    valid = false;
    @Output() googlePlaceAddressChange: any = new EventEmitter();
    @Input() googlePlaceAddress: any;

    @Output() ngModelChange: any = new EventEmitter();

    private autocomplete: any;
    constructor(private googleMapService: GoogleMapsService,
                private element: ElementRef,
                private zone: NgZone) {
    }

    ngOnInit() {
        let self = this;
        this.googleMapService
            .load()
            .subscribe(
                () => {
                    this.autocomplete = new google.maps.places.Autocomplete(this.element.nativeElement);
                    this.autocomplete.addListener('place_changed', function () {
                        self.placeChanged(this.getPlace());
                    });
                }
            );
    }

    private placeChanged(place) {
        this.zone.run(() => {
            this.googlePlaceAddress = {
                address: this.element.nativeElement.value,
                formattedAddress: place.formatted_address,
                latitude: place.geometry.location.lat(),
                longitude: place.geometry.location.lng()
            };
            this.valid = true;
            this.googlePlaceAddressChange.emit(this.googlePlaceAddress);
            this.ngModelChange.emit(this.element.nativeElement.value);
        });
    }

    ngOnChanges(changes): void {
        let googlePlaceDefined = typeof (changes.googlePlaceAddress) !== 'undefined';
        let modelDefined = typeof (changes.ngModel) !== 'undefined';

        if(modelDefined && !googlePlaceDefined) {
            this.valid = false;
        } else if(googlePlaceDefined && !modelDefined) {
            this.valid = true;
        }
    }

    validate(control: AbstractControl) {
        return this.valid === false ? {'googlePlaceAddress': true} : null;
    }
}

如果我在模板驱动的表单中使用这个指令:

...
<input name="addr" type="text" [(ngModel)]="textValue" [(googlePlaceAddress)]="googleAddress" required>
<p *ngIf="addr.errors.googlePlaceAddress">Please select a proposed address</p>
...

效果很好。

现在我需要使用 FormGroup 在响应式表单中使用它

let groups = [
    new FormControl('', [Validators.required])
];

/** HTML **/
...
<input [id]="addr"
    [formControlName]="address"
    class="form-control"
    type="text"
    googlePlace
    [placeholder]="question.label"
    [(googlePlaceAddress)]="googleAddress">
...  

但是在这种情况下,指令的验证永远不会被触发。

我想 angular2 在使用 Reactive Forms 时希望通过它:

new FormControl('', [Validators.required, ???])

我一定是走错了路。

【问题讨论】:

    标签: forms validation angular


    【解决方案1】:

    供将来参考:

    我解决了我用它和一个值访问器创建一个组件的问题:

    @Component({
        selector: 'app-google-place',
        templateUrl: './google-place.component.html',
        styleUrls: ['./google-place.component.scss'],
        providers: [
            {
                provide: NG_VALUE_ACCESSOR,
                useExisting: forwardRef(() => GooglePlaceComponent),
                multi: true
            }
        ]
    })
    export class GooglePlaceComponent implements OnInit, ControlValueAccessor {
        @ViewChild('inputElement') inputElement: ElementRef;
    
        @Input() public placeholder: string = "Address";
        @Input() public textValue: string = "";
    
        private autocomplete: any;
        private _place = null;
    
        constructor(
            private googleMapService: GoogleMapsService,
            private zone: NgZone
        ) {
        }
    
        ngOnInit() {
            this.googleMapService
                .load()
                .subscribe(
                    () => {
                        this.autocomplete = new google.maps.places.Autocomplete(this.inputElement.nativeElement);
                        this.autocomplete.addListener('place_changed', () => this.placeChanged());
                    }
                );
        }
    
        placeChanged() {
            this.zone.run(() => {
                let place = this.autocomplete.getPlace();
                this._place = {
                    address: this.inputElement.nativeElement.value,
                    formattedAddress: place.formatted_address,
                    latitude: place.geometry.location.lat(),
                    longitude: place.geometry.location.lng()
                };
    
                this.propagateChange(this._place);
            });
        }
    
        onNgModelChange($event) {
    
            if(this._place !== null) {
                if(this._place.address !== $event) {
                    this._place = null;
                    this.propagateChange(this._place);
                }
            }
        }
    
        onBlur() {
            this.propagateTouched();
        }
    
        writeValue(obj: any): void {
            if(obj !== undefined) {
                this._place = obj;
            }
        }
    
        propagateChange = (_: any) => {};
        registerOnChange(fn) {
            this.propagateChange = fn;
        }
    
        propagateTouched = () => {};
        registerOnTouched(fn: any): void {
            this.propagateTouched = fn;
        }
    }
    

    使用它,我可以在带有 Validators.required 的 FormGroup 中使用它,并且它仅在用户选择了谷歌地点时才有效。

    编辑

    html:

    <input type="text"
       (blur)="onBlur()"
       #inputElement
       class="form-control"
       [(ngModel)]="textValue"
       (ngModelChange)="onNgModelChange($event)">
    

    服务:

    import {Injectable} from '@angular/core';
    import {Subject} from 'rxjs/Subject';
    import {Observable} from 'rxjs/Observable';
    
    @Injectable()
    export class GoogleMapsService {
    
        private key = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
    
        private loaded = false;
        private currentRequest = null;
    
        constructor() {
        }
    
        load() {
            if (this.loaded) {
                return Observable.create((observer) => {
                    observer.next();
                    observer.complete();
                });
            }
    
            if (this.currentRequest === null) {
                //http://reactivex.io/rxjs/manual/overview.html#multicasted-observables
                const source = Observable.create((observer) => {
                    this.loadMaps(observer);
                });
    
                const subject = new Subject();
                this.currentRequest = source.multicast(subject);
                this.currentRequest.connect();
            }
    
            return this.currentRequest;
        }
    
        private loadMaps(observer: any) {
            const script: any = document.createElement('script');
            script.src = 'https://maps.googleapis.com/maps/api/js?key=' + this.key + '&libraries=places';
    
            if (script.readyState) { // IE, incl. IE9
                script.onreadystatechange = () => {
                    if (script.readyState == 'loaded' || script.readyState == 'complete') {
                        script.onreadystatechange = null;
                        this.loaded = true;
                        observer.next();
                        observer.complete();
                        this.currentRequest = null;
                    }
                };
            } else {
                script.onload = () => { // Other browsers
                    this.loaded = true;
                    observer.next();
                    observer.complete();
                    this.currentRequest = null;
                };
            }
    
            script.onerror = () => {
                observer.error('Unable to load');
                this.currentRequest = null;
            };
    
            document.getElementsByTagName('head')[0].appendChild(script);
        }
    }
    

    “用法”:

    使用模板 ngModel

    <app-google-place ([ngModel)]="place"></app-google-place>
    

    【讨论】:

    • 很有趣,我也想和你一样。请您显示GoogleMapsService 以及如何将其绑定到输入?
    猜你喜欢
    • 2020-09-19
    • 2019-06-28
    • 2017-09-09
    • 1970-01-01
    • 2019-05-04
    • 2020-12-05
    • 1970-01-01
    相关资源
    最近更新 更多