2

I have an angular component "map". This component loads some child components in its template:

...
<app-route-dialog (addRouteEvent)="addRoute($event)"></app-route-dialog>
<app-route *ngFor="let route of routes" 
    [route]="route" 
    [map]="map>
</app-route>
...

The user can load routes from an api by opening the route dialog, filling in a form and submitting it to the server. This triggers

addRoute(route:Route):void {
    this.routes.push(route);
}

on the map component, which in turn adds another "route" component to the map view.

The template of the route component looks sth like this:

<div *ngIf="loading">
    <mat-spinner></mat-spinner>    
</div>
<table *ngIf="!loading">...some route data ... </table>

The component itself

...
@Input() route: Route;
@Input() map: L.Map;
loading = true;
...
ngOnInit(): void {
    // create and add some layers to the map this takes a couple of seconds depending on the data
    this.loading = false;
}

Expected behavior: After addRoute($event) gets called, the added route component should be rendered and show a loading spinner. After creating and adding all Elements for the map, the loading spinner should disappear and a table with data for the loaded route should be displayed.

Actual behaviour: After addRoute($event) nothing happens for a couple of seconds, then the new route component is rendered with the route data table. The loading spinner is never shown.

Edit: I am iterating over an array and creating a map marker for each point in that array. The asynch call to the api happens in the dialog component and is finished before that.

ngOnInit():void {
    const factory = this.resolver.resolveComponentFactory(LeafletTooltipComponent);
    for(let routePoint of this.route.points) {
        let marker = new L.CircleMarker([routePoint.lat, routePoint.lng],{});
        let tooltip = new L.Tooltip();
        let component = factory.create(this.injector);
        component.instance.routePoint= routePoint;
        component.changeDetectorRef.detectChanges();
        let tooltipContent = component.location.nativeElement;
        tooltip.setContent(tooltipContent);
        marker.bindTooltip(tooltip);
        this.markers.addLayer(marker); // markers = L.FeatureGroup
    }
    this.loading = false;
 }

Edit 2: When ditching

let component = factory.create(this.injector);
component.instance.routePoint= routePoint;
component.changeDetectorRef.detectChanges();
let tooltipContent = component.location.nativeElement;
tooltip.setContent(tooltipContent);
marker.bindTooltip(tooltip);

and instead doing sth like

setTimeout(() => {this.loading = false;}, 3000);

everything works as expected.

Lapskaus
  • 1,709
  • 1
  • 11
  • 22
  • The logic you're trying to do within `ngOnInit` before `this.loading = false`, is it `sync` or `async`? – Amer Aug 31 '21 at 14:35
  • I addded the logic in my edit – Lapskaus Sep 01 '21 at 07:26
  • https://stackoverflow.com/questions/54135784/how-to-implement-a-global-loader-in-angular/66917696#66917696 – Eliseo Sep 01 '21 at 09:42
  • after **this.routes.push(route);** try to add **this.routes = this.routes.slice()**. The purpose is to trigger properly change detection in your map component. If this doesn't work you can try add in map component constructor **(private cdr: ChangeDetectorRef)** and then call **this.cdr.detectChanges()** after **this.routes.push(route)** – Deunz Sep 01 '21 at 09:43
  • Manually triggering the change detect did not help. – Lapskaus Sep 01 '21 at 10:04
  • I just think, your map component is not refreshed properly, it doesnt know a new instance of route has been pushed – Deunz Sep 01 '21 at 10:10
  • The problem seems to lie within the dynamicaly created components for the route. – Lapskaus Sep 01 '21 at 10:13

2 Answers2

0

It seems that creating the layer new L.CircleMarker([routePoint.lat, routePoint.lng],{}); and adding it to the group this.markers.addLayer(marker); take some times until finished, but both of them don't prevent the execution to continue.

So I think you need to listen to the add event of the CircleMarker, then change the loading state within its callback.

First, you can test it for one route only, and if it's okay, you can implement some kind of buffer to know that all the routes have been added before changing the loading state.

Amer
  • 6,162
  • 2
  • 8
  • 34
0

You have to set boolean variable into ngOnInit, It's Wokring for me.

...
@Input() route: Route;
@Input() map: L.Map;
loading = false;
...
ngOnInit(): void {
    this.loading = true;
    // Your Code..............
    this.loading = false;
}
  • Exactly! As stated in my 2. Edit, it works when I am **not** using the `ComponentFactoryResolver` to create component instances for each marker. As soon as I do, the initial state of the route component is not rendered, just the final state after `ngOnInit` – Lapskaus Sep 01 '21 at 11:58