Search code examples
javascriptangularrxjsangular-forms

Cannot Read Property


My console hows the following error when I am trying to run these. I tried checking the author property but i am not able to resolve it. The author is defined in the file called comment.ts where author is a string.

StackBlitz

ERROR TypeError: Cannot read property 'author' of undefined

Console points to these lines -

<input matInput formControlName="author" placeholder="Name" type="text" required>
            <mat-error *ngIf="formErrors.author">
              {{formErrors.author}}
            </mat-error>

The Typescript File Code - (dishdetails.component.ts)

    export class DishdetailsComponent implements OnInit {
  dish: Dish;
  dishIds: string[];
  prev: string;
  next: string;
  commentsForm: FormGroup;
  comment: Comment;
  formErrors: {
    'author': '';
    'comment': '';
  };

  validationMessages: {
    'author': {
      'required': 'Author Name is required.',
      'minlength': 'Author Name must be at least 2 characters long.',
      'maxlength': 'Author Name cannot be more than 25 characters long.'
    },
    'comment': {
      'required': 'Comment is required.',
      'minlength': 'Comment must be at least 1 characters long.'
    }
   };
  constructor(private dishservice: DishService, private route: ActivatedRoute,
    private location: Location,
    private fb: FormBuilder) {
      this.createForm();
    }

  ngOnInit() {
    this.dishservice.getDishIds().subscribe(dishIds => this.dishIds = dishIds);
    this.route.params.pipe(
      switchMap((params: Params) => this.dishservice.getDish(params['id'])))
      .subscribe(dish => { this.dish = dish; this.setPrevNext(dish.id); });
  }
  setPrevNext(dishId: string) {
      const index = this.dishIds.indexOf(dishId);
      this.prev = this.dishIds[(this.dishIds.length + index - 1) % this.dishIds.length];
      this.next = this.dishIds[(this.dishIds.length + index + 1) % this.dishIds.length];
  }
  goBack(): void {
    this.location.back();
  }

  createForm() {
     this.commentsForm = this.fb.group({
      author: ['', [Validators.required, Validators.minLength(2), Validators.maxLength(25)]],
      comment: ['', [Validators.required, Validators.minLength(1)]],
      rating : 5
    });
    this.commentsForm.valueChanges
    .subscribe(data => this.onValueChanged(data));
  }
  onValueChanged(data?: any) {
    if (!this.commentsForm) {
      return;
    }
    const form = this.commentsForm;
    for (const field in this.formErrors) {
      if (this.formErrors.hasOwnProperty(field)) {
        this.formErrors[field] = '';
        const control = form.get(field);
        if (control && control.dirty && !control.valid) {
          const messages = this.validationMessages[field];
          for (const key in control.errors) {
            if (control.errors.hasOwnProperty(key)) {
              this.formErrors[field] += messages[key] + ' ';
            }
          }
        }
      }
    }
  }

  onSubmit() {
    this.comment = this.commentsForm.value;
    const d = new Date();
    this.comment.date = d.toISOString();
    this.dish.comments.push(this.comment);
    console.log(this.comment);
    this.comment = null;
    this.commentsForm.reset({
      author: '',
      comment: '',
      rating: 5
    });
  }

The corresponding HTML File - (dishdetails.component.html)

<mat-list-item *ngFor="let comment of dish.comments">
          <p mat-line>
            <span> {{comment.comment}} </span>
          </p>
          <p mat-line>
            <span> {{comment.rating}} Stars</span>
          </p>
          <p mat-line>
            <span> -- {{comment.author}} {{comment.date | date}} </span>
          </p>
        </mat-list-item>
      </mat-list>
      <mat-list>
        <mat-list-item *ngIf=" comment && commentsForm.valid" [hidden]="comment">
          <p mat-line>
            <span> {{comment.comment}} </span>
          </p>
          <p mat-line>
            <span> {{comment.rating}} Stars</span>
          </p>
          <p mat-line>
            <span> -- {{comment.author}}</span>
          </p>
        </mat-list-item>
        </mat-list>

      <form novalidate [formGroup]="commentsForm" (ngSubmit)="onSubmit()">
        <p>
          <mat-form-field class="full-width">
            <input matInput formControlName="author" placeholder="Name" type="text" required>
            <mat-error *ngIf="formErrors.author">
              {{formErrors.author}}
            </mat-error>
          </mat-form-field>
        </p>
        <p>
          <mat-slider formControlName="rating" thumbLabel tickInterval="1" min="1" max="5" step="1" value="5"></mat-slider>   
        </p>
        <p> 
          <mat-form-field class="full-width">
            <textarea matInput formControlName="comment" placeholder="Your Comment" rows="12" required></textarea>
            <mat-error *ngIf="formErrors.comment">
              {{formErrors.comment}}
            </mat-error>
          </mat-form-field>
        </p>
        <button type="submit" mat-button class="background-primary text-floral-white" [disabled]="commentsForm.invalid">Submit</button>
      </form>
    </div>

    }

Solution

  • The formErrors.author property is undefined before you enter anything into the commentsForm. You have 2 options:

    Option 1

    Provide a default value for the formErrors.

    formErrors: { 'author': '', 'comment': '' } = { 'author': '', 'comment': ''};
    

    Option 2

    Or better, use the Elvis operator when accessing the properties. In Angular it's called safe navigation operator ?..

    Replace all the occurences of formErrors.author in the template with formErrors?.author. TBH, it's better to always include the Elvis operator in the template when trying to access properties of objects to avoid these undefined errors.

    The operator checks if formErrors is defined before trying to access it's properties.