【问题标题】:rxjs observable angular 2 on localstorage changerxjs observable angular 2 on localstorage change
【发布时间】:2017-03-16 13:51:33
【问题描述】:

我正在尝试创建一个在 localStorage 变量发生更改时返回值的可观察对象。我的订阅者在更改 localStorage(或内存变量)时没有获得新值。

navbar.component.js

    import { Component, OnInit } from '@angular/core';
    import { UserService } from '../services/user.service';

    /**
     * This class represents the navigation bar component.
     */
    @Component({
      moduleId: module.id,
      selector: 'sd-navbar',
      templateUrl: 'navbar.component.html',
      styleUrls: ['navbar.component.css'],
      providers: [UserService]
    })

    export class NavbarComponent implements OnInit {
      loggedIn: boolean;
      constructor(private us: UserService) { }

      ngOnInit() {
        this.us.isLoggedIn().subscribe(loggedIn => {
          this.loggedIn = loggedIn;
        });
      }
    }

auth.component.ts

    import { Component, OnInit } from '@angular/core';
    import { ActivatedRoute } from '@angular/router';
    import { UserService } from '../shared/services/user.service';

    /**
     * This class represents the lazy loaded AuthComponent.
     */
    @Component({
      moduleId: module.id,
      selector: 'sd-auth',
      templateUrl: 'auth.component.html',
      styleUrls: ['auth.component.css'],
      providers: [UserService]
    })
    export class AuthComponent implements OnInit {
      authParams = {
        provider: '',
        params: {}
      };

      constructor(private route: ActivatedRoute, private us: UserService) { }
      ngOnInit() {
        this.route.params.forEach((param) => {
          this.authParams.provider = param.authprovider;
        });

        this.route.queryParams.forEach((queryParams) => {
          this.authParams.params = queryParams;
        });

        this.us.logIn("google", JSON.stringify(this.authParams));

        console.log(JSON.parse(localStorage.getItem('authParams')));

      }
    }

user.service.ts

    // user.service.ts
    import { Injectable } from '@angular/core';
    import { Observable } from 'rxjs/Observable';
    import { Subscriber } from 'rxjs/Subscriber';

    @Injectable()
    export class UserService {
      private loggedIn = false;
      private logger = new Observable<boolean>((observer: Subscriber<boolean>) => {
        observer.next(this.loggedIn);
      });
      constructor() {
        if (localStorage.getItem('authParams')) {
          this.loggedIn = !!JSON.parse(localStorage.getItem('authParams')).params.id_token;
        } else {
          this.loggedIn = false;
        }
      }

      logIn(provider: string, providerResponse: string) {
        localStorage.setItem('authParams', providerResponse);
        this.loggedIn = true;
      }

      isLoggedIn(): Observable<boolean> {
        return this.logger;
      }

      logOut() {
        localStorage.removeItem('authParams');
        this.loggedIn = false;
      }
    }

流程看起来像

Step 1- Navbar 订阅 UserService(获取默认值 loggedIn=false) 第 2 步 - AuthComponent 更新 UserService(设置 loggedIn = true)

我在导航栏中的订阅没有更新。我在这里想念什么。我是否需要在 UserService 的 logIn 方法中添加一些东西,比如事件发射器?

【问题讨论】:

  • 这里是否使用localstorage都没有关系。订阅者如何知道新价值是可用的? logIn 不会将新值推送到可观察值。此处适合使用主题或事件发射器。

标签: angular local-storage rxjs


【解决方案1】:

你想要的是一个主题。查看文档here

举个简单的例子,如下所示:

export class UserService {
  ...
  private logger = new Subject<boolean>();
  ...

  isLoggedIn(): Observable<boolean> {
    return this.logger.asObservable();
  }

  logIn(provider: string, providerResponse: string) {
    localStorage.setItem('authParams', providerResponse);
    this.loggedIn = true;
    this.logger.next(this.loggedIn);
  }

  logOut() {
    localStorage.removeItem('authParams');
    this.loggedIn = false;
    this.logger.next(this.loggedIn);
  }
...

【讨论】:

  • 你好,小提琴我完全同意代码库,但我有一个问题如下:1)如果用户手动从本地存储中删除条目怎么办,2)如果用户清除浏览器历史怎么办在这种情况下可以可观察的或侦听器的作品?如果是那么如何,请解释一下?
  • @mangesh localStorage 确实为您可以订阅的更改提供了一个事件 API,但正如您所说,它们可以手动更改,或者用户可以在服务器上注销。您可以采取的一种方法是订阅 http 401s,并相应地更新登录值/显示登录表单
  • @mangeshbhuskute 我添加了一个替代答案,可能更像您的想法
【解决方案2】:

更准确地说,使用来自的 BehaviourSubject import { BehaviorSubject } from 'rxjs/BehaviorSubject';

