angularrxjsbehaviorsubjectsubject-observer

Behaviour subject only shows initial value / Replay Subject does not show updated Value


Background:

I have a service where I have an open layers based map. The map component has its own service.

A pop up is generated when an icon is clicked. On this pop up is a button which when clicked takes some data from the object displayed on the pop up and then navigates to a different component.

Im trying to publish/add this data to a behavior subject and then subscribe to it in the new component. Data is added to the behavior subject when the icon is clicked. The pop up component has its own service.

In the destination component I'm trying to subscribe to the pop up services behavior subject in the template using an async pipe.

Problem:

The observable on the behavior subject only returns the initial value. The next function on the behavior subject does not seem to be adding new data to it. It always gives the new value.

When Replay subject is used, nothing shows. Next function simply appears to have no effect at all.

Code :

Popup Service

_analysisBehaviourSubject: BehaviorSubject<any> = new BehaviorSubject();


// other code

addElement(element: any) {
    console.log('adding new element to behaviour subject', element);
    this._analysisBehaviourSubject.next(element);
}

getElement$(): Observable<any> {
    return this._analysisBehaviourSubject.asObservable();
}

Map Service

getAnalysisBehaviourSubject(): Observable<any> {
    return this.clusPopupService.getElement$();
}

Destination Service

getAnalysisBehaviourSubject(): Observable<any> {
    return this.mapService.getAnalysisBehaviourSubject();
}

Then in the destination component there is simply an async pipe.

What I have already tried:

  1. importing the behavior subject directly from the pop up service without moving it through my project structure.

  2. using behavior subject instead of the replay subject as shown in sample code.

  3. Adding all three services to the providers array in app.module.ts. The behavior subject still emits the original value and the replay subject nothing at all.

Update: Popup Service

import { DataPreparationService } from './data-preparation.service';
import { GeneralService } from '../../general.service';
import { Injectable } from '@angular/core';
import OlMap from 'ol/Map';
import OlView from 'ol/View';
import { fromLonLat } from 'ol/proj';
import Overlay from 'ol/Overlay';
import Point from 'ol/geom/Point.js';
import { SourceDataObject } from '../../Models/sourceDataObj';
import { Select } from 'ol/interaction';
import { CookieService } from 'ngx-cookie-service';
import { StoreService } from 'src/app/store.service';

@Injectable({
  providedIn: 'root'
})
export class ClusterPopupService {
  // analysis variables
  deltaEX = 1;
  deltaEy = 3;
  overlayCoords: object = {};
  sourceDataObj: SourceDataObject = { element: '', coordinates: null };
  detectedPoints: Point[] = [];

  // pop up variables
  foundIcon: SourceDataObject;
  newPoint: Point;
  generalSelect: Select;
  num = 0;
  // analysis page Behaviour Subjects
  constructor(
    private genService: GeneralService,
    private dataPrepService: DataPreparationService,
    private cookies: CookieService,
    private store: StoreService
  ) {}

  // behaviour subject m1ethods

  //cluster methods
  filterPoints(newPoint: Point) {
    const newPointCoords: number[] = newPoint.getCoordinates();

    if (this.detectedPoints.length >= 1) {
      for (let i = 0; i <= this.detectedPoints.length - 1; i++) {
        const savedPointCoords = this.detectedPoints[i].getCoordinates();
        if (
          savedPointCoords[0] === newPointCoords[0] &&
          savedPointCoords[1] === newPointCoords[1]
        ) {
          break;
        } else if (i === this.detectedPoints.length - 1) {
          this.detectedPoints.push(newPoint);
        }
      }
    } else {
      this.detectedPoints.push(newPoint);
    }
  }
  async clearFeatureCache() {
    this.foundIcon = undefined;
    this.generalSelect.getFeatures().clear();
    this.detectedPoints = null;
  }
  pushToCookies(currView: OlView) {
    this.cookies.deleteAll();
    const currCoordinates: number[] = currView.getCenter();
    const longitudeString: string = currCoordinates[0].toString();
    const latitudeString: string = currCoordinates[1].toString();
    const zoomLevelString: string = currView.getZoom().toString();
    this.cookies.set('longitude', longitudeString, 1 / 48);
    this.cookies.set('latitude', latitudeString, 1 / 48);
    this.cookies.set('zoom', zoomLevelString, 1 / 48);
    console.log(
      this.cookies.get('longitude'),
      this.cookies.get('latitude'),
      this.cookies.get('zoom')
    );
  }

