8

For a JWT authentification, I make a post request to get the token using the new Http module working with Observables now.

I have a simple Login component showing the form:

@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);
    }
}

I have a Auth service making the request:

@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)
            );
    }
}

Works well but now I want to display error messages inside my component so I need to subscribe in 2 places (Auth for managing success and Login for managing error).

I achieved it using share operator:

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$;
}

And inside the component:

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

It works but I feel doing it wrong.. I just transpose the way we did it with angular1 and promises, do you see better way to achieve it?

bertrandg
  • 3,147
  • 2
  • 27
  • 35
  • 1
    Do you use `auth$` anywhere else in `authService`? if no then you don't need to subscribe to in `autheService`... – micronyks Mar 09 '16 at 10:12

2 Answers2

9

Why would you subscribe to in sharedService, when this approach can be used !

@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")
            );
    }
}


Edit
still if you want to subscribe in sharedService and component you can surely go with this approach. But I'd not recommend this rather before edited section seems perfect to me.

I can't test it with your code. but look at my example here(tested). click on myFriends tab,check browser console and UI. browser console shows subscription result of sharedService & UI shows subscription result of 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")
            );
    }
}
micronyks
  • 54,797
  • 15
  • 112
  • 146
  • Thanks I like this way, it solves perfectly my case but it doesn't answer my global problem of the better way to subscribe an observable inside a component and a service. – bertrandg Mar 09 '16 at 10:36
  • Finally found a solution. updating an answer. – micronyks Mar 09 '16 at 11:02
  • Please keep in mind that i havent tested your code so if you find any difficulty with your code, you can check my tested code and make your code according to it.... – micronyks Mar 09 '16 at 11:23
  • Just tested your plunkr, works good and without using `share` operator.. But I agree with you, I will use your first solution. Thx for your help! – bertrandg Mar 09 '16 at 11:29
  • Now if this is what you wanted, you should accept it as an answer. – micronyks Mar 09 '16 at 11:32
2

You can only subscribe to event in the service and return the corresponding 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;
}

If errors occur, you can catch it using the catch operator:

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

Edit

If you want to subscribe twice to an observable, you need to make it "hot" by calling the share method.

You could also leverage the do operator and only subscribe in the component:

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)
        );
}
Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • I tried this but `authentificate` is not returning an `observable` anymore but a `Subscriber` so impossible to call `catch` or `subscribe` on it.. – bertrandg Mar 09 '16 at 10:28
  • Ok thanks, so I did it correctly. I asked because it seemed not elegant for me but maybe in my case going back with a `promise` with a `then()` inside my service and inside my component is more adapted – bertrandg Mar 09 '16 at 10:44
  • Perhaps it's not necessary to subscribe in the service. I updated my question with another approach based on the `do` operator... – Thierry Templier Mar 09 '16 at 10:50
  • yes nice solution but I imagine `do` will be called when success and error? – bertrandg Mar 09 '16 at 10:54