Aaron Balthaser
Aaron Balthaser

Reputation: 2622

Typescript thinks response array is an object

I have an Observable stream that obviously outputs an array into the subscribe block. I am trying to assign the response to an array variable to render the autocompletion results. Everything works fine except for the typescript error.

Autocomplete Component:

@Component({
  selector: 'auto-complete',
  styleUrls: ['auto-complete.component.scss'],
  template: `
    <div class="ac-container">
      <div class="ac-input">
        <input type="text" [(ngModel)]="query" (keyup)="filter()">
      </div>
      <div class="ac-results" *ngIf="results.length > 0">
        <ul *ngFor="let item of results">
          <li>
            <a (click)="select(item)">{{item}}</a>
          </li>
        </ul>
      </div>
    </div>
  `
})

export class AutoCompleteComponent {
  @Input() fn: Function;

  public query = '';
  public results = [];

  filter() {
    let value = Observable
      .from(this.query)
      .throttleTime(20)
      .map(value => value)
      .distinctUntilChanged()
      .map(search => this.fn(search))
      .switch()
      .subscribe(response => {
        this.results = response;
      }, error => {}
    );
  }
}

Parent Component:

@Component({
  selector: 'auto-completor',
  template: `<auto-complete [fn]="apiSearch"></auto-complete>`
})

export class AppComponent implements OnInit {
  public results: any;

  constructor(
    private service: AppService
  ) {}

  public apiSearch(term) {
    return this.service.getSearchData(term);
  }

  ngOnInit() {
    this.apiSearch = this.apiSearch.bind(this);
  }
}

Error:

enter image description here

IED Error Indication:

enter image description here

I wish I could show examples of things I tried but all I did was googled. I have no idea. Thanks.

Edit / Additions

Fake DB/Http Response

import { Injectable } from '@angular/core';

@Injectable()
export class Database {
  private FAILURE_COEFF = 10;
  private MAX_SERVER_LATENCY = 200;

  private getRandomBool(n) {
    var maxRandomCoeff = 1000;
    if (n > maxRandomCoeff) n = maxRandomCoeff;
    return Math.floor(Math.random() * maxRandomCoeff) % n === 0;
  }

  public getSuggestions(text) {
    var pre = 'pre';
    var post = 'post';
    var results = [];
    if (this.getRandomBool(2)) {
      results.push(pre + text);
    }
    if (this.getRandomBool(2)) {
      results.push(text);
    }
    if (this.getRandomBool(2)) {
      results.push(text + post);
    }
    if (this.getRandomBool(2)) {
      results.push(pre + text + post);
    }

    return new Promise((resolve, reject) => {
      var randomTimeout = Math.random() * this.MAX_SERVER_LATENCY;
      setTimeout(() => {
        if (this.getRandomBool(this.FAILURE_COEFF)) {
          reject();
        } else {
          resolve(results);
        }
      }, randomTimeout);
    });
  }
}

App Service Converting the promise response to Observable

export class AppService {

  constructor(
    private database: Database
  ) {}

  public getSearchData(term) {
    return Observable.defer(() => {
      return Observable.fromPromise(this.database.getSuggestions(term)
        .then(function(res) {
          return res;
        })
      );
    })
  }
}

Upvotes: 4

Views: 1488

Answers (1)

Niek Janssen
Niek Janssen

Reputation: 56

The problem is that the Observable is not typed, because your function fn has no call signature. I cannot check it at the moment, but if you would give fn a lambda expression call signature, the observable will probably take over it's types, enabling you to assign it to results.

@Input() fn: (string) => string[];

Alternatively, you could type results as any, but that's just a quick and very dirty workaround to remove types altogether.

Upvotes: 1

Related Questions