0

I tried to implement the autocomplete function of angular.material.io. However, upon trying coding it in my code I run into the error of having undefined parameters. I don't know why this exactly is the case.

This is my implemented typescript code.

export class BooksComponent implements OnInit {
  private books: Array<Book> = [];
  //Autofillers & form controllers
  private bookCtrl = new FormControl();
  private filteredBooks: Observable<Book[]>;

  constructor() { 
    //Replace this in the future with data passed from the API
    JSONBooks.forEach(book =>
        this.books.push(new Audiobook(book.title, book.description, book.ISBN, book.image, book.price, book.amazonLink,
          book.publisher, book.amountOfChapters, book.soldCopies, book.adaptedForScreens, book.dateAdded, book.rating, book.amountOfHours))
      );
    //Formcontrol and autofills
    if (this.books) {
      this.filteredBooks = this.bookCtrl.valueChanges
      .pipe(
        startWith(''),
        map(book => book ? this.filterBooks(book) : this.books.slice())
      );
    }
    }

    ngOnInit() {}

    private filterBooks(value: Book): Book[] {
      return this.books.filter(
        //Matches book title
        book => book.getTitle.toLowerCase().indexOf(value.getTitle.toLowerCase()) === 0
        );
    }
}

This is my implemented HTML code.

I'd like to find a book with the title

    </p>
      <form>
        <mat-form-field>
          <input matInput placeholder="Title" aria-label="Title" [matAutocomplete]="auto" [formControl]="bookCtrl">
          <mat-autocomplete #auto="matAutocomplete">
            <mat-option *ngFor="let book of filteredBooks | async" [value]="book.getTitle">
              <span>{{book.getTitle}}</span>
            </mat-option>
          </mat-autocomplete>
        </mat-form-field>
      </form>
    <p>

Console output, line 118 is referring to my typescript code, this section: .indexOf(value.getTitle.toLowerCase())

core.js:15723 ERROR TypeError: Cannot read property 'toLowerCase' of undefined
    at books.component.ts:118
    at Array.filter (<anonymous>)
    at BooksComponent.push../src/app/books/books.component.ts.BooksComponent.filterBooks (books.component.ts:116)
    at MapSubscriber.project (books.component.ts:62)
    at MapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/map.js.MapSubscriber._next (map.js:35)
    at MapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (Subscriber.js:54)
    at MergeMapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/mergeMap.js.MergeMapSubscriber.notifyNext (mergeMap.js:84)
    at InnerSubscriber.push../node_modules/rxjs/_esm5/internal/InnerSubscriber.js.InnerSubscriber._next (InnerSubscriber.js:15)
    at InnerSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (Subscriber.js:54)
    at SafeSubscriber.schedulerFn [as _next] (core.js:13514)

I've checked multiple times but cannot seem to find the issue in my code.

NOTE: this.books does get filled and properties can be read. Methodes such as book.getTitle etc work aswell. Somehow the object doesn't get passed to the filterBooks method.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Ruben Szekér
  • 1,065
  • 1
  • 10
  • 21

3 Answers3

0

Add a simple if condition to check the value is not null

 this.books.filter(
        //Matches book title
        book => {
             if(!value.getTitle) return false
             return book.getTitle.toLowerCase().indexOf(value.getTitle.toLowerCase()) === 0
        } );
Sachila Ranawaka
  • 39,756
  • 7
  • 56
  • 80
  • I could do that and I won't get the error message, but the function also won't work. I want the function to work and autocomplete like the example in the link above shown. Thanks for you answer though! – Ruben Szekér May 06 '19 at 10:56
0

In your constuctor code you are using this.bookCtrl.valueChanges.pipe(). The pipe() function returns Observable. so you need to subscribe to that observable and assign this.filteredBooks variable in that.

this.bookCtrl.valueChanges
      .pipe(
        startWith(''),
        map(book => book ? this.filterBooks(book) : this.books.slice())
      )
      .subscribe((books) => this.filteredBooks = books);

Refer the documentation for Observables: http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html

Chetan Khilosiya
  • 491
  • 5
  • 16
  • I coppied your code but it didn't work it gave another error. I also don't get why I need to add this if it's not added in the example? Am I missing something? It gave the following erorr: ``` Type 'Book[]' is not assignable to type 'Observable'.ts(2322) Type 'Book[]' is missing the following properties from type 'Observable': _isScalar, source, operator, lift, and 5 more.ts(2740) ``` – Ruben Szekér May 06 '19 at 10:57
  • 1
    You should subscribe to Observable and then assign the result to variable. I have added the Observable documentation link for reference. – Chetan Khilosiya May 07 '19 at 06:23
  • I gave it a shot, but it doesn't seem to work like I expected it to. I added your code. Only had to change type of my filteredBooks to Book[] instead of Observable, yet it does not work. It gives another error. "BooksComponent.html:61 ERROR TypeError: Cannot read property 'dispose' of null at AsyncPipe.push../node_modules/@angular/common/fesm5/common.js.AsyncPipe._dispose (common.js:4753) at AsyncPipe.push../node_modules/@angular/common/fesm5/common.js.AsyncPipe.transform (common.js:4728)". I also tried viewing some videos about observables but no luck :/ – Ruben Szekér May 13 '19 at 18:38
0

The this.bookCtrl is a control for a text box. Thus, this.bookCtrl.valueChanges is emitting the text in the text-box.

I believe the following code will fix the issue.

// Formcontrol and autofills
if (this.books) {
  this.filteredBooks = this.bookCtrl.valueChanges
    .pipe(
      startWith(''),
      map(bookName => bookName ? this.filterBooks(bookName) : this.books.slice())
    );
}

And the filterBooks function:

  private filterBooks(bookName: string): Test[] {
    return this.books.filter(
      // Matches book title
      book => book.getTitle.toLowerCase().indexOf(bookName.toLowerCase()) === 0);
  }

Also, since bookCtrl and filteredBooks are being used on the html template, it is a good practice to change the access modifiers to public. For more information on this: "private" and "public" in Angular component

jamilsiam
  • 71
  • 1
  • 3
  • Your code does not completly fix the issue. However, nor does it give any errors. I "debugged" the code (I actually just used console.logs) and the filter function works fine and everything. However, it does not show the autocomplete suggestions on HTML. Maybe like Chetan said, I should add an observalbe, which makes sense to me but it doesn't work like I expected it to. – Ruben Szekér May 13 '19 at 18:39