0

I'm losing it here. There are so many questions on this subject, and no matter what I try I can't get it to work.

I'm simply wanting to set the default value of a mat-select.

I have a list of Employees. Clicking on a list item opens a dialog (Employee Details) so I can edit (or add) the Employee's data. I just want the Department mat-select to display the Employee's current department when the mode is edit.

employee-details.component.html

<div fxLayout="column">
   <div class="flex-item" fxFlex mat-dialog-actions fxLayoutAlign="end">
      <button mat-mini-fab color="" aria-label="Cancel" (click)="onCancelClick()">
         <mat-icon>clear</mat-icon>
      </button>
   </div>
   <h4 *ngIf="data.action == 'add'">Add New Employee</h4>
   <h4 *ngIf="data.action == 'edit'">Edit Employee</h4>
   <form [formGroup]="form" class="employee-form">
      <div class="form-group">
         <mat-form-field class="employee-full-width">
            <input
               formControlName="firstname"
               matInput
               required
               placeholder="Employee Firstname"
               >
            <mat-error
               *ngIf="
               form.get('firstname').touched && form.get('firstname').invalid
               "
               >Employee Firstname is Required!</mat-error
               >
         </mat-form-field>
         <mat-form-field class="employee-full-width">
            <input
               formControlName="lastname"
               matInput
               required
               placeholder="Employee Lastname"
               >
            <mat-error
               *ngIf="
               form.get('lastname').touched && form.get('lastname').invalid
               "
               >Employee Lastname is Required!</mat-error
               >
         </mat-form-field>
         <mat-form-field class="employee-full-width">
            <input
               formControlName="title"
               matInput
               required
               placeholder="Employee Title"
               >
            <mat-error
               *ngIf="
               form.get('title').touched && form.get('title').invalid
               "
               >Employee Title is Required!</mat-error
               >
         </mat-form-field>
         <mat-form-field class="employee-full-width">
            <input
               formControlName="rate"
               matInput
               required
               placeholder="Employee Rate"
               >
            <mat-error
               *ngIf="
               form.get('rate').touched && form.get('rate').invalid
               "
               >Employee Rate is Required!</mat-error
               >
         </mat-form-field>
         <mat-form-field class="employee-full-width">
            <mat-label>Department</mat-label>
            <mat-select formControlName="department">
               <mat-option *ngFor="let dept of departments" [value]="dept">
               {{ dept?.title }}
               </mat-option>
            </mat-select>
         </mat-form-field>
      </div>
   </form>
   <div class="flex-item" fxFlex mat-dialog-actions fxLayoutAlign="end">
      <button
         mat-raised-button
         color="secondary"
         class="project-add-button"
         (click)="onNoClick()"
         >
      Cancel
      </button>
      <button
      mat-raised-button
      [disabled]="!form.valid"
      color="primary"
      class="project-add-button"
      (click)="createEmployee(form)"
      >
      Save & Close
      </button>
      <button
      mat-raised-button
      [disabled]="!form.valid"
      color="primary"
      class="project-add-button"
      (click)="createEmployee(form)"
      >
      Save & New
      </button>
   </div>
</div>
<div>
   <div>
      <br />
      <br />
      <mat-accordion>
         <mat-expansion-panel>
            <mat-expansion-panel-header>
               <mat-panel-title>
                  Form value
               </mat-panel-title>
            </mat-expansion-panel-header>
            <code>
               <div fxLayout="column" fxLayoutGap="32px">
                  <div>{{ form.value | json }}</div>
                  <div>Form Validity: {{ form.valid | json}}</div>
               </div>
            </code>
         </mat-expansion-panel>
      </mat-accordion>
   </div>
</div>

employee-details.component.ts