例如:

    @Injectable()
    export class UserService {

      public username = new BehaviorSubject<string>('');
      public preferredLanguage = new BehaviorSubject<string>('');
      public preferredTimezone = new BehaviorSubject<string>('');

      constructor(
        private router: Router,
        private jwtTokenService: JwtTokenService
      ) {
        let token: string = localStorage.getItem('token'); // handled for page hard refresh event
        if (token != null) {
          this.decode(token);
        }
      }

      private decode(token: string) {
        let jwt: any = this.jwtTokenService.decodeToken(token);
        this.username.next(jwt['sub']);
        this.preferredLanguage.next(jwt['preferredLanguage']);
        this.preferredTimezone.next(jwt['preferredTimezone']);
      }

      public setToken(token: any) {
        localStorage.setItem('auth_token', token);
        this.decode(token);
      }

    }

并了解 BehaviorSubject 和 Observable 之间的区别: BehaviorSubject vs Observable?

【讨论】:

    【解决方案3】:

    另一种方法是观察storage 事件

    fromEvent(window, 'storage').subscribe((storageEvent) => {
      //do what you need to do with your storageEvent
    })
    

    这意味着您不需要在任何服务层中包装原生 API。

    【讨论】:

    • 这不会捕获在同一窗口中所做的值更改。为了捕捉值的变化,必须拦截存储的setItemgetItem 函数。看我的回答here
    【解决方案4】:

    我写了一个 StorageService 来支持 Observable localStorage 和 sessionStorage。它支持一项服务。

    存储服务

    import { BehaviorSubject, Observable } from 'rxjs';
    
    /**
     * Storage service
     * used for persist application data in observable key value pair
     */
    export class StorageService {
    
        private storage: Storage;
        private subjects: Map<string, BehaviorSubject<any>>;
    
        /**
         * Constructor with service injection
         * @param storage 
         */
        constructor(storage: Storage) {
            this.storage = storage;
            this.subjects = new Map<string, BehaviorSubject<any>>();
        }
    
        /**
        * watch data of given key
        * @param key 
        * @param defaultValue 
        */
        watch(key: string): Observable<any> {
            if (!this.subjects.has(key)) {
                this.subjects.set(key, new BehaviorSubject<any>(null));
            }
            var item = this.storage.getItem(key);
            if (item === "undefined") {
                item = undefined;
            } else {
                item = JSON.parse(item);
            }
            this.subjects.get(key).next(item);
            return this.subjects.get(key).asObservable();
        }
    
        /**
         * get data of given key
         * @param key 
         */
        get(key: string): any {
            var item = this.storage.getItem(key);
            if (item === "undefined") {
                item = undefined;
            } else {
                item = JSON.parse(item);
            }
            return item;
        }
    
        /**
         * set value on given key
         * @param key 
         * @param value 
         */
        set(key: string, value: any) {
            this.storage.setItem(key, JSON.stringify(value));
            if (!this.subjects.has(key)) {
                this.subjects.set(key, new BehaviorSubject<any>(value));
            } else {
                this.subjects.get(key).next(value);
            }
        }
    
        /**
        * remove given key
        * @param key 
        */
        remove(key: string) {
            if (this.subjects.has(key)) {
                this.subjects.get(key).complete();
                this.subjects.delete(key);
            }
            this.storage.removeItem(key);
        }
    
        /**
         * clear all available keys
         */
        clear() {
            this.subjects.clear();
            this.storage.clear();
        }
    }
    

    本地存储服务

    import { Injectable, Inject } from '@angular/core';
    import { StorageService } from './storage.service';
    
    /**
     * Local storage service
     * used for persist application data in observable key value pair
     */
    @Injectable()
    export class LocalStorageService extends StorageService {
    
        /**
         * Constructor with service injection
         * @param window 
         */
        constructor(@Inject('WINDOW') private window: any) {
            super(window.localStorage);
        }
    }
    

    SessionStorageService

    import { Injectable, Inject } from '@angular/core';
    import { StorageService } from './storage.service';
    
    /**
     * Session storage service
     * used for persist application data in observable key value pair
     */
    @Injectable()
    export class SessionStorageService extends StorageService {
    
        /**
         * Constructor with service injection
         * @param window 
         */
        constructor(@Inject('WINDOW') private window: any) {
            super(window.sessionStorage);
        }
    }
    

    这是您可以使用该服务的方式:

    import { LocalStorageService } from './local-storage.service';
    
    export class TestClass implements OnInit, OnDestroy {
    
        constructor(
            private localStorage: LocalStorageService,
        ) { }
    
        ngOnInit() {
            // get current value
            this.localStorage.get('foo');
    
            // set new value
            this.localStorage.set('foo', 'bar');
    
            // watch value changes
            this.localStorage.watch('foo').pipe(takeUntil(this.unsubscribe)).subscribe(foo => console.log('foo changed', foo));
        }
    
        ngOnDestroy() {
            this.unsubscribe.next();
            this.unsubscribe.complete();
        }
    }
    

    (我是 TypeScript 的新手,有几个月的经验。欢迎任何改进或建议 :-))

    【讨论】:

    • 我对您的代码进行了一些修改,并将 BehaviorSubject 替换为 ReplaySubject。 :) stackblitz.com/edit/…
    猜你喜欢
    • 2016-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-24
    • 2018-03-09
    • 2019-06-09
    • 1970-01-01
    • 2018-10-15
    相关资源
    最近更新 更多