简体   繁体   中英

Cannot read property of undefined in record edit form in angular 2

I have a problem in my edit form with undefined properties. I have an API that allows me to create and update customers. The problem is that some of the nested attributes are optional, this is fine when creating or editing fields that already exist but i can't figure out what to do when editing one of the nested fields on a customer that wasn't created with those fields and keep getting Cannot read property undefined errors.

This is my customer object:

export class Customer { 
  CompanyName: string;
  DisplayName: string;
  FamilyName: string; 
  GivenName: string;
  Id: number;
  Title: string;
  BillAddr: BillAddress;
  ShipAddr: ShipAddress;
  PrimaryPhone:  PrimaryPhoneNumber;
  Mobile: MobileNumber;
  PrimaryEmailAddr: PrimaryEmailAddress;
 }


  export class ShipAddress {
   City: string;
   Country: string;
   CountrySubDivisionCode: string; 
   Line1: string;
   Line2: string; 
   Line3: string; 
   Line4: string; 
   Line5: string; 
   PostalCode:string;
 }

   export class BillAddress {
   City: string;
   Country: string;
   CountrySubDivisionCode: string; 
   Line1: string;
   Line2: string; 
   Line3: string; 
   Line4: string; 
   Line5: string; 
   PostalCode:string;
 }

  export class PrimaryPhoneNumber {
    FreeFormNumber: number;
  }

  export class MobileNumber {
    FreeFormNumber: number;
  }

  export class PrimaryEmailAddress {
    Address: string; 
  }

This is the html from the edit component:

<h1 class="page-header">Edit Customer</h1>
<div [ngBusy]="busy"></div>
<form (ngSubmit)="onSubmit()">
<div class="row">
<div class="col-md-6">
<h3>Customer information</h3>
<div class="form-group">
    <label for="customertitle">Title</label>
    <input type="text" class="form-control" id="cutomertitle" placeholder="Title" name="title" [(ngModel)]="customer && customer.Title" >
</div>
<div class="form-group">
    <label for="customerGivenName">First Name</label>
    <input type="text" class="form-control" id="customerGivenName" placeholder="First Name" name="givenname" [(ngModel)]="customer && customer.GivenName" >
</div>
<div class="form-group">
    <label for="customerFamilyName">Last Name</label>
    <input type="text" class="form-control" id="customerFamilyName" placeholder="Surname" name="familyname" [(ngModel)]="customer && customer.FamilyName" >
</div>
<div class="form-group">
    <label for="customerEmailAddress">Email Address</label>
    <input type="text" class="form-control" id="customerEmailAddress" placeholder="Email" name="email" [(ngModel)]="customer && customer.PrimaryEmailAddr.Address" >
</div>
<div class="form-group">
    <label for="customerPhone">Phone</label>
    <input type="text" class="form-control" id="customerPhone" placeholder="Phone Number" name="primaryphone" [(ngModel)]="customer && customer.PrimaryPhone.FreeFormNumber" >
</div>
<div class="form-group">
    <label for="customerMobile">Mobile</label>
    <input type="text" class="form-control" id="customerMobile" placeholder="Mobile Number" name="mobile" [(ngModel)]="customer && customer.Mobile.FreeFormNumber" >
</div>
</div>

<div class="col-md-6">
<h3>Address:</h3>
<div class="form-group">
    <label for="customerLine1">Line 1</label>
    <input type="text" class="form-control" id="cutomerLine1" placeholder="Line 1" name="line1" [(ngModel)]="customer && customer.BillAddr.Line1" >
</div>
<div class="form-group">
    <label for="customerLine1">Line 2</label>
    <input type="text" class="form-control" id="cutomerLine2" placeholder="Password" name="line2" [(ngModel)]="customer && customer.BillAddr.Line2" >
</div>
<div class="form-group">
    <label for="customerLine1">Line 3</label>
    <input type="text" class="form-control" id="cutomerLine3" placeholder="Password" name="line3" [(ngModel)]="customer && customer.BillAddr.Line3" >
</div>
<div class="form-group">
    <label for="customerCity">City</label>
    <input type="text" class="form-control" id="customerCity" placeholder="Password" name="city" [(ngModel)]="customer && customer.BillAddr.City" >
</div><div class="form-group">
    <label for="customerLine1">State/Province</label>
    <input type="text" class="form-control" id="cutomerLine1" placeholder="Password" name="Province" [(ngModel)]="customer && customer.BillAddr.CountrySubDivisionCode" >
</div>
<div class="form-group">
    <label for="customerLine1">Postal Code</label>
    <input type="text" class="form-control" id="cutomerLine1" placeholder="Password" name="postcode" [(ngModel)]="customer && customer.BillAddr.PostalCode" >
</div>
</div>
</div>
<button type="submit" class="btn btn-default">Save Changes</button>
</form>

The current details are fetched oninit with this function:

getCustomer(id): void {
    this.busy = this.customersService.getCustomer(id)
          .then(customer => this.customer = customer);

  }

First problem is that data is coming async, so customer will be undefined when the view is rendered on all fields. Second problem is indeed that, since some fields do not have a value, will also throw an error.

Usually this could be solved by just initializing the customer :

customer = {};

But here again the problem is, that you have some deeper property paths that will though throw error despite this, like: customer.BillAddr.Line3 . Of course you could initialize aaaall properties you have in your object, but that seems just ugly to me.

All this can be solved with the safe navigation operator . Unfortunately two-way-binding, ie [(ngModel)] does not allow the safe navigation operator.

But!

[(ngModel)]="something" equals the following:

[ngModel]="something" (ngModelChange)="changeHappened($event)"

and one-way-binding does support the safe navigation operator. So what you could then do, is to use the ternary operator, that catches the input user makes and assigns it to your variable (in this example) with customer.Title = $event . And by using the ternary operator you get rid of the error undefined if there is no value initially with applying an empty string to the field:

[ngModel]="customer?.Title" 
(ngModelChange)="customer?.Title ? customer.Title = $event : ''"

(All credit goes to this answer: https://stackoverflow.com/a/36016472/6294072 )

Also I could suggest to use a model-driven form here where you could set all fields as empty initially and then just set values to the fields that have value and get rid of the ngModel. Just to throw in a second option.

But all in all, the above solution should get rid of your errors! :)

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