【问题标题】:AppComponent FormControl seem to not bind with custom material input FormControlAppComponent FormControl 似乎没有与自定义材质输入 FormControl 绑定
【发布时间】:2020-02-06 10:40:36
【问题描述】:

我正在处理自定义材质组件,但在测试时,输入值不会发送到父组件。 AppComponent 中的表单控件值始终为空。

app.component.ts

import { Component, ChangeDetectionStrategy } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'ipmon-cartoppo-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
  title = 'test-carte';

  numCarteCtrl = new FormControl();

}

app.component.html

<h1>Welcome to test-cartes!</h1>
<label>Carte</label>
<cartoppy-saisie-numero-carte formControl="numCarteCtrl" required="false" placeholder="____ ____ ____ ____"></cartoppy-saisie-numero-carte>

<span
  >Valeur dans l'app :

  {{ numCarteCtrl.value | json }}
</span>

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { CartoppyLibCarteModule } from '@ipmon-cartoppo/cartoppy-lib-carte';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, BrowserAnimationsModule, CartoppyLibCarteModule, ReactiveFormsModule],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

saisie-numero-carte.component.html

<mat-form-field>
  <input
    matInput
    cartoppyNumCarte
    type="text"
    [formControl]="numeroCarteCtrl"
    [id]="labelforId"
    [placeholder]="placeholder"
    [required]="required"
    maxlength="19"
  />
  <mat-error *ngIf="numeroCarteCtrl.hasError('required')">
    La saisie de ce champ est obligatoire.
  </mat-error>
</mat-form-field>
<span
  >Valeur du champs :
  <pre>
    {{ numeroCarteCtrl.value }}
    {{ required }}
  </pre>
</span>

saisie-numero-carte.component.ts

import { Component, OnInit, ElementRef, Input, OnDestroy, ChangeDetectionStrategy, HostBinding, Optional, Self } from '@angular/core';
import { FormControl, NgControl, ControlValueAccessor} from '@angular/forms';
import { tap, distinctUntilChanged, takeUntil } from 'rxjs/operators';
import { MatFormFieldControl } from '@angular/material';
import { Subject } from 'rxjs';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { FocusMonitor } from '@angular/cdk/a11y';
/**
 * Composant pour saisir numéro carte
 */
@Component({
  selector: 'cartoppy-saisie-numero-carte',
  templateUrl: './saisie-numero-carte.component.html',
  styleUrls: ['./saisie-numero-carte.component.scss'],
  providers: [
    {
      provide: MatFormFieldControl,
      useExisting: SaisieNumeroCarteComponent
    }
  ],
  changeDetection: ChangeDetectionStrategy.OnPush
})

/**
 * Class Composant
 */
export class SaisieNumeroCarteComponent implements OnInit, ControlValueAccessor, MatFormFieldControl<string>, OnDestroy {
  static nextISaisieNumCarteComponent = 0;

  /**
   * Lié à l'attribut labelfor du label, il permet de donner le focus au champ
   */
  @Input()
  labelforId: string;

  /**
   * id du control
   */
  @HostBinding()
  id = `saisie-num-carte-${SaisieNumeroCarteComponent.nextISaisieNumCarteComponent++}`;

  /**
   * Form control du numéro carte
   */
  numeroCarteCtrl = new FormControl();

  stateChanges = new Subject<void>();

  describedBy = '';

  focused: boolean;

  shouldLabelFloat: boolean;

  private destroy$ = new Subject<void>();

  private _value = '';

  private _placeholder: string;

  private _required = false;

  private _readOnly = false;

  private _disabled = false;

  onChange = (_: any) => {};

  onTouched = () => {};

  get empty(): boolean {
    return !this.value;
  }

  /**
   * param disabled du control
   */
  @Input()
  get disabled(): boolean {
    return this._disabled;
  }
  set disabled(value: boolean) {
    this._disabled = coerceBooleanProperty(value);
    this._disabled ? this.numeroCarteCtrl.disable() : this.numeroCarteCtrl.enable();
    this.stateChanges.next();
  }

  /**
   * param placeholder du control
   */
  @Input()
  get placeholder(): string {
    return this._placeholder;
  }
  set placeholder(value: string) {
    this._placeholder = value;
    this.stateChanges.next();
  }

  /**
   * param required du control
   */
  @Input()
  get required(): boolean {
    return this._required;
  }
  set required(value: boolean) {
    this._required = coerceBooleanProperty(value);
    this.stateChanges.next();
  }

  /**
   * param readOnly du control
   */
  @Input()
  get readOnly(): boolean {
    return this._readOnly;
  }
  set readOnly(value: boolean) {
    this._readOnly = coerceBooleanProperty(value);
  }

