Angular 6 - Behaviour Subject: get data to stay after refresh

Ofir Sasson picture Ofir Sasson · Jul 31, 2018 · Viewed 10.1k times · Source

I'm trying to pass data between course-detail component and course-play component. I used shared service and BehaviorSubject. The data is passing correctly to course-detail through the service, and when go to course-play html page through course-detail html page it works just fine, but when I refresh the page, it's using the default id I gave courseId in the service. I need to find a way for the id to stay in the service all the time after getting it from course-play and just keep that update when I get another course id. Here's my code:

course.ts

course.service

import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { BehaviorSubject } from 'rxjs';

import { Observable, throwError } from 'rxjs';
import { catchError, groupBy } from 'rxjs/operators';

import { ICourse } from './course';

// Inject Data from Rails app to Angular app
@Injectable()
export class CourseService{

  // JSON url to get data from
  private url = 'http://localhost:3000/courses';
  private courseUrl = 'http://localhost:3000/courses.json';

  // Subscribe data
  private courseId = new BehaviorSubject(1);
  public courseId$ = this.courseId.asObservable();

  // here we set/change value of the observable
  setId(courseId) {
    this.courseId.next(courseId)
  }

  constructor(private http: HttpClient) { }

  // Handle Any Kind of Errors
  private handleError(error: HttpErrorResponse) {

    // A client-side or network error occured. Handle it accordingly.
    if (error.error instanceof ErrorEvent) {
      console.error('An error occured:', error.error.message);
    }

    // The backend returned an unsuccessful response code.
    // The response body may contain clues as to what went wrong.
    else {
      console.error(
        'Backend returned code ${error.status}, ' +
        'body was ${error.error}');
    }

    // return an Observable with a user-facing error error message
    return throwError(
      'Something bad happend; please try again later.');
  }

  // Get All Courses from Rails API App
  getCourses(): Observable<ICourse[]> {
  const coursesUrl = `${this.url}` + '.json';

  return this.http.get<ICourse[]>(coursesUrl)
      .pipe(catchError(this.handleError));
  }

  // Get Single Course by id. will 404 if id not found
  getCourse(id: number): Observable<ICourse> {
    const detailUrl = `${this.url}/${id}` + '.json';
    return this.http.get<ICourse>(detailUrl)
        .pipe(catchError(this.handleError));
  }


}

course-detail.component

import { Component, OnInit, Pipe, PipeTransform } from '@angular/core';
import { ActivatedRoute, Router, Routes } from '@angular/router';

import { ICourse } from '../course';
import { CourseService } from '../course.service';


// Course-detail decorator
@Component({
  selector: 'lg-course-detail',
  templateUrl: './course-detail.component.html',
  styleUrls: ['./course-detail.component.sass']
})

export class CourseDetailComponent implements OnInit {
  course: ICourse;
  errorMessage: string;

  constructor(private courseService: CourseService,
        private route: ActivatedRoute,
        private router: Router) {
  }

  // On start of the life cycle
  ngOnInit() {
    // get the current course id to use it on the html file
    const id = +this.route.snapshot.paramMap.get('id');

    // set curretn course Id in the service to use it later
    this.courseService.setId(id);
    this.getCourse(id);
    }

    // Get course detail by id
    getCourse(id: number) {
        this.courseService.getCourse(id).subscribe(
          course => this.course = course,
          error  => this.errorMessage = <any>error;
         )
      }

   // When we click the back button in browser
   onBack(): void {
     this.router.navigate(['/courses']);
   }

}

course-play-component

import { Component, OnInit, Input} from '@angular/core';
import { ActivatedRoute, Router, Routes, NavigationEnd } from '@angular/router';
import { MatSidenavModule } from '@angular/material/sidenav';

import { ICourse } from '../course';
import { CourseService } from '../course.service';


// Couse-play decorator
@Component({
  selector: 'lg-course-play-course-play',
  templateUrl: './course-play.component.html',
  styleUrls: ['./course-play.component.sass']
})

export class CoursePlayComponent implements OnInit {
  errorMessage: string;
  course: ICourse;
  courseId: number;


  constructor(private courseService: CourseService,
      private route: ActivatedRoute,
      private router: Router) {
        courseService.courseId$.subscribe( courseId => {
          this.courseId = courseId;
        })

  }

    // On start of the life cycle
    ngOnInit() {
        // get the current segment id to use it on the html file
        const segmentId = +this.route.snapshot.paramMap.get('id');
        this.getCourse(this.courseId);
      }

      // Get course detail by id
      getCourse(id: number) {
          console.log(id);
          this.courseService.getCourse(id).subscribe(
            course => this.course = course,
            error  => this.errorMessage = <any>error;
           )
        }

    // When we click the back button in browser
     onBack(): void {
       this.router.navigate(['/courses/:id']);
     }

}

Answer

jack picture jack · Jul 31, 2018

It is default behavior of BehaviorSubject. When ever you refresh page you will get default id from service. If you want to get your updated id, then store it in local storage/cookie and update your behavior subject from that value.

To store value in localStorage when you update your id:

localStorage.setItem(key, value);

To get item from localStorage:

localStorage.getItem(key);

And when page refresh, you need to read value from localStorage and emit value to subscriber using next();. Example:

this.your_service.your_behavior_subject.next(value_from_localstorage);

you need to emit in global component [app.component.ts]. So it is available through out your application