简体   繁体   中英

How to handle 2-way binding to a value of a null property of an object in Angular 8?

I am trying to do a 2-way binding that seems like it should be simple but I cannot figure this one out.

I have a select list which contains all Contact Types, this is displayed on a Contact details page.

Some Contacts will not have a Contact Type assigned to them. If no Contact Type is assigned, I have designed the Web API to return a null value for contact.contactType .

When trying to bind the list and the contact.contactType is null , I get the error 'Cannot read property 'id' of null' , which makes sense. This is breaking the page.

If possible, I would like to keep the API returning a null value. If none assigned, I am storing null in the database for the [Contact].[ContactTypeId] column.

Is the API & Database design incorrect and should be designed differently? I would like to think no. It's simply a [Contact] table with a column of [ContactTypeId] and a [ContactType] table with a column of [Id]. The SQL query for getting a Contact record joins the [ContactType].[Id] on the [Contact].[ContactTypeId]. Seems like that should work just fine.

Should I not return a null value for contact.contactType ?

How should I go about handling this situation?


I have tried using [(ngModel)]="contact.contactType?.id"> but I get the error 'The '?.' operator cannot be used in the assignment' 'The '?.' operator cannot be used in the assignment' .


API Response: Contact w/Contact Type

{
    "id": 1,
    "name": "John Doe",
    "contactType": {
        "id": 1,
        "name": "General Contractor",
        "description": "Some description"
    }
}

API Response: Contact w/No Contact Type

{
    "id": 1,
    "name": "John Doe",
    "contactType": null
}

API Response: Contact Types List

[
    {
        "description": "Aquatic Consultant description",
        "id": 1,
        "name": "Aquatic Consultant"
    },
    {
        "description": "Architect description",
        "id": 2,
        "name": "Architect"
    },
    {
        "description": "Facility description",
        "id": 3,
        "name": "Facility"
    },
    {
        "description": "General Contractor description",
        "id": 4,
        "name": "General Contractor"
    },
    // ... more contact types ...
]

contact-details.component.ts

@Component({
  selector: 'app-contact-details',
  templateUrl: './contact-details.component.html',
  styleUrls: ['./contact-details.component.scss']
})
export class ContactDetailsComponent implements OnInit {

  contact: Contact;
  contactTypes: ContactType[];

  constructor(
    private _apiService: ApiService,
    private _route: ActivatedRoute,
    private _toastr: ToastrService) { }

  ngOnInit() {
    this.getContact();
    this.getContactTypes();
  }

  private getContact(): void {
    const id = +this._route.snapshot.paramMap.get('id');
    this._apiService.contacts.findById(id)
      .subscribe(
        contact => { this.contact = contact; },
        error => {
          switch (error.status) {
            case 404:
              this._toastr.error('Contact not found');
              break;
            default:
              this._toastr.error(error);
              break;
          }
        });
  }

  private getContactTypes(): void {
    this._apiService.contacts.getContactTypes()
      .subscribe(
        types => this.contactTypes = types,
        error => this._toastr.error(error));
  }

contact-details.component.html

<mat-form-field class="w-100">
    <mat-label>Type</mat-label>
    <mat-select [(ngModel)]="contact.contactType.id"> <!-- Here is the error -->
        <mat-option [value]="null">None</mat-option>
        <mat-option *ngFor="let contactType of contactTypes" [value]="contactType.id">
            {{ contactType.name }}
        </mat-option>
    </mat-select>
</mat-form-field>

contact.model.ts

import { ContactType } from './contact-type.model';

export class Contact {
    constructor() { }

    public id: number;
    public name: string;
    public contactType: ContactType;
}

contact-type.model.ts

export class ContactType {
    constructor() { }

    public id: number;
    public description: string;
    public name: string;
}

I would try having an empty object before your api call. Your error might be caused by the delay it takes to your variable to be populated

in your contact-details.component.ts do something like

contact: Contact = {
    id: -1,
    name: "";
    contactType: {
        id: -1,
        description: '',
        name: ''
    }
}

this will avoid the browser to try to access an attribute out of null

I figured out a temporary solution to this. When loading the page with a Contact that has no Contact Type, the select list is binding properly and selecting the default value. Saving the Contact with no Contact Type assigned still saves null in the database table.

I am not a fan of doing it this way because there will be many more cases like this in the future with this project. Some pages will have many select lists that will have empty values and repeating this process over and over is not efficient.

So if you have a better solution, please feel free to post.

I changed the getContact() success callback to instantiate a blank ContactType if the API returns null for contact.contactType .


this._apiService.contacts.findById(id)
    .subscribe(
        contact => { 
            this.contact = contact; 
            if (!this.contact.contactType) {
                this.contact.contactType = new ContactType();
            }
        },
        error => {
            ... omitted ...
        }
    });

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM