Home > database >  How to correctly save data fetched from an api in an angular app, so that we won't make a reque
How to correctly save data fetched from an api in an angular app, so that we won't make a reque

Time:09-20

I would like to save some data fetched from an API so that a user don't have to refetch the content when he revisits the page. Also it helps to persist the scroll position (don't know if it's the right way to do that though).

Currently, I am saving it in a service using a BehaviourSubject and making a request only if the service does not hold any value, in case it does I simply get that value instead of making a http call. And clear the service on user logout.

This is the code

blogs_storage.service.ts

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { Blog } from './blog.model';

@Injectable({ providedIn: 'root' })
export class BlogStorageService {
  private blogs = new BehaviorSubject<Blog[]>(null);
  private userBlogs = new BehaviorSubject<Blog[]>(null);

  clearStorage() {
    this.blogs.next(null);
    this.userBlogs.next(null);
  }

  getBlogs(): BehaviorSubject<Blog[]> {
    return this.blogs;
  }

  getUserBlogs(): BehaviorSubject<Blog[]> {
    return this.userBlogs;
  }

  storeUserBlogs(blogs: Blog[]): void {
    this.userBlogs.next(blogs);
  }

  storeBlogs(blogs: Blog[]): void {
    this.blogs.next(blogs);
  }
}

blogs.service.ts


import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { BehaviorSubject, catchError, exhaustMap, tap, throwError } from 'rxjs';
import { Blog, NewBlog } from './blog.model';
import { BlogStorageService } from './blogs-storage.service';

@Injectable({ providedIn: 'root' })
export class BlogService {
  constructor(
    private http: HttpClient,
    private blogStorage: BlogStorageService
  ) {}

  fetchAllBlogs() {
    const blogs: Blog[] = this.blogStorage.getBlogs().getValue();
    if (blogs) {
      return this.blogStorage.getBlogs().asObservable();
    } else {
      return this.http.get<{ data: Blog[]; status: number }>('blogs').pipe(
        tap((blogs) => {
          this.blogStorage.storeBlogs(blogs.data);
        }),
        exhaustMap(() => {
          return this.blogStorage.getBlogs().asObservable();
        })
      );
    }
  }

  fetchUserBlogs() {
    const blogs: Blog[] = this.blogStorage.getUserBlogs().getValue();
    if (blogs) {
      return this.blogStorage.getUserBlogs().asObservable();
    } else {
      return this.http
        .get<{ data: Blog[]; status: number }>('users/blogs')
        .pipe(
          tap((blogs) => {
            this.blogStorage.storeUserBlogs(blogs.data);
          }),
          exhaustMap(() => {
            return this.blogStorage.getUserBlogs().asObservable();
          })
        );
    }
  }

  fetchBlogBySlug(slug: string) {
    return this.http.get<{ data: Blog; status: number }>(`blogs/${slug}`);
  }

  fetchCategoriesList() {
    return this.http.get<{
      data: { _id: string; title: string; slug: string }[];
      status: number;
    }>('blogs/category-list');
  }

  postblog(data: NewBlog) {
    return this.http.post('blogs/create', data);
  }

  getSavedBlogs() {
    return this.http.get<{ data: Blog[]; status: number }>('users/savedblogs');
  }

  saveBlog(slug: string, alreadySaved: boolean) {
    return this.http.put(
      `users/savedblogs/${slug}/${alreadySaved ? 'unsave' : 'save'}`,
      {}
    );
  }
}

And on logout

  logout() {
    this.user = null;
    this.isAuth.next(false);
    this.blogStorage.storeUserBlogs(null);
    localStorage.removeItem('userData');
    this.router.navigate(['/auth/login']);
  }

Is clearing the data like this secure? Also are there any better ways to achieve this behaviour?

I am kind of new to angular so if my implementation have any kind of flaws please let me know.

CodePudding user response:

I have used a similar approach by using BehaviorSubject. But instead of having a separate BehaviorSubject try to used map of BehaviorSubject.

Ex:-

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { Blog } from './blog.model';

@Injectable({ providedIn: 'root' })
export class StorageService {
  private storeData = new Map<StorageKey, BehaviourSubject>();

  clearData(key: StorageKey) {
    if(storeData.get(key)){
      this.storeData.get(key).next(null)
    }
  }

  getData(key: StorageKey): BehaviorSubject<any> {
    return this.userBlogs.get(key);
  }

  storeData(key: StorageKey,data: any): void {
    if(!this.storeData.get(key)){
      this.storeData.add(key, new BehaviourSubject<any>())
    }
    this.userBlogs.next(blogs);
  }

}

public enum StorageKey{
   BLOGS = 'BLOGS',
   USER_BLOGS = 'USER_BLOGS'
}

Plus If you don't want to spent time on developing your own framework you can achieve similar state maintaining ability using NgRx

CodePudding user response:

Create public observables in blogs_storage.service.ts instead of returning the BehaviorSubjects. And then where the service is used, then subscribe to the observables.

Here is a good video that does a good explanation. It is long but you can watch it at 1.25 or 1.5 speed.

https://youtu.be/HnNytR32Otk

  • Related