Я работаю над пользовательским компонентом материала, но во время тестирования входное значение не отправляется родительскому компоненту. Значение элемента управления формы в 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 {}
sais ie -numero-carte.component. html
<code><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 }}
sais ie -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 {}