2016-11-28 33 views
1

私は、次のような問題のまわりで私の頭をラップしようとしている:角度2反応型+ディレクティブ検証

私は入力欄にオートコンプリート機能を追加「グーグル・場所・オートコンプリート」ディレクティブを持っています。

Googleプレースを強制的に選択できるようにし、ユーザーがプレースを選択した場合にのみ「有効」になるようにしました。

例えば:私はテンプレート駆動の形でこのディレクティブを使用する場合は

@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は、反応性フォームを使用している場合、それは、経由与えられることを期待しているとします

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

私はどこか間違った道をとっている必要があります。私は価値のアクセサと一緒に外のコンポーネントを作成する私の問題解決

::私はValidators.requiredとFormGroupでそれを使用することができ、これを使用して

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

をし、今後の参考のため

答えて

1

ユーザーがGoogleの場所を選択した場合にのみ有効になります。

EDIT

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> 
+0

非常に興味深い、私はあなたと同じことをしたいと思います。あなたは 'GoogleMapsService'とそれを入力にどのようにバインドするのかを表示できますか? –

+1

編集内容にサービスを追加しました – RVandersteen

関連する問題