When clicking on OpenLayers Map in Angular, the value may not update as expected or may be set back

My initial project incorporates OpenMaps / Openlayers. The specific component I am referring to appears as follows:

import {AfterViewInit, ChangeDetectionStrategy, Component} from '@angular/core';
    import {Map, MapBrowserEvent, View} from 'ol';
    import {OSM} from "ol/source";
    import {fromLonLat, toLonLat} from "ol/proj";
    import {defaults, MousePosition} from "ol/control";
    
    import {createStringXY, toStringHDMS} from "ol/coordinate";
    import {Tile} from "ol/layer";
    
    @Component({
      selector: 'app-map',
      templateUrl: './map.component.html',
      styleUrls: ['./map.component.css'],
      changeDetection: ChangeDetectionStrategy.Default
    })
    export class MapComponent implements AfterViewInit {
    
      latitude: number = 52.520008;
      longitude: number = 13.404954;
      private map?: Map;
      view?: View;
    
      _lastClicked: string;
    
      constructor() {
        this._lastClicked = 'abc'; // This value remains unchanged in the frontend
      }
    
      ngAfterViewInit() {
        let osmTile = new Tile({
          source: new OSM()
        });
    
        this.view = new View({
          center: fromLonLat([this.longitude, this.latitude]),
          zoom: 8
        });
    
        let mousePositionControl = new MousePosition({
          coordinateFormat: createStringXY(4),
          projection: 'EPSG:4326',
          undefinedHTML: ' ',
          className: 'custom-mouse-position',
          target: document.getElementById('mouse-position') || undefined
        });
        let controls = defaults({
          attributionOptions: {
            collapsible: false
          }
        }).extend([mousePositionControl]);
    
        this.map = new Map({
          target: 'map',
          controls: controls,
          layers: [
            osmTile
          ],
          view: this.view
        });
        this.map.on('click', this.onClick);
      }
    
      onClick(evt: MapBrowserEvent) {
        var coordinate = evt.coordinate;
        this.lastClicked = toStringHDMS(toLonLat(coordinate));
        console.log(this.lastClicked + ' was clicked'); // This correctly prints the values
      }
    
      get lastClicked(): string {
        return this._lastClicked;
      }
    
      set lastClicked(value: string) {
        this._lastClicked = value;
      }
      
      checkCoordinate() {
        console.log(this.lastClicked + ' represents the current position'); // This wrongly prints a value
      }
    }
    

The HTML template for that component is structured like so:

<h1>There is a map</h1>
    {{lastClicked}}
    <p>
        <button (click)="checkCoordinate()"> Check Coordinate </button>
    </p>
    <div id="mouse-position"></div>
    <div id="map" class="map"></div>
    </div>
    

The end result along with the console output is displayed here:

https://i.sstatic.net/ulXT8.png

Upon the initial click on the map, the correct output is shown in the console, while the top value still reads 'abc': https://i.sstatic.net/zkuLc.png (Debugging verifies that the coordinates are indeed stored within the property lastChecked.)

Subsequently, after clicking the 'Check Coordinate' button, the original value ('abc') is oddly printed rather than the updated one.

https://i.sstatic.net/nKpKe.png (Debugging also confirms that the old value persists in the lastChecked property.)

I have a strong sense that there is an element missing from the Angular tutorials and resources I utilized during my learning process. Any assistance in clarifying this issue would be greatly appreciated.

EDIT:

Further investigation led me to identify the following root cause/problem: https://i.sstatic.net/nNl0X.png It appears that "this" points to the ol/Map instance, not my primary component (which I named MapComponent).

Although this discovery does not provide a solution, it brings me closer to resolving the issue.

Answer №1

After some trial and error, I have managed to find a solution that may be helpful for someone.

It's worth noting that the code contains unnecessary parts and can be a bit messy, but it gets the job done.

The issue stemmed from binding a function to the

this.map.on('click', this.onClick);
, which caused the context of this within the onClick method to refer to the ol/Map, rather than the Angular component. This led to confusion when trying to access properties within the method.

I also made some adjustments to better organize the modules for clarity.