import { Component, OnInit, Optional, Inject, AfterViewInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { EmployeeService } from 'src/app/core/services/entity/employee.service';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
import { DialogData } from 'src/app/employees/employee-list/employee-list.component';
import { DepartmentService } from 'src/app/core/services/entity/department.service';
import { Department } from 'src/app/shared/models/department';

@Component({
  selector: 'app-employee-details',
  templateUrl: './employee-details.component.html',
  styleUrls: ['./employee-details.component.css']
})
export class EmployeeDetailsComponent implements OnInit, AfterViewInit {
  departments: Department[];
  selectedDepartment: Department;

  form = new FormGroup({
    id: new FormControl(''),
    firstname: new FormControl(''),
    lastname: new FormControl(''),
    title: new FormControl(''),
    rate: new FormControl(''),
    department: new FormControl(''),
  });

  constructor(
    private departmentService: DepartmentService,
    private service: EmployeeService,
    public dialogRef: MatDialogRef<EmployeeDetailsComponent>,
    @Optional() @Inject(MAT_DIALOG_DATA) public data: DialogData
  ) {
    if (data.action === 'edit') {
      this.form.patchValue(data.employee);
    }
  }

  ngOnInit() {
    this.departmentService.departments$.subscribe(r => {
      this.departments = r;
    });
  }
  // **** THIS DOESN'T ACTUALLY DO ANYTHING, AS FAR AS I CAN TELL ****
  ngAfterViewInit() {
    this.form.controls.department.setValue(this.data.employee.department);
  }

  createEmployee(f: FormGroup) {
    if (this.data.action === 'edit') {
      this.service.updateEmployee(f.value).then(response => {
        this.closeDialog();
      });
      // creating a new one
    } else {
      this.service.createEmployee(f.value).then(response => {
        this.closeDialog();
      });
    }
  }

  onCancelClick() {
    this.onNoClick();
  }

  onNoClick() {
    this.dialogRef.close({
      event: 'cancel'
    });
  }

  closeDialog() {
    this.dialogRef.close({
      event: this.data.action,
      data: this.data.employee
    });
  }
}

I'm displaying the 'data' at the bottom of the dialog using:

<div>{{ form.value | json }}</div>

and it looks like (which tells me the Employee object is making it over to the dialog just fine):

{
    "id": "9UAPoKCFofH7aWBUwdiO",
    "firstname": "John",
    "lastname": "Doe",
    "title": "Manager",
    "rate": "120",
    "department": {
        "code": "1000",
        "id": "2PtDUwPBXT7pUPWTOibD",
        "title": "Project Management"
    }
}

To populate the departments mat-select, I'm using a service which CRUDs Firebase:

  ngOnInit() {
    this.departmentService.departments$.subscribe(r => {
      this.departments = r;
    });
  }

The relevant HTML from the template:

<mat-form-field class="employee-full-width">
  <mat-label>Department</mat-label>
    <mat-select formControlName="department">
      <mat-option *ngFor="let dept of departments" [value]="dept">
       {{ dept?.title }}
      </mat-option>
    </mat-select>
</mat-form-field>

employee.ts

import { Department } from './department';

export interface Employee {
    id: string;
    firstname: string;
    lastname: string;
    title: string;
    department: Department;
    rate: number;
}

department.ts

export interface Department {
    id: string;
    title: string;
    code: string;
}

2 Answers 2

3

The reason is because the department object in your employee record does not match the department object in your query result. Yes, the values match, but the object reference does not match (its a separate object).

You can use the [compareWith] attribute to pass in a function that compares the ids of the objects. mat-select supports the same [compareWith] functionality as the standard Angular select. https://angular.io/api/forms/SelectControlValueAccessor#customizing-option-selection

So you would have this in your template:

<mat-select formControlName="department" [compareWith]="compareFn">

And then in your component:

compareFn(d1: Department, d2: Department): boolean {
    return d1 && d2 ? d1.id === d2.id : d1 === d2;
}
Sign up to request clarification or add additional context in comments.

1 Comment

Perfect. I could of read that documentation a hundred times without understanding it. Now it's clear.
0

Try

ngAfterViewInit() {
 this.form.controls['department'].setValue(this.data.employee.department);
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.