7.11: Updating Profile Photo in NavBar

BehaviorSubject

In this module, we'll learn how to pass data from any component to any other component in Angular.

We can communicate and pass data around between components with our services.

If we add a property called MainPhotoUrl to our AuthService - any component we want can subscribe to this property and receive updates when changes are made.

We subscribe to observables - and, MainPhotoUrl will be an observable - but, a different kind of observable: BehaviorSubject.

A BehaviorSubject is a type of subject.

A subject is also an observer. It can be subscribed to and other subscribers can receive updated data - but, it can alo receive data.

A BehaviorSubject

  1. nees an initial value (it must always return a value when subscribed to)

  2. always returns the last value of the subject when subscribed to

  3. you can use the getValue() method in non-observable code

These Reactive Programming ideas and techniques are really difficult to wrap your head around and they're one of the most tricky parts about Angular. They're also really difficult to explain in text! As is often the case, practice will make it more clear. If you're someone who learns best by reading or you'd like a more thorough introduction to Reactive Programming check out this Readme from André Staltz.

Adding BehaviorSubject to the AuthService

In auth.service.ts, import BehaviorSubject from rxjs and add a new property:

import { BehaviorSubject } from 'rxjs/BehaviorSubject';

// ...

private photoUrl = new BehaviorSubject<string>('../../assets/user.png');

As stated before, we have to supply an initial photo for the BehaviorSubject. So, we'll use a default placeholder profile picture (think a Facebook profile when there is no photo).

Search on the web for a generic user placeholder image. I'd suggest one as generic and androgynous as possible. We won't be checking their gender. Although, having two default profile images and checking their gender might be an interesting feature to practice your skills.

Save it to your assets folder - and, if it is called something different, make sure to update your code to match.

Next, we'll add another property to auth.service.ts:

currentPhotoUrl = this.photoUrl.asObservable();

Add a method called changeMemberPhoto():

changeMemberPhoto(photoUrl: string) {
    this.photoUrl.next(photoUrl);
}

And, we'll need to update the login() method so that when a user logs in, we set the currentPhotoUrl to the photoUrl contained in the user object.

login(model: any) {
    return this._http.post<AuthUser>(this.baseUrl + 'auth/login', model, { headers: new HttpHeaders()
        .set('Content-Type', 'application/json') })
        .map(user => {
        if (user) {
            localStorage.setItem('token', user.tokenString);
            localStorage.setItem('user', JSON.stringify(user.user));
            this.decodedToken = this._jwtHelperService.decodeToken(user.tokenString);
            this.currentUser = user.user;
            this.userToken = user.tokenString;
            this.changeMemberPhoto(this.currentUser.photoUrl);                          //  <--- Added
        }
        });
}

Subscribing in Components

Now, we need to subscribe to this in the NavComponent.

In nav.component.ts, add a property called photoUrl and update the ngOnInit() method to the following:

photoUrl: string;

//  ...

ngOnInit() {
    this.authService.currentPhotoUrl.subscribe(photoUrl => this.photoUrl = photoUrl);
}

Now, instead of using the photoUrl from the user - we'll use the photoUrl data that we're subscribing to.

<div class="profile-photo">
    <img src="{{ photoUrl }}" alt="{{ authService.currentUser.knownAs }}">
</div>

Next, we need to update this in app.component.ts as well:

if (user) {
    this._authService.currentUser = user;
    this._authService.changeMemberPhoto(user.photoUrl);
}

We also need to update the member-edit.component.ts so that the profile panel's image is also hooked up to the same data stream.

photoUrl: string;                                                                              //  <--- Added

//  ...

ngOnInit() {
    this._route.data.subscribe(data => {
      this.user = data['user'];
    });
    this._authService.currentPhotoUrl.subscribe(photoUrl => this.photoUrl = photoUrl);          //  <--- Added
}

Also, update the member-edit.component.html template:

And, finally - we need to update the photo-editor.component.ts

We'll call the changeMemberPhoto() method and set the currentUser.photoUrl in our AuthService

We'll also reset the token in local storage.

 setMainPhoto(photo: Photo) {
    this._userService.setMainPhoto(this._authService.decodedToken.nameid, photo.id).subscribe(() => {
      this.currentMain = _.findWhere(this.photos, { isMain: true });
      this.currentMain.isMain = false;
      photo.isMain = true;
      this._authService.changeMemberPhoto(photo.url);                               //  <--- Modified
      this._authService.currentUser.photoUrl = photo.url;                           //  <--- Added
      localStorage.setItem('user', JSON.stringify(this._authService.currentUser));  //  <--- Added
    }, error => {
      this._alertify.error(error);
    });
}

Wrap-up

Test it our in your application. When you change the main photo in the profile editing section of the site, it should update in the profile panel and in the navbar.

We finally added our profile photo in the navbar feature. That was quite a bit of work. Especially for something that doesn't add all that much to our app.

But, we learned a lot and that's what we're here for!

Last updated