Now onto the implementation:

Firstly, here is the updated MapComponent along with its HTML:

import {ChangeDetectionStrategy, Component} from '@angular/core';
import {Map} from 'ol';

@Component({
  selector: 'app-map',
  templateUrl: './map.component.html',
  styleUrls: ['./map.component.css'],
  changeDetection: ChangeDetectionStrategy.Default
})
export class MapComponent   {

  constructor() {
  }

  onMapReady($event: Map) {
    console.log("Map Ready")
  }

  meld(evt: MouseEvent) {
    console.log("Meld!")
  }
}
<div class="content">
  <h1>There is a map</h1>
  <app-ol-map class="map"
              [center]="[13.404954, 52.520008]"
              [zoom]="8.5"
              (click)="meld($event)"
  ></app-ol-map>
</div>

(Some CSS styling is also included, but not crucial at this point.)

You'll notice the (click) method in the code, even though it doesn't do much. It serves to trigger click events and update related changes in the component.

In addition, I borrowed another component from here, renamed it OlMapComponent, and customized it:

import {AfterViewInit, Component, EventEmitter, Input, NgZone, Output} from '@angular/core';
import {Map, MapBrowserEvent, View} from 'ol';
import {Coordinate, createStringXY, toStringHDMS} from 'ol/coordinate';
import {defaults, MousePosition} from 'ol/control';
import Projection from 'ol/proj/Projection';
import {fromLonLat, get as GetProjection, toLonLat} from 'ol/proj'
import {Extent} from 'ol/extent';
import OSM from 'ol/source/OSM';
import {Tile} from "ol/layer";
import VectorLayer from "ol/layer/Vector";
import {Vector} from "ol/source";
import {KML} from "ol/format";

//Code continued...
//More code snippet here...

By correctly handling this in the onClick method as an instance of ol/Map, I can now retrieve the value of lastClicked directly from the ol/Map itself. To display this value in the frontend, I've created a getter that simply forwards the value.

Although my IDE shows errors, using `@ts-ignore` allows me to proceed with the setup.

And guess what? It works perfectly fine.

Initially, everything looks normal: https://i.sstatic.net/5B5vd.jpg

But the real magic happens when I click on the map: https://i.sstatic.net/igXrs.jpg