  async preparePopup(
    fishCoords: SourceDataObject[],
    munitCoords: SourceDataObject[],
    wrackCoords: SourceDataObject[],
    sedimentCoords: SourceDataObject[],
    generalSelect: Select,
    view: OlView,
    globalMap: OlMap,
    localDoc?: Document
  ) {
    const extent: number[] = view.calculateExtent(globalMap.getSize());
    const container = localDoc.getElementById('popup');
    const content = localDoc.getElementById('popup-content');
    const closer = localDoc.getElementById('popup-closer');
    const analyseButton = localDoc.getElementById('analyseButton');

    const popUpOverlay: Overlay = new Overlay({
      element: container,
      autoPan: true
    });
    this.generalSelect = generalSelect;
    closer.onclick = () => {
      popUpOverlay.setPosition(undefined);
      this.foundIcon = undefined;
      generalSelect.getFeatures().clear();
      this.detectedPoints = null;

      this.newPoint = null;

      closer.blur();
      return false;
    };

    globalMap.addOverlay(popUpOverlay);
    generalSelect.on('select', event => {
      this.newPoint = event.selected[0].getGeometry() as Point;

      this.foundIcon = this.dataPrepService.binSearch(
        wrackCoords,
        this.newPoint.getCoordinates(),
        'wrack Array'
      );

      if (this.foundIcon === undefined) {
        this.foundIcon = this.dataPrepService.binSearch(
          sedimentCoords,
          this.newPoint.getCoordinates(),
          'sediment array'
        );
      }
      if (this.foundIcon === undefined) {
        this.foundIcon = this.dataPrepService.binSearch(
          fishCoords,
          this.newPoint.getCoordinates(),
          'fish array'
        );
      }

      if (this.foundIcon === undefined) {
        this.foundIcon = this.dataPrepService.binSearch(
          munitCoords,
          this.newPoint.getCoordinates(),
          'munition array'
        );
      }
      if (this.foundIcon !== undefined) {
        this.sourceDataObj = this.foundIcon;
        popUpOverlay.setPosition(fromLonLat(this.foundIcon.coordinates));
      } else {
        if (this.sourceDataObj === null) {
          this.sourceDataObj = { element: '', coordinates: null };
          this.sourceDataObj.element = 'not found';
          console.log(this.genService.backEndUri);
        }
        this.sourceDataObj.element = 'not found';
        popUpOverlay.setPosition(this.newPoint.getCoordinates());
      }
      console.log('the icon found is:', this.foundIcon);
      switch (this.sourceDataObj.element) {
        case 'sediment':
          content.innerHTML =
            '<p>You clicked on a <code>' +
            this.sourceDataObj.element +
            '</code> with coordinates <code>' +
            this.sourceDataObj.coordinates +
            '</code>, and  ID <code>' +
            this.sourceDataObj.sampleId +
            '</code>. Analyse this element?</p><p><button onclick="window.location.href=\'' +
            this.genService.localUri +
            '/analyseSediment' +
            '\'"' +
            'id="analyseButton">Analyse</button></p>';
          this.sourceDataObj = null;
          this.pushToCookies(view);
          break;
        case 'fish':
          this.store.addElement(this.sourceDataObj.miscData);

          content.innerHTML =
            '<p>You clicked on a <code>' +
            this.sourceDataObj.element +
            '</code> with coordinates <code>' +
            this.sourceDataObj.coordinates +
            '</code>,  cruise ID <code>' +
            this.sourceDataObj.miscData.cruiseId +
            '</code> and target ID <code>' +
            this.sourceDataObj.miscData.sampleId +
            '</code>. Analyse this element?</p><p><button onclick="window.location.href=\'' +
            this.genService.localUri +
            '/analyseFish' +
            '\'"' +
            ' id="analyseButton">Analyse</button></p>';

          this.sourceDataObj = null;
          this.pushToCookies(view);

          break;
        case 'Munition':
          content.innerHTML =
            '<p>You clicked on a <code>' +
            this.sourceDataObj.element +
            '</code> with coordinates <code>' +
            this.sourceDataObj.coordinates +
            '</code>,  cruise ID <code>' +
            this.sourceDataObj.miscData.cruiseId +
            '</code> and target ID <code>' +
            this.sourceDataObj.miscData.targetId +
            '</code>. Analyse this element?</p><p><button onclick="window.location.href=\'' +
            this.genService.localUri +
            '/analyseMunition' +
            '\'"' +
            'id="analyseButton">Analyse</button></p>';
          this.sourceDataObj = null;
          this.pushToCookies(view);
          break;
        case 'wrack':
          content.innerHTML =
            '<p>You clicked on a <code>' +
            this.sourceDataObj.element +
            '</code> with coordinates <code>' +
            this.sourceDataObj.coordinates +
            '</code>, and  target ID <code>' +
            this.sourceDataObj.miscData.targetId +
            '</code>. Analyse this element?</p><p><button onclick="window.location.href=\'' +
            this.genService.localUri +
            '/analyseWrack' +
            '\'"' +
            ' id="analyseButton">Analyse</button></p>';
          this.sourceDataObj = null;
          this.pushToCookies(view);
          break;
        case 'not found':
          content.innerHTML =
            '<p>You selected more than one Icon. Please Zoom in and select only one for analysis</p>';
          break;
        default:
          content.innerHTML =
            '<p>The map feature wasn"t quite selected. Please close the pop up and retry</p>';
          break;
      }
    });
  }
}

Store Service

import { Injectable } from '@angular/core';
import { BehaviorSubject, ReplaySubject } from 'rxjs';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class StoreService {
  _analysisBehaviourSubject: BehaviorSubject<any> = new BehaviorSubject();
  analysis$: Observable<any> = this._analysisBehaviourSubject.asObservable();
  constructor() {
    console.log('I AM THE STORE');
  }

  addElement(element: any) {
    console.log('adding new element to behaviour subject', element);
    this._analysisBehaviourSubject.next(element);
  }
  getElement$(): Observable<any> {
    return this.analysis$;
  }
}

Solution

  • I figured it out. The subject pattern and its shades work fine.

    In my popup service above i navigate to the component through href. My best guess is that this destroys the component tree and the instance of the service that holds the values is destroyed too.

    Although I cannot prove this, I did try implementing a replay subject to be updated as i navigated from one component to the other, and then subscribing to it when i was at the new component in my project and that worked just fine. So I'm pinning this one on the use of href.

    Need to find another way to route to my component after the press of the button in the inner html for open layers.

    Thanks to everyone who took time out to read or reply to this.