【问题标题】:How to prevent observable emitted value from being mutated如何防止可观察到的发射值被变异
【发布时间】:2019-09-09 21:11:51
【问题描述】:

我订阅了 observable 并将一个新元素取消移动到结果数组中,该数组会改变数据。如果有人试图改变数据而不是进行深度复制,我如何通过抛出异常来防止服务级别的数据突变?

我有一个角度服务,可以根据国家代码从 API 获取状态列表。这是一个单例服务,意味着不同的模块及其组件之间共享相同的实例。我正在使用 ShareReplay() 缓存响应。在我的组件中,我订阅 observable 并改变结果(通过将新值转移到数组中)缓存的数据被改变。现在我正在对结果进行深度复制,这很好,但是如果有人尝试使用该服务并改变原始值而不是进行深度复制,我希望我的 observable 抛出异常。我还尝试让服务返回可观察对象的深层副本,但这不起作用,猜想 lodash 不知道如何深层复制可观察对象,除非我放弃 shareReplay(),在服务级别实现我自己的 ReplaySubject 并让 next() 方法返回发射值的深层副本可能吗?

服务

@Injectable()
export class StatesService {

    private states$: Observable<State[]> = null;

    constructor(private _http: HttpService) {}

    getStatesFromCountry(countryCode3: string, freshData: boolean = false): Observable<State[]> {
        let code = Object.keys(CountryEnum)
            .filter((key: any) => isNaN(key))
            .find(key => key === countryCode3.toUpperCase());

        if(!code) return Observable.throw("Country Code was not recognized. Make sure it exists in CountryEnum.");

        let url: string = `api/states?country=${countryCode3}`;

        if (freshData) return this.getStates(url);

        return this.states$ ? this.states$ : this.states$ = this.getStates(url).shareReplay(1);
    }

    private getStates(url: string): Observable<State[]> {
        return this._http.get(url)
            .map(response => response)
            .catch(error => Observable.throw(<ApiError>JSON.parse(error)));
    }

}

组件

    private loadStates(): Subscription {
        const usaStates$ = this._statesService.getStatesFromCountry(CountryEnum[CountryEnum.USA]);
        const canStates$ = this._statesService.getStatesFromCountry(CountryEnum[CountryEnum.CAN]);

        return Observable.forkJoin(usaStates$, canStates$).subscribe(
            ([usaStates, canStates]) => {
                this.statesList = _.cloneDeep(usaStates.concat(canStates));

                //Here if I unshift without doing deep copy first, other 
                //components that are using this shared service will now 
                //receive a mutated array of states

                this.statesList.unshift(<State>{id: null, code: 'All'});
            },
            error => { ApiError.onError(error, this._notificationService, this._loaderService); }
        );
    }

【问题讨论】:

  • 或者你不能简单地改变数据。而不是this.statesList.unshift(//...,而是this.stateList = [{ id: null, code: 'All' }, ...usaStates, ...canStates}]
  • @mbojko 谢谢,这是一个很好的建议,但我不能保证使用该服务的其他开发人员也会这样做,因此需要一种抛出异常的方法
  • 在共享之前,您可以使用自定义设置器将数据映射到一个对象中。
  • @mbojko 实际上,你的回答让我这样做了.shareReplay(1).map(x =&gt; _.cloneDeep(x)); 我想我会坚持这样我在服务级别上进行深度克隆,而不必强迫其他开发人员在他们的组件中做到这一点,谢谢!

标签: javascript angular typescript rxjs observable


【解决方案1】:

您应该能够在值(某处)上使用Object.freeze

const test = [1,2,3,4];
Object.freeze(test);

test.unshift(0); // error will be thrown

无法添加属性 5,对象不可扩展

【讨论】:

  • 谢谢,我尝试返回.shareReplay(1).map(x =&gt; Object.freeze(x)),但仍然能够取消移动元素。猜想 Object.freeze() 需要在发出 ReplaySubject 值时应用,但我无法覆盖 shareReplay() 中的 next() 方法?
猜你喜欢
  • 1970-01-01
  • 2021-09-19
  • 2020-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-22
相关资源
最近更新 更多