Angular2 @ TypeScript Observable错误

12

我有一个输入字段,当用户键入搜索字符串时,我想等待用户停止输入至少300毫秒(防抖),然后再执行_heroService http请求。只有更改的搜索值才会传递到服务中(distinctUntilChanged)。switchMap返回一个新的observable,将这些_heroService observables组合起来,按照它们原始请求的顺序重新排列,并仅向订阅者提供最新的搜索结果。

我正在使用Angular 2.0.0-beta.0和TypeScript 1.7.5。

我该怎么做才能使这个东西正常工作?

我收到了编译错误:

Error:(33, 20) TS2345: Argument of type '(value: string) => Subscription<Hero[]>' is not assignable to parameter of type '(x: {}, ix: number) => Observable<any>'.Type 'Subscription<Hero[]>' is not assignable to type 'Observable<any>'. Property 'source' is missing in type 'Subscription<Hero[]>'.
Error:(36, 31) TS2322: Type 'Hero[]' is not assignable to type 'Observable<Hero[]>'. Property 'source' is missing in type 'Hero[]'.

运行时错误(在搜索输入字段中键入第一个键后):

EXCEPTION: TypeError: unknown type returned
STACKTRACE:
TypeError: unknown type returned
at Object.subscribeToResult (http://localhost:3000/rxjs/bundles/Rx.js:7082:25)
at SwitchMapSubscriber._next (http://localhost:3000/rxjs/bundles/Rx.js:5523:63)
at SwitchMapSubscriber.Subscriber.next (http://localhost:3000/rxjs/bundles/Rx.js:9500:14)
...
-----async gap----- Error at _getStacktraceWithUncaughtError 
EXCEPTION: Invalid argument '[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object]' for pipe 'AsyncPipe' in [heroes | async in Test@4:16]

测试1.ts

import {bootstrap}         from 'angular2/platform/browser';
import {Component}         from 'angular2/core';
import {HTTP_PROVIDERS}    from 'angular2/http';

import {Observable}        from 'rxjs/Observable';
import {Subject}           from 'rxjs/Subject';
import 'rxjs/Rx';

import {Hero}              from './hero';
import {HeroService}       from './hero.service';

@Component({
    selector: 'my-app',
    template: `
        <h3>Test</h3>
        Search <input #inputUser (keyup)="search(inputUser.value)"/><br>
        <ul>
            <li *ngFor="#hero of heroes | async">{{hero.name}}</li>
        </ul>
    `,
    providers: [HeroService, HTTP_PROVIDERS]
})

export class Test {

    public errorMessage: string;

    private _searchTermStream = new Subject<string>();

    private heroes: Observable<Hero[]> = this._searchTermStream
        .debounceTime(300)
        .distinctUntilChanged()
        .switchMap((value: string) =>
            this._heroService.searchHeroes(value)
                .subscribe(
                    heroes => this.heroes = heroes,
                    error =>  this.errorMessage = <any>error)
        )

    constructor (private _heroService: HeroService) {}

    search(value: string) {
        this._searchTermStream.next(value);
    }
}

bootstrap(Test);

hero.ts

export interface Hero {
    _id: number,
    name: string
}
hero.service.ts
import {Injectable}     from 'angular2/core';
import {Http, Response} from 'angular2/http';
import {Headers, RequestOptions} from 'angular2/http';
import {Observable}     from 'rxjs/Observable';
import 'rxjs/Rx';

import {Hero}           from './hero';

@Injectable()

export class HeroService {

    private _heroesUrl = 'api/heroes';

    constructor (private http: Http) {}

    getHeroes () {
        return this.http.get(this._heroesUrl)
            .map(res => <Hero[]> res.json())
            .do(data => console.log(data))
            .catch(this.handleError);
    }

    searchHeroes (value) {
        return this.http.get(this._heroesUrl + '/search/' + value )
            .map(res => <Hero[]> res.json())
            .do(data => console.log(data))
            .catch(this.handleError);
    }

    addHero (name: string) : Observable<Hero>  {

        let body = JSON.stringify({name});
        let headers = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: headers });

        return this.http.post(this._heroesUrl, body, options)
            .map(res =>  <Hero> res.json())
            .do(data => console.log(data))
            .catch(this.handleError)
    }

    private handleError (error: Response) {
        // in a real world app, we may send the server to some remote logging infrastructure
        // instead of just logging it to the console
        console.log(error);
        return Observable.throw('Internal server error');
    }
}

首页文件

<!DOCTYPE html>
<html>
  <head>
    <base href="/">
    <script src="angular2/bundles/angular2-polyfills.js"></script>
    <script src="typescript/lib/typescript.js"></script>
    <script src="systemjs/dist/system.js"></script>
    <script src="angular2/bundles/router.dev.js"></script>
    <script src="rxjs/bundles/Rx.js"></script>
    <script src="angular2/bundles/angular2.js"></script>
    <script src="angular2/bundles/http.dev.js"></script>
    <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
    <script>
      System.config({
        transpiler: 'typescript',
        typescriptOptions: { emitDecoratorMetadata: true },
        packages: {'components': {defaultExtension: 'ts'}}
      });
      System.import('components/test1')
            .then(null, console.error.bind(console));
    </script>
  </head>
  <body>
    <my-app>Loading...</my-app>
  </body>
</html>

这是另一个版本的“test2.ts”,在每个(keyup)事件后都可以正常执行http请求:

import {bootstrap}         from 'angular2/platform/browser';
import {Component}         from 'angular2/core';
import {HTTP_PROVIDERS}    from 'angular2/http';

import {Hero}              from './hero';
import {HeroService}       from './hero.service';

@Component({
    selector: 'my-app',
    template: `
        <h3>Test</h3>
        Search <input #inputUser (keyup)="search(inputUser.value)"/><br>
        <ul>
            <li *ngFor="#hero of heroes">{{hero.name}}</li>
        </ul>
    `,
    providers: [HeroService, HTTP_PROVIDERS]
})

export class Test {

    public heroes:Hero[] = [];
    public errorMessage: string;

    constructor (private _heroService: HeroService) {}

    search(value: string) {
        if (value) {
            this._heroService.searchHeroes(value)
                .subscribe(
                    heroes => this.heroes = heroes,
                    error =>  this.errorMessage = <any>error);
        }
        else {
            this.heroes = [];
        }
    }
}

bootstrap(Test);

你最好升级你的Angular 2参考资料,因为它已经不再是Beta版了。 - bilpor
1个回答

18

.subscribe(...) 返回一个 Subscription,而不是一个 Observable。 移除 subscribe(...) 或者用 .map(...) 替换它,并且当你访问该值时使用 .subscribe(...) 获取值。


非常感谢您的帮助。您能再具体一些吗?对我来说,Observables非常困难。我应该把.subscribe(..)块放在哪里? - Herman Fransen
1
你提供了很多代码,我没有完全调查它。我发现 *ngFor="#hero of heroes"应该与*ngFor="#hero of heroes | async"配合使用,async会为您执行subscribe(),否则您需要在想要访问值的位置使用 this.heroes.subscribe(...) - Günter Zöchbauer
感谢您的耐心等待,我是一个新手;-)我应该在哪里以及如何精确地放置 this.heroes.subscribe(...)?我猜测生命周期钩子 ngOnChanges() { this.heroes.subscribe(...) },但是这会导致运行时错误:EXCEPTION: Cannot find a differ supporting object '[object Object]' in [heroes in Test@4:16] 和编译错误:Error:(39, 23) TS2322: Type 'Hero[]' is not assignable to type 'Observable<Hero[]>'. Property 'source' is missing in type 'Hero[]'. - Herman Fransen
问题是你是否真的需要它。你尝试过只删除 subscribe(...) 部分吗?你已经在使用 | async 管道,那应该就可以了。否则,我猜这需要一个运行中的 plunker,否则仅从静态代码看来会太复杂了。 - Günter Zöchbauer
1
是的,使用 | async 管道可以使其正常工作。谢谢...但我更喜欢使用 .subscribe(...),因为我可以在其中添加错误处理。此外,我也喜欢学习它的工作原理... - Herman Fransen
还有一种操作符可以捕获错误,您不需要使用subscribe。如果您仍然想使用它,可以像这样使用:subscribe(value => this.someArr.push(value)),但这不是非常类似于react的风格。 - Günter Zöchbauer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接