2017-09-30 2 views
-1

J'ai un composant personnalisé <data-input-text> qui a deux modes: régulier et désactivé. Voici le modèle (je l'ai simplifié un peu pour le cas de démonstration):Custom ControlValueAccessor dans les formulaires gérés par template

<label *ngIf="!disabled" 
    class="field-label" 
    [ngClass]="{'focused' : isFocused, 'with-errors' : errors}"> 
    <input class="field-value" 
      [type]="type" 
      [required]="required" 
      (focus)="onFocus()" 
      (blur)="onBlur()" 
      [(ngModel)]="value" 
      #fieldInput="ngModel"> 

</label> 
<div class="field-label" *ngIf="disabled"> 
    <span class="field-value">{{ value }}</span> 
    <span class="field-name">{{ label }}</span> 
</div> 

Dans la forme mère , j'utiliser ce composant de la façon suivante:

<form #profileForm="ngForm"> 
    <data-text-input 
      label="lastName" 
      [required]="true" 
      [disabled]="userIsRegistered"      
      name="lastName" 
      ngModel></data-text-input> 
</form> 

userIsRegistered renvoie un booléen, qui doit basculer entre le champ de saisie ou les intervalles dans le composant. Tout fonctionne bien jusqu'à ici.

Je mis la forme dans le composant parent pour correspondre à la BehaviorSubject comme ceci:

this._sub = this.dl.selectedEmployee.subscribe(u => { 
    if (u.id) { 
    this.isLoading = false; 
     setTimeout(() => { 
     this.profileForm.setValue(u); 
     this.profileForm.control.markAsPristine(); 
     }, 10); 
    } 
}); 

Voici la coutume ControlValueAccessor composant:

import { Component, Input, ViewChild, forwardRef, 
     AfterViewInit, OnInit, OnChanges, 
     NgModule } from '@angular/core'; 

import { NG_VALUE_ACCESSOR, NG_VALIDATORS, 
     ControlValueAccessor, FormControl, 
     Validator, NgForm } from '@angular/forms'; 

@Component({ 
    selector: 'data-text-input', 
    template: ` 
    <label *ngIf="!disabled" 
     class="field-label"> 
     <input class="field-value" 
       [type]="type" 
       [required]="required" 
       (blur)="onBlur()" 
       [(ngModel)]="value" 
       #fieldValue="ngModel"> 
     <span class="field-name">{{ label }}</span> 

    </label> 
    <div class="field-label" *ngIf="disabled"> 
     <span class="field-value">{{ value }}</span> 
     <span class="field-name">{{ label }}</span> 
    </div> 
    `, 
    providers: [ 
     { 
     provide: NG_VALUE_ACCESSOR, 
     useExisting: forwardRef(()=> DataTextInputComponent), 
     multi: true 
     }, 
     { 
     provide: NG_VALIDATORS, 
     useExisting: forwardRef(()=> DataTextInputComponent), 
     multi: true 
     } 
    ] 
    }) 

    export class DataTextInputComponent implements OnChanges, ControlValueAccessor, Validator { 

@Input() public disabled: boolean = false; 
@Input() public label: string; 
@Input() public required: boolean = false; 
@Input() public type: string = 'text'; 
@ViewChild('fieldValue') public fieldValue: FormControl; 

// infrastructure 
public registerOnChange(fn: any) { this.propagateChange = fn; } 
public registerOnTouched(fn: any) { this.propagateTouch = fn; } 

private propagateChange = (_: any) => { }; 
private propagateTouch = (_: any) => { }; 

/** 
* inner value 
*/ 
private innerValue: any = null; 

/** 
* on changes hook 
*/ 
public ngOnChanges(): void { 
    if (this.disabled) { 
     this.propagateChange(this.innerValue); 
    } 
} 

/** 
* input events 
*/ 
public onBlur(): void { 
    this.propagateChange(this.innerValue); 
    this.propagateTouch(true); 
} 

/** 
* value accessor setter and getter 
*/ 
public get value(): any { 
    return this.innerValue; 
}; 

public set value(value: any) { 
    if (value !== 'undefined') { 
     this.innerValue = value; 
     this.propagateChange(value); 
     this.propagateTouch(true); 
    } 
} 

/** 
* value accessor implementation 
* @param value 
*/ 
public writeValue(value: any): void { 
    if (value !== this.innerValue) { 
     this.innerValue = value; 
    } 
} 

/** 
* validation 
* @param c 
*/ 
public validate(c: FormControl) { 
    return this.errors = (this.disabled) ? null : this.customValidate(c); 
} 
private customValidate(c: FormControl): {} { 
    if (c.touched) { 
    // some validation logic which is not relevant here; 
    return null; 
    } 
    return null; 
} 
} 

Il existe d'autres composants utilisés dans la forme aussi (comme un sélecteur de couleur et un ng-select).

Donc, la partie étrange est la suivante. La valeur du formulaire est définie correctement. Pas d'erreurs Les valeurs sont affichées correctement (pour les deux, disabled et !disabled) dans les composants d'entrée de texte de données, ainsi que dans les autres composants du formulaire). La partie étrange est que lorsque j'inspecte l'objet this.profileForm avec le débogueur, la propriété controls a tous les contrôles avec leurs valeurs respectives, mais la propriété value du formulaire manque ceux, où la propriété disabled est définie sur vrai.

Voici le Plunker: https://plnkr.co/edit/nbWQZzQjhGae622CanGa?p=preview

Toutes les idées?

+0

Je ne vois pas de contrôle de courrier électronique dans votre code. Pouvez-vous créer un plunker démontrant votre problème? – yurzui

+0

J'ai mis à jour la question avec le plunker – pop

+0

[Ne pas publier d'images de code ou d'erreurs] (https://meta.stackoverflow.com/q/303812/995714) En outre, vous devez envoyer tout le code qui montre le problème ici, pas un plunker qui peut changer ou disparaître demain en aidant personne à l'avenir: [mcve] – Rob

Répondre

0

Eh bien, ce ne fut pas évident, jusqu'à ce que j'ai tracé sur le chemin de la fixation d'une valeur jusqu'à AbstractControl.prototype.updateValueAndValidity et il est avéré, que l'utilisation du nom de la variable disabled était une mauvaise idée ici:

<form #profileForm="ngForm"> 
    <data-text-input 
      label="lastName" 
      [required]="true" 
      [disabled]="userIsRegistered"      
      name="lastName" 
      ngModel></data-text-input> 
</form> 

J'ai renommé la propriété disabled en isReadOnly - parce que readonly est également un attribut qui peut être vérifié ailleurs et également une interface TypeScript - et, tada, cela fonctionne.