Home > Back-end >  Angular/RXJS: Changing values of an object from an observable changes its value in the BehaviorSubje
Angular/RXJS: Changing values of an object from an observable changes its value in the BehaviorSubje

Time:02-25

This is an odd one... I have a singleton (providedIn: "root") service that has a BehaviorSubject, that is private, but exposed as an observable:

private readonly _caseData$ = new BehaviorSubject<CaseData | null>(null);
Context = this.caseData$.asObservable();

context gets set by another function which calls next on the subject and then values are retrieved off of Context.

In a component I have a function that takes a value, and pipes off of Context removes some values from an array on CaseData and calls another function in the service:

unassign = (tag: Tag) => {
  this.caseContext.Context.pipe(
    switchMap((caseData) => {
      const index = caseData.tags.findIndex(tagId => tagId === tag.tagId);
      if (index > -1) {
        caseData.tags.splice(index, 1);
      }
      return of(caseData);
    }),
    concatMap((caseData: CaseData) => {
      return this.caseContext.patchCase(caseData);
    })
  )
}

This works fine but when I call patchCase and subscribe to the behavior subject in that function it has changed. The tags that were removed in the switchMap above are also removed in the CaseData in the behavior subject.

public patchCase(updated: CaseData): Observable<CaseData> {
    return of(updated).pipe(
      mergeMap((changed: CaseData) => {
        return combineLatest([this._caseData$, of(changed)]);
      }),
      map(([original, changed]: [CaseData, CaseData]) => { 
            ^- this value has the tags removed that were removed in the previous function.      
        return {
          caseId: changed.caseId,
          operations: createPatch(original, changed)
        };
      }),
      concatMap((caseOperations: { caseId: string; operations: Operation[] }) => {
        return this._caseApi.patch(caseOperations.caseId, caseOperations.operations);
      }),
      tap((updated: CaseData) => {
        this.load(updated.caseId);
      })
    );
  }

Im sure there is a better way to structure this whole thing than what I have above, and I am able to get around this by making a copy of the object in the unassign function before I remove the tags, but why does case data change when I change it in another observable stream that is piping off an observable and not even touching the behavior subject? And if this is expected, is there a better way to do this sort of thing without mutating the value in the subject aside from just making a copy?

CodePudding user response:

it happends because of this line

caseData.tags.splice(index, 1);

in this case .tags array is mutated. to get rid of it try to modify your code like this:

unassign = (tag: Tag) => {
  this.caseContext.Context.pipe(
    map((caseData) => ({
      ...caseData,
      tags: caseData.tags.filter(tagId => tagId !== tag.tagId)
    })),
    concatMap((caseData: CaseData) => {
      return this.caseContext.patchCase(caseData);
    })
  )
}

switchMap was useless here, as you are basically mapping simple value to simple value, not the observable.

  • Related