0

I am working on an Angular Reactive Form where our system sends an email to a customer. The controls and email template are set out in a JSON file that is in Contentstack (not relevant, just FYI). When the user wants to send an email, they select the template they want to use and fill out a form. They can then see a preview of the email before it is sent.

I've been working on an issue where for one template, Appointment Reminder, the date was not showing in the proper format in the template. I have fixed the format problem, but now I have another problem.

The form for the Appointment Reminder template has five input fields: First Name, Last Name, Agent Secondary Phone, Appointment Date, and Appointment Time. All fields except Agent Secondary Phone are required by Validators. When the form opens, the First and Last names are auto-filled by the backend, and the Appointment Date is prefilled with the current date. I am using MatDatePicker from Angular Material on this field, so there is an icon at the end of the line where if you click on it and choose another date, the field will change to the date selected in the datepicker.

Here's the problem: When you fill out the Appointment Time field and hit tab or click outside the field (to enable the Next button so you can see the preview), the date in the Appointment Date field disappears, and the field turns red because of the Validator. This happens regardless of whether you picked a new date or just left it as today's date.

The interesting part, though, is that the Next button remains enabled (contrary to the Validation error), and if you click next and go to the template, the date you selected (or the current date if you didn't) appears in the template. So the date hasn't been lost.

I cannot figure out why the date is disappearing nor how to fix it. Any help is appreciated.

Here is the code at issue. There are many different email templates with various fields and validators and such, not just the Appointment Reminder template, so some of the code doesn't apply to the Appointment Reminder template.

Here is the HTML file for the Angular component:

<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>

Here is the Typescript file for the component:

import { Component, EventEmitter, Input, 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';

@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 {
  @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;

  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() {
    if (this.formGroup.valid) {
      Object.keys(this.formGroup.controls).forEach(key => {
        console.log('key is ' + key);
        console.log('key includes date T or F? ' + (key).includes('Date'));
        if ((key).includes('Date')) {
          console.log('within onChange if block');
          console.log('date is currently ' + this.formGroup.get(key).value);
          const formattedDate = formatDate(this.formGroup.get(key).value, 'MM/dd/yyyy', 'en-US');
          console.log('formattedDate is ' + formattedDate);
          this.formGroup.get(key).setValue(formattedDate);
          console.log('date is now ' + this.formGroup.get(key).value);
        }
      });
      this.onTemplateDetailedFilled.emit(true);
    } else {
      this.onTemplateDetailedFilled.emit(false);
    }
  }
}

Here is the JSON file that contains the controls and email template:

{
  "controls": [
    {
      "name": "firstName",
      "label": "First name",
      "value": "",
      "path": "customer.firstName",
      "type": "text",
      "validators": {
        "required": true
      }
    },
    {
      "name": "lastName",
      "label": "Last name",
      "value": "",
      "path": "customer.lastName",
      "type": "text",
      "validators": {
        "required": true
      }
    },
    {
      "name": "agentSecondaryPhone",
      "label": "Agent Secondary Phone",
      "value": "",
      "path": "",
      "type": "text",
      "validators": {
        "required": false
      }
    },
    {
      "name": "appointmentDate",
      "label": "Appointment date",
      "value": "",
      "path": "",
      "type": "date",
      "validators": {
        "required": true
      }
    },
    {
      "name": "appointmentTime",
      "label": "Appointment time",
      "value": "",
      "path": "",
      "type": "time",
      "validators": {
        "required": true
      }
    }
  ],
  "emailTemplate": "Dear ${firstName}, <p></p> <p></p> Your appointment is ${appointmentDate} at ${appointmentTime} to discuss the information I sent you. Please call me 24 hours prior to your phone appointment if you are unable to keep it.",
  "exactTargetMapping": {
    "FirstName": "firstName",
    "LastName": "lastName",
    "State": "state",
    "AgentPhone": "agentPhone",
    "AgentEmail": "agentEmail",
    "AgentName": "agentName",
    "AppointmentDate": "appointmentDate",
    "AppointmentTime": "appointmentTime",
    "CA_LicenseNumber": "caLicenseNumber",
    "AgentSecondaryPhone": "agentSecondaryPhone"
  },
  "messageMetdata": {
    "firstName": "path:control.firstName",
    "lastName": "path:control.lastName",
    "state": "path:customer.state",
    "agentPhone": "path:agent.agentPhone",
    "agentEmail": "path:agent.agentEmail",
    "agentName": "path:agent.agentName",
    "appointmentDate": "path:control.appointmentDate",
    "appointmentTime": "path:control.appointmentTime",
    "caLicenseNumber": "path:agent.caLicenseNumber",
    "agentSecondaryPhone": "path:control.agentSecondaryPhone"
  }
}
mat.hudak
  • 2,803
  • 2
  • 24
  • 39
apex2022
  • 777
  • 2
  • 8
  • 28

0 Answers0