Search code examples
angular

Angular cannot read property of undefined in component/form


I am trying to get UPDATE to work from my CRUD API. I know the issue is either in the form or component for UPDATE, but I'm not sure what I'm missing.

Form HTML

<form #form="ngForm" (ngSubmit)="updateReminder(form.value)">
  <div class="form-group">
    <label for="nameField">Name:</label>
    <input name="nameField" class="form-control" [ngModel]="reminders.name">
    <br />
    <label for="occasionField">Occasion:</label>
    <input name="occasionField" class="form-control" 
[ngModel]="reminders.occasion">
 </div>
 <button type="submit">save</button>
</form>

Form Component

import { Component, OnInit, Input } from '@angular/core';
import { ReminderService } from '../providers/reminder.service';

@Component({
  selector: 'app-results',
  templateUrl: './results.component.html',
  styleUrls: ['./results.component.css'],
  providers: [ReminderService]

})
export class ResultsComponent implements OnInit {

constructor(private reminderService:ReminderService) {}

 reminderList = null;
 @Input() reminders; 


ngOnInit() {}   


updateReminder(obj:any):void {
 this.reminders.name = obj.nameField;
 this.reminders.occasion = obj.occasionField;
 this.reminderService.updateReminder(this.reminders._id, this.reminders)
  .subscribe((result) =>{
    location.reload();
 })
}

App Component HTML

<div class="container">
<app-intro title="{{title}}"></app-intro>
<app-form ></app-form>
<app-results *ngFor='let reminder of reminderList' [reminders]="reminder">
</app-results>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256- FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"> 

App Component (There are no routers)

import { Component } from '@angular/core';
import { ReminderService } from './providers/reminder.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [ReminderService]
})
export class AppComponent {

 title = 'The Amazing Reminder App';

  constructor(private reminderService:ReminderService) {}

  reminderList = null;

 ngOnInit() {
    this.reminderService.listReminders().subscribe((reminders)=>{
        this.reminderList = reminders;
    });
 } 
}

The error is in the HTML:

ERROR TypeError: Cannot read property 'name' of undefined

<input name="nameField" class="form-control" [ngModel]="reminders.name">

Solution

  • The problem is that as an Input variable, the reminders object is initially undefined until angular has finished initializing the component. See this line right here.

    @Input() reminders;
    

    The html part however does not wait to render and therefore tries to use this variable very early on.

    <input name="nameField" class="form-control" [ngModel]="reminders.name">
    

    So when it tries to resolve the model before the input variable is set during the initialization process, it hits an undefined variable and tries to find a name field in it, which of course does not exist yet.

    There are multiple ways to solve this problem.

    1. give the reminders object a default value, maybe even an empty object. I personally think this way you sacrifice memory usage to prevent errors, which seems kind of unclean
    2. use the elvis operator as a null check in your html: [ngModel]="reminders?.name". You have to do this in every field that tries to access a field of the reminders object, which can be a lot of code depending on your form
    3. only render the form when the reminders object is finally set. This can easily be done with a simple *ngIf on the surrounding div or form: <div class="form-group" *ngIf="reminders">

    This is probably a personal choice but for me the last option seems to be the cleanest. There's no reason to render your view before the needed values are existing and then rerender it when they are set.

    There are more questions around this topic, for example you can check out this one right here.

    Hope this helps.


    EDIT

    So I rechecked your code and while your updateReminder function is the form submit, I guess this is a pure update form. In this case, I'd say that option 3 should be working fine. Angular does some neat change detection and rerendering, so you should probably first check out if the input variable is set correctly. If yes, angular should do the rest and rerender the *ngIf part the moment it realizes that the object exists now.

    Before we check the input variable it is always helpful to understand how angular handles input variables, which in the end boils down to the so called Lifecycle Hooks. One special hook is the ngOnInit() hook. Here's what angular's documentation says about the hook:

    Initialize the directive/component after Angular first displays the data-bound properties and sets the directive/component's input properties

    What this basically means is that variables transported to the component as an @Input are not set when the component is created (constructor code), but only after it was initialized. This results in some execution delay and in the end to the error you stated above.

    To check out if the input variable was set correctly, we can just log it to the console inside ngOnInit().

    ngOnInit() {
      console.log(this.reminders);
    }  
    

    As a comparision, if you do this in the constructor, you will see that it is still empty at this point.

    constructor(private reminderService:ReminderService) {
      // should be undefined at this point
      console.log(this.reminders);
    }
    

    So if the reminders variable is logged from the ngOnInit, the *ngIf approach should work fine.