  /**
   * param value du control
   */
  @Input()
  get value(): string | null {
    return this._value;
  }
  set value(numCarte: string | null) {
    console.log('numCarte: ', numCarte);
    if (numCarte) {
      this.numeroCarteCtrl.setValue(numCarte.replace(/\s+/g, ''));
      this.onChange(numCarte);
      this.stateChanges.next();
    }
  }

  get errorState() {
    return this.ngControl.errors !== null && !!this.ngControl.touched;
  }

  /**
   * constructor
   * @param _elementRef ElementRef<HTMLElement>
   */
  constructor(@Optional() @Self() public ngControl: NgControl, private fm: FocusMonitor, private _elementRef: ElementRef<HTMLElement>) {
    if (this.ngControl != null) {
      this.ngControl.valueAccessor = this;
    }
    fm.monitor(_elementRef.nativeElement, true).subscribe(origin => {
      this.focused = !!origin;
      if (!this.focused && typeof this.numeroCarteCtrl.value === 'string') {
        this.numeroCarteCtrl.setValue(undefined);
      }
      this.stateChanges.next();
    });
  }

  /**
   * Fonction appelée quand le composant est initié
   */
  ngOnInit() {
    this.numeroCarteCtrl.valueChanges
      .pipe(
        takeUntil(this.destroy$),
        distinctUntilChanged(),
        tap((numero: string) => {
          console.log('numero: ', numero);
          this.value = numero;
        })
      )
      .subscribe();
  }

  /**
   * Fonction de l'inteface ControlValueAccessor
   */
  writeValue(obj: string | null): void {
    console.log('----- In writeValue -----: ', obj);
    this.value = obj;
  }

  /**
   * Fonction de l'inteface ControlValueAccessor
   */
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  /**
   * Fonction de l'inteface ControlValueAccessor
   */
  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  /**
   * Fonction de l'inteface ControlValueAccessor
   */
  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  /**
   * Fonction du class MatFormFieldControl
   * @param ids  tableau
   */
  setDescribedByIds(ids: string[]): void {
    this.describedBy = ids.join(' ');
  }

  /**
   *  Fonction du class MatFormFieldControl
   * @param event MouseEvent
   */
  onContainerClick(event: MouseEvent): void {
    if ((event.target as Element).tagName.toLowerCase() !== 'input') {
      const input: HTMLInputElement | null = this._elementRef.nativeElement.querySelector('input');
      if (input) {
        input.focus();
      }
    }
  }

  /**
   * Fonction appelée avant la destruction du component
   */
  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
    this.stateChanges.complete();
    this.fm.stopMonitoring(this._elementRef.nativeElement);
  }
}

cartoppy-lib-carte.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SaisieNumeroCarteComponent } from './components/saisie-numero-carte/saisie-numero-carte.component';
import { MatFormFieldModule, MatInputModule, MAT_LABEL_GLOBAL_OPTIONS } from '@angular/material';
import { ReactiveFormsModule } from '@angular/forms';
import { NumCarteDirective } from './directives/num-carte.directive';

/**
 * Module CartoppyLibCarteModule
 */
@NgModule({
  imports: [CommonModule, MatFormFieldModule, MatInputModule, ReactiveFormsModule],
  declarations: [SaisieNumeroCarteComponent, NumCarteDirective],
  exports: [SaisieNumeroCarteComponent],
  providers: [{ provide: MAT_LABEL_GLOBAL_OPTIONS, useValue: { float: 'never' } }]
})
export class CartoppyLibCarteModule {}

【问题讨论】:

  • 使用ChangeDetectionStrategy的任何具体原因?尝试从两个组件中删除它并检查。
  • @AnkurAkvaliya 没有具体用法。删除它没有任何改变

标签: angular typescript angular-material


【解决方案1】:

我认为缺少将值更改传递给父级

formControl="numCarteCtrl" 更改为formControlName="numCarteCtrl"

在构造函数中添加下面

 this.numeroCarteCtrl.valueChanges.subscribe(val => {
   this.onChnage(val)
  });

或者在下面使用

   ngOnChanges(inputs) {
        this.onChange(this.numeroCarteCtrl.value);
    }

这里正在工作stackblitz

【讨论】:

  • @Sajus 您在将表单控件传递给自定义元素时有错字。使用 formControlName 将 form-control-name 作为字符串。查看更新的答案
  • @Sajus 我已经添加了工作堆栈闪电战。我会帮你调试你的问题。
  • 感谢您的帮助;)
  • 我也尝试过使用 formControlName 但没有成功
  • 也许你可以在 stackblitz 中复制你的代码然后我可以看到问题
猜你喜欢
  • 2020-10-02
  • 2021-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-07
  • 2019-02-10
  • 2017-01-28
  • 2021-04-26
相关资源
最近更新 更多