Search code examples
angularresponsiverouter-outlet

Angular router-outlet does not work after resize of window


Good morning, I have started a month ago to learn Angular and I am doing my own portfolio, so far so good.

I have a problem though with the router-outlet. At the begining it worked fine, but after making some changes to make the web responsive now it does strange things.

1. app.component.html

<!-- If large screen -->
<div (window:resize)="onResize()" class="row mainpage h-100" *ngIf="!isMobile">
  <div class="col-lg-9 maincontent">
      <cabecera></cabecera>
      <section id="main" class="wrapper">
          <router-outlet></router-outlet>
      </section>
  </div>
  <div id="menu" class="col-lg-3 mainmenu menuwrapper">
    <mainmenu></mainmenu>
  </div>
</div>

<!-- If small screen -->
<div (window:resize)="onResize()" class="row mainpage h-100" *ngIf="isMobile">
  <div class="col-xs-12 maincontent">
    <div id="menu" class="mainmenu">
      <mainmenumovil></mainmenumovil>
    </div>
    <section id="main" class="maincontentmobile">
        <router-outlet></router-outlet>
    </section>
  </div>
</div>

As you can see in 1, It changes the distribution of the components depending on the screen. At the begining it works fine always. If I am in large screen and resize to small screen, the view changes and router still works. But when I go back to large screen, it stops working. everything is fine but the links to change the router-outlet don't do anything. If I go back to small screen then it works again.

2. app.component.ts

import { Component, OnInit } from '@angular/core';
import {ResponsiveService} from './utils/responsive.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  title = 'SilesBonilla';
  public isMobile: Boolean;
  constructor(private responsiveService:ResponsiveService){
  }
  ngOnInit(){
    this.responsiveService.getMobileStatus().subscribe( isMobile =>{
      if(isMobile){
        console.log('Mobile device detected');
        this.isMobile = true;
      }
      else{
        console.log('Desktop detected');
        this.isMobile = false;
      }
    });
    this.onResize();
  }

  onResize(){
    this.responsiveService.checkWidth();
  }
}

3. responsive.service.ts

import { Injectable } from '@angular/core';
import { Subject } from "rxjs/Subject";
import { BehaviorSubject } from "rxjs/BehaviorSubject";
import { Observable } from "rxjs/Observable";

@Injectable()
export class ResponsiveService {
    private isMobile = new Subject();
    public screenWidth: string;


    constructor() {
        this.checkWidth();
    }

    onMobileChange(status: boolean) {
        this.isMobile.next(status);
    }

    getMobileStatus(): Observable<any> {
        return this.isMobile.asObservable();
    }

    public checkWidth() {
        var width = window.innerWidth;
        if (width <= 992) {
            this.screenWidth = 'sm';
            this.onMobileChange(true);
        }  else {
            this.screenWidth = 'lg';
            this.onMobileChange(false);
        }
    }
}

In 2 and 3 You can see I am not doing anything special when changing from small to large screen, in fact this is copy paste from a tutorial I followed.

4. app.routing.ts

(imports)

const appRoutes: Routes = [
    {path: '', redirectTo: 'home', pathMatch: 'full'},
    {path: 'home', component: HomeComponent},
    {path: 'experience', component: ExperienceComponent},
    {path: 'projects', component: ProjectsComponent},
    {path: 'studiesskills', component: StudiesskillsComponent},
    {path: 'certifications', component: CertificationsComponent},
    {path: 'hobbies', component: HobbiesComponent},
    {path: 'projects/projectGo',component: ProjectGoComponent},
    {path: 'projects/projectIndex',component: ProjectIndexComponent},
    {path: '**', component: ErrorComponent} // ESTA SIEMRPE DEBE SER LA ULTIMA DE TODAS.
];

export const appRoutingProviders: any[] = [];
export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes);

I am using lines of code like this in the menu to call the router-outlet

<a rel="noopener" class="menuitem row menuitemelement" [routerLink]= "['/home']" [routerLinkActive]="['menuitemelementselected']"><h5 class="centrado">Home</h5></a>

So again, nothing special. If you didn't understand well the strange behaviour, you can check it in my website https://silesbonilla.com try to change the size to small to convert it to mobile view, and then go back and you will notice that the menu stops working.

Anything else you need just ask.


Solution

  • It seems that <router-outlet> behaves like <ng-content>. If you have duplicate versions in the same component (even if they're guarded by *ngIf), only one will be used*.

    I have approached this problem in the same way as I fix the <ng-content> problem - by using a template with a template outlet.

    Minimal HTML to demonstrate my solution:

    <div (window:resize)="onResize()" *ngIf="!isMobile">
      <ng-container *ngTemplateOutlet="routeroutlet">
      </ng-container>
    </div>
    
    <div (window:resize)="onResize()" *ngIf="isMobile">
      <ng-container *ngTemplateOutlet="routeroutlet">
      </ng-container>
    </div>
    
    <ng-template #routeroutlet>
      <router-outlet></router-outlet>
    </ng-template>
    

    DEMO: https://stackblitz.com/edit/router-template-vvwum9

    *I don't have official docs for this. I say this from the experience of creating components with complex <ng-content> handling.

    You can experiment with this by doing the following.

    <div class="first">
      <router-outlet></router-outlet>
    </div>
    <div class="second">
      <router-outlet></router-outlet>
    </div>
    

    You will not see two versions of the current route's component - the first one is ignored. So maybe the compiler only uses the last one it finds?