1

I'm trying limit the emission of subscription:

My object Preferences can change all the time. The API request should run only first time and when x has been changed . I added distinctUntilChanged, but API request fires only on init and when I fire changeY or changeX nothing runs the request, and console.log shows same prev.x and current.x values. When I remove distinctUntilChanged, the api request fires whenever I change changeX () or changeY ()

test.page.ts:

import {Component, OnDestroy, OnInit} from '@angular/core';
import {AuthService} from '../_auth/auth.service';
import {AppService} from '../_app/app.service';
import {User, UserProfileService} from '../../api';

import {Preferences} from '../_interfaces/preferences';
import { Subject} from 'rxjs';
import {tap, takeUntil, switchMap, take, distinctUntilChanged} from 'rxjs/operators';

@Component({
    selector: 'app-test',
    templateUrl: './test.page.html',
    styleUrls: ['./test.page.scss'],
})
export class TestPage implements OnInit, OnDestroy {


    public menu: any;
    private user: User;
    public preferences: Preferences;
    private readonly destroy$: Subject<void> = new Subject();
    private i = 1;

    constructor(public appService: AppService, public authService: AuthService, public userProfile: UserProfileService) {


        this.authService.currentUser
            .pipe(
                take(1),
                tap((user: User) => this.user = user),
                switchMap(() => this.appService.currentPreferences.pipe(
                    distinctUntilChanged((prev, current) => {
                        console.log(prev.x, current.x)
                        return prev.x === current.x;
                    }),
                    tap((preferences: Preferences) => this.preferences = preferences), takeUntil(this.destroy$))),
// fire Api request
                switchMap(() => this.userProfile.getMenu(this.user.id, this.preferences.x).pipe(take(1), takeUntil(this.destroy$))),
                takeUntil(this.destroy$)
            )
            .subscribe(res => {
                this.menu = res;
                alert('Request API done');

            });


    }
        // used in html on button (click)="changeX()"
    changeX() {
        const copyPreferences: Preferences = this.preferences;
        copyPreferences.x = new Date();
        this.appService.setPreferences(copyPreferences);
    }
             // used in html on button (click)="changeY()"
    changeY() {
        const copyPreferences: Preferences = this.preferences;
        copyPreferences.y = new Date();
        this.appService.setPreferences(copyPreferences);
    }

    ngOnInit() {
    }

    ngOnDestroy(): void {
        this.destroy$.next();
        this.destroy$.complete();
    }


}

preferences.ts:

export interface Preferences {

    appToken?: string;
    shiftId?: number;
    restaurantId?: number;
    restaurants?: any;
    x?: any;
    y?: any;
}

app.service.ts

import {Injectable} from '@angular/core';
import {Storage} from '@ionic/storage';
import {Observable, ReplaySubject} from 'rxjs';


@Injectable({
    providedIn: 'root'
})
export class AppService {


    public currentPreferences: Observable<any>;
    public prefernecesState = new ReplaySubject<any>(1);

    constructor(private storage: Storage) {


        this.currentPreferences = this.prefernecesState.asObservable();


            this.setPreferencesState();

    }

    async setPreferencesState() {
        this.storage.get('preferences').then(res => {
            if (res) {
                 this.prefernecesState.next(res);
            }
        });
    }


    public setPreferences(preferences) {
        this.storage.remove('preferences');
        this.storage.set('preferences', preferences);
        this.prefernecesState.next(preferences);
    }


}
georgeawg
  • 48,608
  • 13
  • 72
  • 95
jay
  • 23
  • 3

1 Answers1

1

This

const copyPreferences: Preferences = this.preferences;

doesn't actually make a copy, it's just creating another reference to the same object. Any mutation of copyPreferences is automatically a mutation of this.preferences. Hence the test prev.x === current.x will always return true, because prev and curr are the same object.

Shallow copying should suffice:

const copyPreferences = Object.assign({}, this.preferences);
A. Gladkiy
  • 3,134
  • 5
  • 38
  • 82
mbojko
  • 13,503
  • 1
  • 16
  • 26
  • when I added new field to Parameters.menu.menuId and now trying use it on compare doesn't work ... does it problem with creating copyPreferences ? export interface Preferences { appToken?: string; shiftId?: number; restaurantId?: number; restaurants?: any; x?: any; y?: any; menu?: { menuId?: number; date?: string; product?: MenuProduct; }; } – jay Dec 17 '19 at 13:12
  • Yes. In the original version, the structure of `Preferences` was flat (no nested properties), and `Object.assign` was enough. Now `menu` is an object within an object, so it gets a little more complicated. For proposed solutions, see https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript – mbojko Dec 17 '19 at 13:15