【问题标题】:Angular2: How to subscribe to Http.post observable inside a service and a component properly?Angular2:如何正确订阅服务和组件内的 Http.post 可观察对象?
【发布时间】:2026-02-10 13:15:01
【问题描述】:

对于 JWT 身份验证,我现在使用新的 Http 模块与 Observables 一起发出请求以获取令牌。

我有一个简单的Login 组件显示表单:

@Component({
selector: 'my-login',
    template: `<form (submit)="submitForm($event)">
                <input [(ngModel)]="cred.username" type="text" required autofocus>
                <input [(ngModel)]="cred.password" type="password" required>
                <button type="submit">Connexion</button>
            </form>`
})
export class LoginComponent {
    private cred: CredentialsModel = new CredentialsModel();

    constructor(public auth: Auth) {}

    submitForm(e: MouseEvent) {
        e.preventDefault();
        this.auth.authentificate(this.cred);
    }
}

我有一个 Auth 服务发出请求:

@Injectable()
export class Auth {
    constructor(public http: Http) {}

    public authentificate(credentials: CredentialsModel) {
        const headers = new Headers();
        headers.append('Content-Type', 'application/json');

        this.http.post(config.LOGIN_URL, JSON.stringify(credentials), {headers})
            .map(res => res.json())
            .subscribe(
                data => this._saveJwt(data.id_token),
                err => console.log(err)
            );
    }
}

效果很好,但现在我想在我的组件中显示错误消息,所以我需要在 2 个地方订阅(Auth 用于管理成功,Login 用于管理错误)。

我使用share 运算符实现了它:

public authentificate(credentials: CredentialsModel) : Observable<Response> {
    const headers = new Headers();
    headers.append('Content-Type', 'application/json');

    const auth$ = this.http.post(config.LOGIN_URL, JSON.stringify(credentials), {headers})
                            .map(res => res.json()).share();

    auth$.subscribe(data => this._saveJwt(data.id_token), () => {});

    return auth$;
}

在组件内部:

submitForm(e: MouseEvent) {
    e.preventDefault();
    this.auth.authentificate(this.cred).subscribe(() => {}, (err) => {
        console.log('ERROR component', err);
    });
}

它有效,但我觉得做错了.. 我只是将我们使用 angular1 和 promises 的方式进行转换,您有没有更好的方法来实现它?

【问题讨论】:

  • 您在authService 的其他任何地方使用auth$ 吗?如果没有那么你不需要订阅autheService...

标签: angular observable rxjs5 angular2-http


【解决方案1】:

只能订阅服务中的事件并返回对应的observable:

public authentificate(credentials: CredentialsModel) {
    const headers = new Headers();
    headers.append('Content-Type', 'application/json');

    var obs = this.http.post(config.LOGIN_URL, JSON.stringify(credentials), {headers})
        .map(res => res.json())
        .subscribe(
            data => this._saveJwt(data.id_token)
        );

    return obs;
}

如果发生错误,您可以使用catch 运算符捕获它:

submitForm(e: MouseEvent) {
  e.preventDefault();
  this.auth.authentificate(this.cred).catch((err) => {
    console.log('ERROR component', err);
  });
}

编辑

如果你想订阅一个 observable 两次,你需要通过调用 share 方法使其“热”。

您还可以利用 do 运算符并仅在组件中订阅:

public authentificate(credentials: CredentialsModel) {
    const headers = new Headers();
    headers.append('Content-Type', 'application/json');

    return this.http.post(config.LOGIN_URL, JSON.stringify(credentials), {headers})
        .map(res => res.json())
        .do(
            data => this._saveJwt(data.id_token)
        );
}

【讨论】:

  • 我试过了,但authentificate 不再返回observable,而是返回Subscriber,所以不可能调用catchsubscribe
  • 好的,谢谢,我做对了。我问是因为它对我来说似乎并不优雅,但也许在我的情况下,在我的服务和组件内部使用 promisethen() 更适应
  • 或许不需要订阅服务。我使用基于do 运算符的另一种方法更新了我的问题...
  • 是不错的解决方案,但我想do会在成功和错误时被调用?
【解决方案2】:

你为什么要订阅sharedService,什么时候可以使用这种方法!

@Injectable()
export class Auth {
    constructor(public http: Http) {}

    public authentificate(credentials: CredentialsModel) {
        const headers = new Headers();
        headers.append('Content-Type', 'application/json');

            return  this.http.post(config.LOGIN_URL, JSON.stringify(credentials), {headers})      //added return
            .map(res => res.json());
            //.subscribe(
            //    data => this._saveJwt(data.id_token),
            //    err => console.log(err)
            //);
    }
}

@Component({
selector: 'my-login',
    template: `<form (submit)="submitForm($event)">
                <input [(ngModel)]="cred.username" type="text" required autofocus>
                <input [(ngModel)]="cred.password" type="password" required>
                <button type="submit">Connexion</button>
            </form>`
})
export class LoginComponent {
    private cred: CredentialsModel = new CredentialsModel();

    constructor(public auth: Auth) {}

    submitForm(e: MouseEvent) {
        e.preventDefault();
        this.auth.authentificate(this.cred).subscribe(
               (data) => {this.auth._saveJwt(data.id_token)},  //changed
               (err)=>console.log(err),
               ()=>console.log("Done")
            );
    }
}


编辑
如果您想订阅sharedServicecomponent,您当然可以采用这种方法。 但我不建议这样做,在编辑部分对我来说似乎很完美之前。

我无法使用您的代码对其进行测试。但看看我的example here(tested)。点击myFriends tab,检查浏览器控制台和用户界面。浏览器控制台显示sharedService的订阅结果&UI显示component的订阅结果。


  @Injectable()
  export class Auth {
    constructor(public http: Http) {}

    public authentificate(credentials: CredentialsModel) {
        const headers = new Headers();
        headers.append('Content-Type', 'application/json');

           var sub =  this.http.post(config.LOGIN_URL, JSON.stringify(credentials), {headers})      //added return
            .map(res => res.json());

           sub.subscribe(
                data => this._saveJwt(data.id_token),
                err => console.log(err)
               );

           return sub;
    }
}

export class LoginComponent {
    private cred: CredentialsModel = new CredentialsModel();

    constructor(public auth: Auth) {}

    submitForm(e: MouseEvent) {
        e.preventDefault();
        this.auth.authentificate(this.cred).subscribe(
               (data) => {this.auth._saveJwt(data.id_token)},  //not necessary to call _saveJwt from here now.
               (err)=>console.log(err),
               ()=>console.log("Done")
            );
    }
}

【讨论】:

  • 谢谢我喜欢这种方式,它完美地解决了我的问题,但它并没有解决我在组件和服务中订阅 observable 的更好方法的全球性问题。
  • 终于找到了解决办法。更新答案。
  • 请记住我没有测试过你的代码,所以如果你发现你的代码有任何困难,你可以检查我测试过的代码并根据它制作你的代码....
  • 刚刚测试了你的 plunkr,效果很好,没有使用share 运算符。但我同意你的观点,我会使用你的第一个解决方案。感谢您的帮助!
  • 现在,如果这是您想要的,您应该接受它作为答案。