0

I'm continuing the project of a former coworker, and I have a doubt on some code, I don't understand some parts of it.

To give you a little context, we have a UserInfo class, and it looks like:

export class UserInfo {
  constructor(
    public readonly userLoginId: string,
    public readonly hasLocalPassword: boolean,
    public readonly loginMethods: Array<LoginInfo>,
    public readonly loginMethodsAvailable: Array<LoginInfo>,
    public readonly isAuthenticated: boolean,
  ) {}
}

export class LoginInfo {
  constructor(
    public readonly loginID: number,
    public readonly displayName: string,
    public readonly logoUrl: string
  ) {
    if ([loginID, logoUrl, displayName].some(s => !s)) {
      throw Error("not all arguments passed for LoginInfo");
    }
  }
}

then in a Service class, we have 2 observables, for the properties loginMethods and loginMethodsAvailable.

 private readonly _loginMethodSub = new BehaviorSubject(Array<LoginInfo>());
  private readonly _loginMethodAvailableSub = new BehaviorSubject(Array<LoginInfo>());

 public readonly loginMethod = this._loginMethodSub.asObservable().pipe(map(loginMethod => loginMethod || []));

 public readonly loginMethodAvailable = this._loginMethodAvailableSub.asObservable().pipe(map(loginMethod => loginMethod || []));

now the first doubt is, in both these observables, we map() the LoginInfo class, but "how" does it assign the class property loginMethods or loginMethodsAvailable to the right observable? I hope it's clear what I mean, from this code, it seems that both observable are identical, they just use a different behaviourSubject, but how does the code know that one is for the class property loginMethods, and one for loginMethodsAvailable? Initially I thought that with the map(), if you use the exact name of the Class property, it will filter that observable and output only that property, but the names used in the map() are identical in both cases so that doesn't matter.

finally what might be the key part, is the constructor of this Service class:

constructor(private http: HttpClient) {
    this.User.pipe(map(uInfo => uInfo && uInfo.loginMethods)).subscribe(this._loginMethodSub);
    this.User.pipe(map(uInfo => uInfo && uInfo.loginMethodsAvailable)).subscribe(this._loginMethodAvailableSub);
  }

this.User is simply the main observable:

  private readonly _userSub = new BehaviorSubject<UserInfo>(null);
  public readonly User = this._userSub.asObservable();

So what is going on in the constructor? I never seen this syntax, subscribing and passing to it a BehaviorSubject?

And again, my main doubt is, how does the observable loginMethod and the observable loginMethodAvailable, emit the right values? In what part of this code is that defined?

The code is working, when I subscribe to loginMethod the output values are the Property loginMethods: <LoginInfo[]> of the class UserInfo, and when I subscribe to loginMethodAvailable, I get the values of the property loginMethodsAvailable:<LoginInfo[]>, I just don't understand how.

I hope I made myself clear!

AJ-
  • 1,638
  • 1
  • 24
  • 49
  • It's not clear what your question is. The code isn't helped by not declaring any types, but it really is very simple. RxJS `map` here is just ensuring that an array is always returned. If `loginInfo` is null or undefined, an empty array will be returned instead. – Kurt Hamilton May 13 '20 at 07:47
  • It works, but it's too complex. I don't know if there is a reason for him to have `_loginMethodSub ` and `_loginMethodAvailableSub` subjects. We can easily construct `loginMethod` and `loginMethodAvailable` without them. – HTN May 13 '20 at 07:48
  • my question is, in all that code, what makes the 2 observable "loginMethod" and "loginMethodAvailable" emit the right values? And the righ values are, the property "loginMethods" for the first observable, and "loginMethodsAvailable" for the second, this is not clear! To me, both observable should return the same thing – AJ- May 13 '20 at 08:05
  • it must be the code in the constructor, but then the whole map() thing is not clear to me, basically in the constructor, he uses map() to do what? Filter the values from the observable, to just return on specific property? – AJ- May 13 '20 at 08:08

1 Answers1

1

So what is going on in the constructor? I never seen this syntax, subscribing and passing to it a BehaviorSubject?

When you have multiple subscribers and expect every one of them receives the same value you have to use BehaviorSubject. More on that here

And again, my main doubt is, how does the observable loginMethod and the observable loginMethodAvailable, emit the right values? In what part of this code is that defined?

Depending on circumstances they emit different values.

Take a look at the example snippet with your code samples.

const _loginMethodSub = new rxjs.BehaviorSubject();
const _loginMethodAvailableSub = new rxjs.BehaviorSubject();
const _userSub = new rxjs.BehaviorSubject(null);
const User = _userSub.asObservable();

const loginMethod = _loginMethodSub.asObservable().pipe(
  rxjs.operators.map(loginMethod => loginMethod || []));

loginMethod.subscribe(value => {
  console.log(value);
})

const loginMethodAvailable = _loginMethodAvailableSub.asObservable().pipe(
  rxjs.operators.map(loginMethod => loginMethod || []));

loginMethodAvailable.subscribe(value => {
  console.log(value);
})

console.log('User.pipe call ctor')

User.pipe(rxjs.operators.map(uInfo => uInfo && uInfo.loginMethods)).subscribe(_loginMethodSub);

User.pipe(rxjs.operators.map(uInfo => uInfo && uInfo.loginMethodsAvailable)).subscribe(_loginMethodAvailableSub);

console.log('api call')

_userSub.next({
  loginMethods: [{
    loginID: 1,
    displayName: 'test1',
    logoUrl: 'test1'
  }, {
    loginID: 2,
    displayName: 'test2',
    logoUrl: 'test2'
  }],
  loginMethodsAvailable: [{
    loginID: 1,
    displayName: 'test1',
    logoUrl: 'test1'
  }]
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>
Józef Podlecki
  • 10,453
  • 5
  • 24
  • 50
  • great, thank you. So basically this part: map(uInfo => uInfo && uInfo.loginMethodsAvailable) , is equal to saying: "filter the values of this observable, so that it only returns the property uInfo.loginMethodsAvailable", and the "uInfo &&" is there because if the object was null, and this part wasn't here, it would get the classic error : cannot read value XXX of undefined. Is that right? – AJ- May 13 '20 at 12:58
  • Yes, `uInfo && uInfo.loginMethodsAvailable` is more like check if `uInfo` is not falsy (otherwise fast exit with falsy value) and then return `uInfo.loginMethodsAvailable` as you said – Józef Podlecki May 13 '20 at 13:02
  • great, and last doubt, the fact that we pass in the subscribe(), the 2 behaviourSubject, that is equal to doing: ........subscribe(s => _loginMethodAvailableSub.next(s)), so basically assigning the emitted value to the behaviourSubject, right? – AJ- May 13 '20 at 13:06
  • I think `subscribe` checks if we are providing callback func or subject otherwise example wouldnt work. I think internally it could wrap it in a function which you mentioned – Józef Podlecki May 13 '20 at 13:25