我在 Angular Material Datepicker 中发出日期时遇到了一个持续的问题。我正在使用一个应用程序,其中 Contentstack 中有 JSON 文件。 JSON 文件包含 Angular Reactive 表单的控件以及每个控件的电子邮件模板。在一些模板中有一个日期控件。 JSON 文件的想法是,反应式表单填充 JSON 文件中的电子邮件模板,然后将其发送到视图。我在表单中使用 MatDatepicker 来选择日期。我一直在努力解决的问题是,即使表单显示的日期为 08/31/2019,当电子邮件预览在视图中显示时,日期显示为 2019 年 8 月 31 日星期四 00:00 等。我想要它在电子邮件中显示为 8/31/2019。我知道问题与格式化选择和发射之间的日期有关。
我找到了解决这个问题的方法。每次进行更改时,我们都会调用一个名为 onChange() 的方法。该方法只是检查表单是否有效,如果有效,则将 EventEmitter 发射设置为 true,否则设置为 false。我发现如果我循环 formGroup 键,取出 formControlName 中带有日期的键,订阅它们的 valueChanges,然后对每次更改进行日期格式化,电子邮件预览将正确显示日期。
问题就在这里。完成此操作后,当您单击日期选择器日历上的日期时,日历将不再消失。您必须在日历外部单击才能使其消失。与此同时,即使您让日历消失,调用也会继续进行,直到您最终达到调用堆栈限制。
根据此处的其他问题和答案,我尝试添加一个 Subscription 对象并将其与 valueChanges 一起使用,然后在 ngOnDestroy 中取消订阅它。这对解决问题没有任何帮助。谁能告诉我如何在检测到并格式化值更改后关闭呼叫?
这是组件 TypeScript 文件:
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ContentStackService } from 'src/app/services/content-stack.service';
import { Customer } from 'src/app/models/customer.model';
import { AdditionalEmailTemplateDefintionResponse, Control, TemplateDefinition } from 'src/app/models/additional-email-template-definition.model';
import { formatDate } from '@angular/common';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-ssp-send-additional-email-template-detail',
templateUrl: './ssp-additional-email-template-detail.component.html',
styleUrls: ['./ssp-additional-email-template-detail.component.scss'],
})
export class SspSendAdditionalEmailTemplateDetailComponent implements OnInit, OnDestroy {
@Input() customer : Customer;
@Input() selectedTemplateId:string;
@Input() formGroup: FormGroup;
@Input() productType: string;
@Input() stateCode: string;
@Output() onTemplateDetailedFilled = new EventEmitter();
@Output() templateDefinitionEmitter = new EventEmitter<TemplateDefinition>();
customerInfoLookUp = new Map<string, string>();
public templateDefinition!: TemplateDefinition;
public subscr: Subscription;
constructor(
private contentStackService: ContentStackService,
private fb: FormBuilder) {}
ngOnInit(): void {
console.log('*****SspSendAdditionalEmailTemplateDetailComponent****');
console.log('input customer', this.customer);
// Build customer lookup used for dynamic form population.
this.buildCustomerLookup();
this.contentStackService.getAdditionalEmailTemplateDefinition(this.selectedTemplateId)
.subscribe((formData: AdditionalEmailTemplateDefintionResponse) => {
console.log('getAdditionalEmailTemplateDefinition', formData);
this.templateDefinition = formData.template_definition;
this.createForm(formData.template_definition.controls);
this.templateDefinitionEmitter.emit(this.templateDefinition);
});
}
createForm(controls: Control[]) {
for (const control of controls) {
const validatorsToAdd = [];
for (const [key, value] of Object.entries(control.validators)) {
switch (key) {
case 'min':
validatorsToAdd.push(Validators.min(value));
break;
case 'max':
validatorsToAdd.push(Validators.max(value));
break;
case 'required':
if (value) {
validatorsToAdd.push(Validators.required);
}
break;
case 'requiredTrue':
if (value) {
validatorsToAdd.push(Validators.requiredTrue);
}
break;
case 'email':
if (value) {
validatorsToAdd.push(Validators.email);
}
break;
case 'minLength':
validatorsToAdd.push(Validators.minLength(value));
break;
case 'maxLength':
validatorsToAdd.push(Validators.maxLength(value));
break;
case 'pattern':
validatorsToAdd.push(Validators.pattern(value));
break;
case 'nullValidator':
if (value) {
validatorsToAdd.push(Validators.nullValidator);
}
break;
default:
break;
}
}
if (control.type === 'date') {
this.formGroup.addControl(
control.name,
this.fb.control(new Date(), validatorsToAdd),
);
} else {
console.log('control.value', control);
if (control.path) {
const pathSplit = control.path.split('.');
if (pathSplit[0] === 'customer') {
control.value = this.customerInfoLookUp.get(pathSplit[1]);
}
}
this.formGroup.addControl(
control.name,
this.fb.control(control.value, validatorsToAdd),
);
this.onChange();
}
}
}
buildCustomerLookup() {
this.customerInfoLookUp.set('firstName',
this.customer.person.personNameList[0].firstName);
this.customerInfoLookUp.set('lastName', this.customer.person.personNameList[0].lastName);
}
onChange() {
Object.keys(this.formGroup.controls).forEach(key => {
if ((key).includes('Date')) {
this.subscr = this.formGroup.get(key).valueChanges.subscribe(x => {
console.log('Value of ' + key + ' changed to ' + x);
this.formGroup.get(key).setValue(formatDate(x, 'MM/dd/YYYY', 'en-US'));
});
}
});
if (this.formGroup.valid) {
this.onTemplateDetailedFilled.emit(true);
} else {
this.onTemplateDetailedFilled.emit(false);
}
}
ngOnDestroy(): void {
this.subscr.unsubscribe();
}
}
这是该组件的 HTML:
<ng-container [formGroup]="formGroup" *ngFor="let control of templateDefinition?.controls" class="ssp-template-detail-full-width">
<!-- input type text-->
<mat-form-field *ngIf="[
'text',
'password',
'email',
'number',
'search',
'tel',
'url'
].includes(control.type)" class="ssp-template-detail-full-width">
<mat-label>{{control.label}}</mat-label>
<input matInput [id]="control.name" [formControlName]="control.name" [type]="control.type" (change)="onChange()">
</mat-form-field>
<!-- input type textarea -->
<mat-form-field *ngIf="[
'textarea'
].includes(control.type)" class="ssp-template-detail-full-width">
<mat-label>{{control.label}}</mat-label>
<textarea matInput [id]="control.name" [formControlName]="control.name" (change)="onChange()"></textarea>
</mat-form-field>
<!-- checkbox-->
<p *ngIf="[
'checkbox'
].includes(control.type)" class="ssp-template-detail-full-width">
<mat-checkbox [id]="control.name" [formControlName]="control.name" (change)="onChange()">{{control.label}}</mat-checkbox>
</p>
<!-- dropdown-->
<mat-form-field *ngIf="[
'dropdown'
].includes(control.type)" class="ssp-template-detail-full-width">
<mat-label>{{control.label}}</mat-label>
<mat-select [id]="control.name" [formControlName]="control.name" (change)="onChange()">
<mat-option *ngFor="let option of control.options" [value]="option.value">
{{option.option}}
</mat-option>
</mat-select>
</mat-form-field>
<!-- date -->
<mat-form-field *ngIf="[
'date'
].includes(control.type)" class="ssp-template-detail-full-width">
<mat-label>{{control.label}}</mat-label>
<input matInput [matDatepicker]="picker" [id]="control.name" [formControlName]="control.name" (change)="onChange()">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
<mat-hint>MM/DD/YYYY</mat-hint>
</mat-form-field>
<!-- input type text-->
<mat-form-field *ngIf="[
'time'
].includes(control.type)" class="ssp-template-detail-full-width">
<mat-label>{{control.label}}</mat-label>
<input matInput [id]="control.name" [formControlName]="control.name" type="text" (change)="onChange()">
</mat-form-field>
</ng-container>
export class SspSendAdditionalEmailTemplateDetailComponent ...
private subscriptionList = new Subscription(); // Instantiate it immediately
...
onChange() {
Object.keys(this.formGroup.controls).forEach(key => {
if ((key).includes('Date')) {
// This is why I call it subscriptionList, you are adding subscriptions to the list
this.subscriptionList.add(
this.subscr = this.formGroup.get(key).valueChanges.subscribe(x => {
console.log('Value of ' + key + ' changed to ' + x);
this.formGroup.get(key).setValue(formatDate(x, 'MM/dd/YYYY', 'en-US'));
})
);
}
});
if (this.formGroup.valid) {
this.onTemplateDetailedFilled.emit(true);
} else {
this.onTemplateDetailedFilled.emit(false);
}
}
ngOnDestroy(): void {
// Then you tell the entire list to unsubscribe.
// If a sub in the list is already unsubscribed, it silently passes.
this.subscriptionList.unsubscribe();
}