1

I have a component which visualize some data in an mat accordion, next to it is a google map which shows the location of each data point. On top of the component I have a dropdown, depending on the selection project data is refreshed and the accordion + map should update.

The map is its own component and data is shared via a service. I don't want to show the map when there is no data to visualize. I would like to work with [hidden] instead of ngIf because I dont need a new map instance in that case.

My current problem is that when the map is in a hidden state, then I change the drop down selection, project data gets updated and I have actual data, map is visible again, markers are shown but the map is fully zoomed out with with grey borders on the top and the bottom.

I can get it to work with ngIf and ChangeDetectorRef which probably is not best practice and I may have an error somewhere.

Template:

<div [hidden]="!projects || (projects && projects.length == 0)"  class="right-panel" style="border-radius: 16px; overflow: hidden">
  <app-map (markerClicked)="setExpandedPanel($event)"></app-map>
</div>

Map Component Template:

<div style="width: 100%; height: 100%; min-height: 750px">
  <input id="pac-input" #placesContainer style="margin-top: 10px" type="text"/>
  <google-map (mapInitialized)="onMapReady($event)" mapId="myMap" [zoom]="zoom" [center]="center"
              width="100%" height="100%">

    @for (marker of projectMarkers; track marker.position) {
      <map-advanced-marker (mapClick)="onMarkerClick(marker.id)"
                           [position]="marker.position"
                           [content]="marker.icon"
                           [zIndex]="marker.zIndex"
      />
    }
  </google-map>
</div>

Map Component:

zoom: number = 7;
  center: google.maps.LatLngLiteral = ..

  projects: Project[] = [];

  @Output() markerClicked = new EventEmitter<string | null>();

  private mapReady$ = new BehaviorSubject<boolean>(false);

  projectMarkers: CustomMapMarker[] = [];
  currentExpandedProjectId: string | null = null;

  markerSvgString = '<svg width="62" height="78" ..</g></svg>';

  @ViewChild('placesContainer') placesElement!: ElementRef;
  private map!: google.maps.Map;

  constructor(
    private projectStateService: ProjectStateService,
    private mapService: MapBaseService,
    private ref: ChangeDetectorRef
  ) {}

  ngAfterViewInit(): void {
    combineLatest([
      this.projectStateService.projects$,
      this.mapReady$
    ]).subscribe(([ projects, mapReady]) => {
      if(mapReady && projects && projects.length > 0){
        this.projects = projects!;
        this.setProjectMarkers();
        this.setBounds();
        this.ref.detectChanges()
      }
    });
  }

  async onMapReady(map: google.maps.Map): Promise<void> {
    await this.mapService.loadMapLibraries()
    this.map = map;
    this.map.setOptions({
      gestureHandling: 'cooperative',
      fullscreenControl: false,
      mapTypeControl: true,
      mapTypeControlOptions: {
        style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
        position: google.maps.ControlPosition.TOP_RIGHT
      }
    });
    await this.mapService.initPlacesSearch(map, this.placesElement);
    this.mapReady$.next(true);
  }

Just switching between drop down selections with different projects works perfectly, its all about the switch of no projects > hidden map to projects > visible map.

1 Answer 1

0

Wrap the two methods in a setTimeout so that it renders after the element is shown.

  ngAfterViewInit(): void {
    combineLatest([
      this.projectStateService.projects$,
      this.mapReady$
    ]).subscribe(([ projects, mapReady]) => {
      if(mapReady && projects && projects.length > 0){
        this.projects = projects!;
        setTimeout(() => {
          this.setProjectMarkers();
          this.setBounds();
        });
      }
    });
  }
Sign up to request clarification or add additional context in comments.

4 Comments

Mh, it makes it better when using hidden, but not really consistent, sometimes the map renders not complete/correctly when switching via the dropdown. I think I need to keep my onMapReady function to synchronize when the map and data is loaded. I really, would like to understand why that happens. Shouldn't my obs logic trigger some kind of change anyway after setBounds()? I am not sure why it only works with ref.detectChanges()
@Christian Do you have changeDetection set as OnPush?
No I don't, but that would explain it.
@Christian updated my answer, I think the element shoud be shown before the methods are called

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.