1

I basically have a App which renders a small map. On this page is also a "toggle" button which hides the small map with a *ngIf in the parent component and displays the map in large instead. If I now import BrowserAnimationsModule in app.module.ts the large map is not rendered if I click on the toggle button (only if i set a timeout of 1ms before the map is created in ngOnInit of map-base.component.ts).

Sorry..it's a bit hard to explain, here's the code:

App.module.ts

...
@NgModule({
  declarations: [
    AppComponent,
    MapSmallComponent,
    MapLargeComponent,
    MapBaseComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    BrowserAnimationsModule, // comment this line, to make the app work
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.ts

@Component({
  selector: 'app-root',
  template: `
  <app-map-small *ngIf="small"></app-map-small>
  <app-map-large *ngIf="!small"></app-map-large>
  <button (click)="small = !small">Toggle</button>`,
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  small = true;
}

map-base.component.ts

@Component({
  selector: 'app-map-base',
  template: `<div id="map" class="map"></div>`,
  styleUrls: ['./map-base.component.scss']
})
export class MapBaseComponent implements OnInit {
  map: OlMap;
  constructor() { }

  ngOnInit() {
    this.map = new OlMap({
      target: 'map',
      layers: [new OlTileLayer({ source: new OlOSM() })],
      view: new OlView({ zoom: 13, center: fromLonLat([13.376935, 52.516181]) }),
      controls: []
    });
  }
}

map-small.component.ts

@Component({
  selector: 'app-map-small',
  template: `
    <p>other stuff</p>
    <div style="max-width:500px">
      <app-map-base></app-map-base>
    </div>
    <p>other stuff2</p>
  `,
  styleUrls: ['./map-small.component.scss']
})
export class MapSmallComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }
}

map-large.component.ts

@Component({
  selector: 'app-map-large',
  template: `
  <p>Some other stuff</p>
  <app-map-base></app-map-base>`,
  styleUrls: ['./map-large.component.scss']
})
export class MapLargeComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }
}

dependencies

"dependencies": {
    "@angular/animations": "^6.0.9",
    "@angular/cdk": "^6.4.0",
    "@angular/common": "^6.0.2",
    "@angular/compiler": "^6.0.2",
    "@angular/core": "^6.0.2",
    "@angular/forms": "^6.0.2",
    "@angular/http": "^6.0.2",
    "@angular/material": "^6.4.0",
    "@angular/platform-browser": "^6.0.2",
    "@angular/platform-browser-dynamic": "^6.0.2",
    "@angular/router": "^6.0.2",
    "core-js": "^2.5.4",
    "ol": "^5.1.3",
    "rxjs": "^6.0.0",
    "zone.js": "^0.8.26"
  },

So why works everything fine, if I don't import BrowserAnimationsModule and it breaks if I import it? I want to use Angular Material, that's I would need it...

stoph
  • 177
  • 2
  • 11

4 Answers4

2

Here is a solution,

Component template

<div #mapElementRef class="map"></div>

Component class implements OnInit and has the next property

@ViewChild('mapElementRef', { static: true }) mapElementRef: ElementRef;

finally initialize the map on ngOnInit method, you can use

this.map.setTarget(this.mapElementRef.nativeElement);

Hope it helps.

Raphaël Balet
  • 6,334
  • 6
  • 41
  • 78
cabesuon
  • 4,860
  • 2
  • 15
  • 24
0

You have to use ngAfterViewInit, then you can be 'sure' the element inside your components template is present in the DOM.

ngAfterViewInit() {
  this.map = new OlMap({
    target: 'map',
    layers: [new OlTileLayer({ source: new OlOSM() })],
    view: new OlView({ zoom: 13, center: fromLonLat([13.376935, 52.516181]) }),
    controls: []
  });
}
Poul Kruijt
  • 69,713
  • 12
  • 145
  • 149
0

Attention dirty fix
I did try the version of @cabesuon but it still wasn't working.

Like @cabueson said, do

<div #mapElementRef class="map"></div>

And then in your component.ts, but

  • infantilize it under the ngAfterViewInit() as @PoulKrujit said
  • add the setTimeout(() => {}) this is the dirty fix

Here the result

export class MapBaseComponent implements AfterViewInit {
  @ViewChild('mapElementRef', { static: true }) mapElementRef: ElementRef

  map: OlMap

  constructor() {}

  ngAfterViewInit() {
    setTimeout(() => {
      this.map = new Map({
        layers: [new OlTileLayer({ source: new OlOSM() })],
        view: new OlView({ zoom: 13, center: fromLonLat([13.376935, 52.516181]) }),
        controls: [],
      })

      this.map.setTarget(this.mapElementRef.nativeElement)
    }, 0)
  }
}

I'm not 100% sure why, but that's my guess.
ion-content does not refresh himself once instantiate. But, putting the code under the timeout force ion-content to be refreshed. It's also the reason why you don't have to put any millisecond.

Other whay to force the ionic app to refresh does exists, tick for example, but timeout is good enough for what we're doing here.

Raphaël Balet
  • 6,334
  • 6
  • 41
  • 78
0

I had a similar issue, and solved it differently. Let me share with you what I learned.

I have an Angular (13) app, with two components that can be displayed exclusively thanks to a router-outlet... So my App looks like that :

// App.component.html
<router-outlet>

// App.route.module.ts
const routes: Routes = [
  { path: 'map-component', component: MapComponent },
  { path: 'settings', component: SettingsComponent },
];

The map-component is my default route. So when I start my application everything is fine.

But when I enable the "settings" route, my MapComponent is destroyed, then when I go back to "map-component", during some frames I can see that both components are presents in the DOM (as shown in screen shot bellow) :

enter image description here

So what I did in my map component to solve this issue is the following code :

// Map.component.ts

export class MapComponent implements AfterViewInit, DoCheck {
private previousHeight = 0;
@ViewChild('map') map!: ElementRef;


ngAfterViewInit(): void {
    this.previousHeight = this.map.nativeElement.offsetHeight;
    this.mapService.map.setTarget(this.map.nativeElement);
  }

  ngDoCheck(): void {
     if (this.map) {
      if(this.previousHeight !== this.map.nativeElement.offsetHeight) {
        this.previousHeight = this.map.nativeElement.offsetHeight;
        this.mapService.map.updateSize();
      }
    }
  }
}

Because Angular call the DoCheck method during animation, I update the map size all along the animation.

I am not fully satisfied of that method. What I really would like is to disable the router animation (and keep all other animation). Buf even after looking in the angular documentation, I can't find any thing about this "default animation"...

Adrien BARRAL
  • 3,474
  • 1
  • 25
  • 37