0
votes

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$;
  }
}
2
Try to share the whole service classes instead of single snippets as this makes it easier to understand and grasp the context of your methods. Further, is the console.log being executed with the desired message/element?gerstams
Additionally, please adjust _analysisBehaviourSubject to _analysisReplaySubject if you intend to show the ReplaySubject here. This will help to keep confusions lowgerstams
Thankyou for the suggestions to improve the question. I have now attached the popup service and the newly implemented store service. I have also changed the name to behavior subject.BEvo
Can you recreate this in a stackblitz - preferably with an abstracted version of your project rather than your actual project.Kurt Hamilton

2 Answers

1
votes

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.

0
votes

I think what happens is that you create a new observable everytime getElement$() is being called with the asObservable() function. I feel the docs are not 100% clear on this.

What I suggest is to create the observable on class level as well instead of on method level.

So in your Popup service you can do this:

_analysisBehaviourSubject: ReplaySubject<any> = new ReplaySubject();
_analysis$: Observable<any> = this._analysisBehaviourSubject.asObservable();

Alternatively, you could subscribe to the subject directly as it extends Observable.