As shown in the images, the value is updated correctly due to:

  1. Delegating via the
    get lastClicked(): string { // @ts-ignore return this.map?.lastClicked || 'undefined'; }
  2. Manipulating the DOM with
     const lcc = document.getElementById('lastClicked') || undefined; if (lcc) lcc.innerHTML = 'Located click is: <code>' + this.lastClicked + '</code>';

Furthermore, the click event triggers the click method of MapComponent, leading to updates in the underlying OlMapComponent.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Transforming a "singular or multiple" array into an array of arrays using TypeScript

What is causing the compilation error in the following code snippet, and how can it be resolved: function f(x: string[] | string[][]): string[][] { return Array.isArray(x[0]) ? x : [x]; } Upon inspection, it appears that the return value will constantly ...

The issue with Angular's Array.push method arises when only the last object in the array is being pushed after a mat-checkbox

I am currently working with two components called item-list and item. The item-list component is responsible for displaying a list of items with checkboxes. I have been facing an issue where the Array that stores the checked items only retains the last ite ...

The directive isn't functioning within a submodule

I am facing an issue with getting a directive to function properly in a lazy loaded module. Even after carefully reviewing the documentation and adding the directive to the declarations array of my main module, the directive works fine in the main module b ...

Function being called from TypeScript in HTML is not functioning as expected

Hello, I am currently using Django and looking to implement TypeScript for a specific function within my application. Below is the content of my TypeScript file: testselector.ts: getSelectionText() { var text = ""; if (window.getSelection) { ...

Can observable data be saved into another observable data storage?

I attempted to retrieve the array of first names from the observable below, which receives its response from an API (hardcoded here). I aim to store the original data in a separate observable. Thus, my implementation is as follows: this.initialData = Obse ...

What is the best way to implement a useState within a context for testing with jest?

function CustomComponent() { const {val, change} = useContext(ProviderContext) return ( <TextField> onChange={({target}) => { change(target) }} value={val} </TextField> ); } describe('test', ( ...

Trouble with Angular: mat-sidenav not responding to autosize or mode changes when resized

Currently, I am working on integrating the mat-sidenav into a project. Most of the functionality is working well, but there is one particular issue that arises. When running the app locally in a browser and resizing the window tab multiple times, it opens ...

Issues have been reported with Angular 10's router and anchorScrolling feature when used within a div that is positioned absolutely and has overflow set

I feel like I may be doing something incorrectly, but I can't quite pinpoint the issue. Any help or pointers would be greatly appreciated. My current setup involves Angular 10 and I have activated anchorScrolling in the app-routing.module file. const ...

Display alternative navigation paths to the user in Angular that differ from the original routes

I am currently developing a full stack web application using Angular, Node (Express), and mySQL. I am looking to display a different route to the user than the actual one. Is there a way to achieve this? For instance, let's say this is my dashboard pa ...

Modifying the <TypescriptModuleKind> setting for typescript transpilation in project.csproj is not supported in Visual Studio 2017

I recently encountered an issue with changing the module kind used by the transpiler in Visual Studio. Despite updating the <TypescriptModuleKind> in the project's project.csproj file from commonjs to AMD, the transpiler still defaults to using ...

The TypeScript declaration for `gapi.client.storage` is being overlooked

When I call gapi.client.storage.buckets.list(), TypeScript gives me an error saying "Property 'storage' does not exist on type 'typeof client'." This issue is occurring within a Vue.js application where I am utilizing the GAPI library. ...

The push method in Typescript does not have the capability to capture a tuple type within an array

const testArray:[number, string] = [10, 'test', 's']; It's not functioning correctly. const testArray:[number, string] = [10, 'test']; // Changes have been made. testArray.push('test'); Now it's working a ...

Change (EU Time) date format of dd/mm/yyyy hh:mm:ss to a timestamp

Is there a way to convert time into a timestamp? I attempted to use .getTime(), but it seems to be switching the day and month. const date = new Date('01-02-2003 01:02:03'); console.log(date.getTime()); It appears to be converting to the US Tim ...

Enhancing Angular Models with Property Decorators

In my Angular 8 application, I faced an issue where the backend model and frontend model are not identical. For example, the backend model stores dates in SQL format while I needed them in a JavaScript friendly format on the frontend. To tackle this probl ...

Showing the Nested Object following retrieval from the API

Greetings everyone, I am facing an issue with displaying basic data from an API service that contains a NESTED json object. The challenge I am encountering is that most tutorials only focus on displaying data from array objects, not nested ones. The str ...

Encountering TS2339 error while attempting to append a child FormGroup within Angular framework

I'm currently working with Angular8 and facing an issue while attempting to include a child FormGroup to a form using the addControl method: this.testForm = new FormGroup({ id: new FormControl(0), people: new FormGroup({ } ...

Angular: Unable to retrieve defined data when loading a component

There is a nagging question in my mind that I hesitate to ask because deep down, I know the answer is probably staring me in the face. After struggling with code for two days straight, I am on the brink of pulling my hair out. Although I am relatively new ...

How to Integrate FullCalendar with Your Angular Application

Having some confusion with installing Fullcalendar in my Angular 8 project. I followed the instructions on the Fullcalendar website and installed the package under @fullcalendar using npm install --save @fullcalendar/angular, but then came across examples ...

How can I make angular material data table cells expand to the full width of content that is set to nowrap?

This example demonstrates how the mat-cells are styled with a specific width: .mat-cell { white-space: nowrap; min-width: 150rem; } If the width is not specified, the table will cut off the text due to the white-space property being set to nowrap. Is ...

Conditional application of Angular animations is possible

After implementing the fadein effect from Angular-Animations in my ASP.NET based Angular project, I encountered an issue where only the first row is faded-in while the other rows are not displayed when using *ngIf. Here is a snippet of the code: <ng-te ...