3

I have the following inside of a Component's template:

<span>
  {{(userService.CurrentUser | async)?.FullName }}
</span>

userService.CurrentUser is an Observable that generates objects of type User. User has propreties named FirstName, LastName, FullName, etc.

This code works fine as it stands. I'm now trying to customize what is displayed when at different screen sizes. I've modified the template as follows:

<!--  At screen size sm , show the user's first name  -->
<span class="d-none d-sm-inline d-md-none">
  {{(userService.CurrentUser | async)?.FirstName }}
</span>

<!--  At screen size md or larger, display user's full name  -->
<span class="d-none d-md-inline">
  {{(userService.CurrentUser | async)?.FullName }}
</span>

Unfortunately, this doesn't work as expected. A little experimentation seems to indicate that whichever one of the two <span> elements comes first in the template's markup will have its expression evaluated, while the second one won't, leading it to have no content.

If I bind to a property within the component instead of using the async pipe to bind to an Observable, I don't have this problem.

While I could keep track of the current value of the Observable by subscribing to it within my component and maintaining my own copy of the most recent value of the Observable, this seems to be rewriting the async pipe.

I also see in angular - using async pipe on observable<Object> and bind it to local variable in html that I could get the most recent observable value stored into a variable by using an *ngIf.

Why does what I have not work as expected? I'd like to understand before going with one of the two alternatives above.

Update: Here's the code I'm using the set up the CurrentUser Observable. Based on a comment, I think this is the source of the problem. I suspect that it's the fact that I'm only keeping track of a single subscriber in the function passed to the Observable constructor. I don't think I understand the proper way to create an Observable and then notify Observers.

export class UserService {
  private currentUser : User;
  private currentUserObservable : Observable<User>;
  private currentUserObserver : Observer<User>;

  constructor()  {
    this.currentUserObservable = new Observable<User>(
      observer => {
        this.currentUserObserver = observer;
      }
    );
  }

  get CurrentUser() : Observable<User> {
    return this.currentUserObservable;
  }

  login (emailAddress : string, password : string) : void {
    this.currentUser = new User(emailAddress, "username", "First Name", "Last Name");      
    this.currentUserObserver.next(this.currentUser);
  }
}
ryanmcfall
  • 585
  • 1
  • 6
  • 20
  • Have you tried to use the `share()` operator? You can set a property in your class to `this.user$ = userService.CurrentUser.share()` and then use that property in your template (`user$ | async`). If that works I'll write up about it in an answer. – Daniel W Strimpel Mar 16 '18 at 19:20
  • Can we see your `CurrentUser` observable? – ConnorsFan Mar 16 '18 at 19:21
  • Using Single Subscription for Multiple Async Pipes: http://nitayneeman.com/posts/using-single-subscription-for-multiple-async-pipes-in-angular/ – d4rty Mar 16 '18 at 19:43
  • Possible duplicate of [angular - using async pipe on observable and bind it to local variable in html](https://stackoverflow.com/questions/43325791/angular-using-async-pipe-on-observableobject-and-bind-it-to-local-variable-i) – Estus Flask Mar 16 '18 at 21:28
  • @ConnorsFan I've updated it to include the code which provides the Observable, and I think that implementation is likely the problem. As I stated in the edit, I think the problem lies in my understanding of Observables. – ryanmcfall Mar 19 '18 at 14:06
  • @DanielWStrimpel I don't think this worked; I had to do `userService.currentUser.pipe(share())` to get it to compile, but it didn't change anything. However, I think something else is the source of the problem - see my updated question above. – ryanmcfall Mar 19 '18 at 14:17

1 Answers1

3

It should work as it is. However there are several inefficiencies as the documentation points out: Storing conditional result in a variable (Show a set of properties from the same object)

You could try:

<ng-container *ngIf="userService.CurrentUser | async as user">

  <!--  At screen size sm , show the user's first name  -->
  <span class="d-none d-sm-inline d-md-none">
    {{ user.FirstName }}
  </span>

  <!--  At screen size md or larger, display user's full name  -->
  <span class="d-none d-md-inline">
    {{ user.FullName }}
  </span>

</ng-container>

There is only one suscription and it is much cleaner.

  • Thanks, but I understand the inefficiencies and saw the approach of using the ng-if to bind to a local variable already. I was hoping to understand why the approach I specified didn't work correctly. I think my Observable implementation is the problem; see some of my responses to the comments above. – ryanmcfall Mar 19 '18 at 13:53