14 votes

Obtenez une instance de FormControl en interrogeant le modèle d'un composant.

Je dispose d'une FormFieldComponent qui encapsule le HTML et la logique d'affichage des erreurs pour un champ de formulaire :

@Component({
  selector: 'field',
  template: `
    <div class="form-group">
      <label class="control-label">{{label}}</label>
      <ng-content></ng-content>  <!-- Will display the field -->
      <!-- Here, put error display logic -->
    </div>
  `
})
export class FormFieldComponent {
  @Input() label: string;  // Field label
  @Input() theControl: FormControl;  // Current form control, required to display errors
}

En FormFieldComponent J'ai besoin d'une instance du FormControl pour afficher les erreurs.

Mon formulaire ressemble alors à ceci :

<form [formGroup]="myForm">
  ...
  <field label="Title" [theControl]="myForm.get('title')">
    <input type="text" formControlName="title">
  </field>
  ...
</form>

Mais je ne suis pas entièrement satisfait du code ci-dessus. Comme vous pouvez le constater, je spécifie la clé du champ à deux endroits : dans le champ [theControl] et dans la propriété de l'entrée formControlName directive.

Le code serait plus concis si je pouvais simplement écrire :

<field label="Title">
  <input type="text" formControlName="title">
</field>

Remarquez comment le [theControl] La propriété d'entrée a disparu. Le site FieldComponent devrait être capable de mettre la main sur l'instance de FormControl qu'il contient, mais comment ?

J'ai essayé d'utiliser le @ContentChildren pour rechercher les directives FormControl dans le modèle du composant, mais cela ne fonctionne pas :

export class FormFieldComponent {
  @ContentChildren(FormControlDirective) theControl: any;
}

Une autre option consisterait à transmettre la clé du champ en tant qu'entrée à la fonction FormFieldComponent et ensuite laisser le composant utiliser cette clé pour :

  • Appliquer de manière programmatique le formControlName au champ qu'il contient.
  • Mettre la main sur son parent <form> accéder à l'instance FormGroup correspondante, et extraire l'instance FormControl de celle-ci.

12voto

n00dl3 Points 12707

Réponse courte : Vous ne pouvez pas

Tu ne peux tout simplement pas. (Enfin, peut-être que vous pouvez, mais ça va être difficile !)

Réponse longue : vous ne pouvez pas, mais...

FormControl n'est pas injectable. Les directives sont injectables, mais, vous devrez faire face à formControlName , ngModel , formControl etc. et ils ne seraient pas accessibles depuis le composant d'habillage mais depuis ses enfants...

Dans votre cas, vous pouvez essayer avec @ContentChildren(FormControlName) theControl: any; car il n'y a pas de FormControlDirective implicite dans votre code, mais vous ne pourriez pas accéder à l'élément FormControl de toute façon (propriété _control est interne, donc ce serait un hack)...

Vous devez donc vous en tenir à la gestion de vos erreurs à partir du composant traitant de la FormGroup .

MAIS si vous voulez afficher une entrée personnalisée (qui n'affichera pas le message d'erreur tel quel mais pourra montrer que cette entrée est en état d'erreur (l'élément hôte obtiendra l'attribut ng-valid , ng-invalid il s'agit donc d'une question de style), vous pouvez le faire en implémentant l'option ControlValueAccessor .

Un pont entre un contrôle et un élément natif.

Un ControlValueAccessor abstrait les opérations d'écriture d'une nouvelle valeur à un élément DOM représentant un contrôle d'entrée.

Cela signifie que les directives/composants implémentant cette interface peuvent être utilisés avec ngModel , formControl ,etc...

eg : <my-component [(ngModel)]="foo"></my-component>

ce n'est pas la reproduction exacte de votre problème, mais cette implémentation a résolu le même type de problème pour moi :

export const INPUT_VALUE_ACCESSOR: any = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => InputComponent),
  multi: true
};

@Component({
  selector: "field",
  template: `<!--put anything you want in your template-->
            <label>{{label}}</label>
            <input #input (input)="onChange($event.target.value)" (blur)="onTouched()" type="text">`,
  styles: [],
  providers: [INPUT_VALUE_ACCESSOR]
})
export class InputComponent implements ControlValueAccessor {
  @ViewChild("input")
  input: ElementRef;
  @Input()
  label:string;
  onChange = (_: any) => { };
  onTouched = () => { };

  constructor(private _renderer: Renderer) { }

  writeValue(value: any): void {
    const normalizedValue = value == null ? "" : value;
    this._renderer.setElementProperty(this.input.nativeElement, "value", normalizedValue);
  }

  registerOnChange(fn: (_: any) => void): void {
      this.onChange = fn;
  }
  registerOnTouched(fn: () => void): void { this.onTouched = fn; }

  setDisabledState(isDisabled: boolean): void {
    this._renderer.setElementProperty(this.input.nativeElement, "disabled", isDisabled);
  }
}

alors vous pouvez juste :

<field label="Title" formControlName="title"></field>

7voto

Steve Brush Points 553

Vous pouvez obtenir l'instance Nom du contrôle de formulaire via :

@Component({
  selector: 'field',
  templateUrl: './field.component.html',
  styleUrls: ['./field.component.scss']
})
export class FieldComponent implements AfterContentInit {
  @Input()
  public label: string;

  @ContentChild(FormControlName)
  public controlName: FormControlName;

  public ngAfterContentInit(): void {
    console.log(this.controlName.control);
  }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X