【问题标题】:Angular 2 - Content not loading in router outletAngular 2 - 内容未加载到路由器插座中
【发布时间】:2017-04-07 14:53:52
【问题描述】:

我对 Angular 2 还是很陌生,希望你们能帮助我。

我有一个相当简单的应用程序,有一个登录页面,成功登录后,用户会被定向到带有侧边菜单的页面。登录屏幕没有这个侧边菜单。当用户注销时,他会再次被引导到登录页面。

问题是登录后侧边菜单可见,但其他内容仅在刷新后可见。注销也是一样,注销后页面是空白的,只有刷新后才会显示内容(登录页面)。我可能做错了什么,但即使在查看其他问题后我也无法弄清楚。

这是我的路线:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { ProfileComponent } from './profile-component/profile-component.component'
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { LoginComponent } from './login/login.component';
import { LoggedInGuard } from './logged-in/logged-in.guard';

const appRoutes: Routes = [
  {path: 'profile', component: ProfileComponent, canActivate: [LoggedInGuard]},
  {path: 'login', component: LoginComponent},
  {path: '', component: LoginComponent},
  {path: '**', component: PageNotFoundComponent},
];

@NgModule({
  imports: [
    RouterModule.forRoot(appRoutes)
  ],
  exports: [
    RouterModule
  ]
})
export class AppRoutingModule {
}

LoginComponent:

import {Component, OnInit} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {LoginService} from '../login-service/login-service.service';
import {Router} from '@angular/router';


@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss'],
  providers: [ LoginService ]
})
export class LoginComponent implements OnInit {

  loginForm: FormGroup;
  error: String;

  constructor(private formBuilder: FormBuilder,
              private loginService: LoginService,
              private router: Router) {
  }


  ngOnInit() {
    if (this.loginService.isLoggedIn()) {
      this.router.navigate(['/profile']);
    }
    this.loginForm = this.formBuilder.group({
      username: ['', Validators.required],
      password: ['', Validators.required]
    });
  }

  login(): void {
    let self = this;
    this.loginService.login(this.loginForm.value.username, this.loginForm.value.password).subscribe(function (result) {
      if (result) {
        self.router.navigate(['/profile']);
      }

    }, function (error) {
      self.error = 'Invalid';
    });
  }
}

AppComponent HTML 如下所示:

<md-toolbar color="primary" *ngIf="isLoggedIn()">
  <span>Sporter volgsysteem</span>
</md-toolbar>

<md-sidenav-layout *ngIf="isLoggedIn()">
  <md-sidenav #start mode="side" [opened]="true">
      <a routerLink="/add" routerLinkActive="active" md-button color="primary" disabled="false"><md-icon class="icon">add</md-icon><span class="nav-item">Toevoegen</span></a>
      <a routerLink="/compare" routerLinkActive="active" md-button color="primary"><md-icon class="icon">swap_horiz</md-icon><span class="nav-item">Vergelijken</span></a>
      <a routerLink="/search" routerLinkActive="active" md-button color="primary"><md-icon class="icon">search</md-icon><span class="nav-item">Zoeken</span></a>
      <a routerLink="/profile" routerLinkActive="active" md-button color="primary"><md-icon class="icon">account_box</md-icon><span class="nav-item">Profiel</span></a>
      <a routerLink="/feedback" routerLinkActive="active" md-button color="primary"><md-icon class="icon">feedback</md-icon><span class="nav-item">Feedback</span></a>
      <a routerLink="/faq" routerLinkActive="active" md-button color="primary"><md-icon class="icon">info</md-icon><span class="nav-item">FAQ</span></a>

      <div class="spacer"></div>

      <a md-button color="primary" routerLink="/login" (click)="logout()"><md-icon class="icon">exit_to_app</md-icon><span class="nav-item">Uitloggen</span></a>
  </md-sidenav>


  <router-outlet></router-outlet>
</md-sidenav-layout>


<router-outlet *ngIf="!isLoggedIn()"></router-outlet>

AppComponent:

import {Component} from '@angular/core';
import {LoginService} from "./login-service/login-service.service";
import {Router } from '@angular/router'


@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  providers: []
})
export class AppComponent {

  constructor(private loginService : LoginService,
              private router: Router) {
  }

  logout() {
    this.loginService.logout();
    this.router.navigate(['/login']);
  }

  isLoggedIn() {
    return this.loginService.isLoggedIn();
  }

}

那么为什么登录后不显示ProfileComponent的内容,为什么注销后登录页面不显示,但刷新时都显示?

更新

大多数人认为这是由于多个未命名的router-outlets 造成的,因此要验证我是否删除了其中一个插座并一直显示侧边菜单布局。当然是为了测试目的。这并不能解决问题,它给了我相同的行为:配置文件内容仅在刷新后加载。

更新 2 我猜这与使用*ngIf 显示router-outlet 有关

【问题讨论】:

  • 您的 AppComponent 有 2 个 &lt;router-outlet&gt; 并且都没有 name="xxx"。每条路由只允许一个未命名的路由器出口。
  • @GünterZöchbauer 即使由于*ngIf而只有1个可见?我在控制台中没有收到任何错误。
  • 啊,对不起,我错过了第一个*ngIf
  • 我仍然认为问题出在双路由器插座上。将显示逻辑推断为具有自己的路由的单独组件可能会更简单。

标签: javascript angular angular2-routing


【解决方案1】:

根据上面来自@bhetzie 和@jaime-torres 的cmets 以及我自己的假设,我进一步调查了。 事实证明,问题确实出在双 router-outlet 上。

我的解决方案是提取一个LoginModule 模块,然后在该模块中重定向到登录页面。在所有其他情况下,我会路由到另一个名为 SvsModule 的模块,该模块显示带有侧导航结构的登录内容。

所以SvsModule 有以下路由:

RouterModule.forChild([
  {path: 'svs', component: ProfileComponent, canActivate:[LoggedInGuard]},
  {path: 'svs/profile', component: ProfileComponent, canActivate:[LoggedInGuard]}
])

并且 ProfileComponent 使用以下 HTML:

<md-sidenav-layout>
  <md-sidenav #start mode="side" [opened]="true">
    <md-nav-list>
      <a routerLink="/add" routerLinkActive="active" md-button color="primary" disabled="false">
        <md-icon class="icon">add</md-icon>
        <span class="nav-item">Toevoegen</span>
      </a>
      <a routerLink="/compare" routerLinkActive="active" md-button color="primary">
        <md-icon class="icon">swap_horiz</md-icon>
        <span class="nav-item">Vergelijken</span>
      </a>
      <a routerLink="/search" routerLinkActive="active" md-button color="primary">
        <md-icon class="icon">search</md-icon>
        <span class="nav-item">Zoeken</span>
      </a>
      <a routerLink="/profile" routerLinkActive="active" md-button color="primary">
        <md-icon class="icon">account_box</md-icon>
        <span class="nav-item">Profiel</span>
      </a>
      <a routerLink="/feedback" routerLinkActive="active" md-button color="primary">
        <md-icon class="icon">feedback</md-icon>
        <span class="nav-item">Feedback</span>
      </a>
      <a routerLink="/faq" routerLinkActive="active" md-button color="primary">
        <md-icon class="icon">info</md-icon>
        <span class="nav-item">FAQ</span>
      </a>

      <div class="spacer"></div>

      <a md-button color="primary" routerLink="/login" (click)="logout()">
        <md-icon class="icon">exit_to_app</md-icon>
        <span class="nav-item">Uitloggen</span></a>
    </md-nav-list>

  </md-sidenav>
  <router-outlet></router-outlet>

</md-sidenav-layout>

我会接受这个作为答案,但感谢上述用户在我的道路上帮助我。

【讨论】:

    【解决方案2】:

    我认为问题在于上述 cmets 建议的双路由器插座。我在我的项目中做了类似的事情。

    这是我的解决方案:

    我首先创建了一个服务来确定登录状态(我意识到事件发射器不是好的做法,我正在努力将其更改为行为主题):

    GlobalEventsManager

    import { EventEmitter, Injectable } from '@angular/core';
    
    
    @Injectable()
    export class GlobalEventsManager {
         public showNavBar: EventEmitter<boolean> = new EventEmitter<boolean>();
    
    
    
    }
    

    我为我的路由使用了 authguard 来检查登录令牌是否未过期。我为此使用了 angular2-jwt 库

    AuthGuard

    import { Injectable } from '@angular/core';
    import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
    import { tokenNotExpired } from 'angular2-jwt';
    import { GlobalEventsManager } from './GlobalEventsManager';
    
    
        @Injectable()
        export class AuthGuard implements CanActivate {
    
            constructor(private router: Router, private globaleventsManager: GlobalEventsManager) {}
            canActivate(next: ActivatedRouteSnapshot,
                        state: RouterStateSnapshot) {
    
            if (tokenNotExpired('currentUser')) {
                this.globaleventsManager.showNavBar.emit(true);
                return true;
    
            } else {
                localStorage.removeItem('currentUser');
                this.router.navigate(['/login']);
                return false;
            }
    
            }
    
    
        }
    

    当用户使用我的登录组件登录时,我将 GlobalEventsManager 设置为 true

    登录

    constructor(
            private router: Router,
            private authenticationService: AuthenticationService,
            private globaleventsManager: GlobalEventsManager) { }
    
    ngOnInit() {
            // reset login status
    
            this.authenticationService.logout();
            this.globaleventsManager.showNavBar.emit(false);
        }
    
        login() {
            this.loading = true;
            this.authenticationService.login(this.model.username, this.model.password)
                .subscribe( (result) => {
                    this.globaleventsManager.showNavBar.emit(true);
                    this.router.navigate(['/']);
    

    在我的导航栏中,我订阅了 GlobalEventsManager 并为结果设置了一个布尔属性:

    导航栏

    import { Component } from '@angular/core';
    import { Router } from '@angular/router';
    import { NavActiveService } from '../../../services/navactive.service';
    import { GlobalEventsManager } from '../../../services/GlobalEventsManager';
    
    @Component({
      moduleId: module.id,
      selector: 'my-navbar',
      templateUrl: 'navbar.component.html',
      styleUrls:['navbar.component.css'],  
    })
    export class NavComponent {
      showNavBar: boolean = true;
    
      constructor(private router: Router,              
                  private globalEventsManager: GlobalEventsManager){
    
        this.globalEventsManager.showNavBar.subscribe((mode:boolean)=>{
          this.showNavBar = mode;
        });
    
      }
    
    }
    

    在我的导航栏 HTML 中,我现在可以创建两个导航栏,一个导航栏只有一个登录导航,当我的 authguard 注意到 toekn 已过期或用户未登录时,它会显示导航栏,仅登录作为选项。登录后,GlobalEventsManager 的值会发生变化,并且会显示我登录的导航栏:

    导航栏 HTML

    <div *ngIf="showNavBar">
    //My logged in navbar 
    </div>
    <div *ngIf="!showNavBar">
    //navbar that just displays login as an option
    </div>
    

    【讨论】:

    • 如果您将router-outlets 放在 div 中(总共两个),这对您有用吗?我试过这个,但我认为问题是由 *ngIf 引起的,摘要已经完成,然后 router-outlet 被添加到 DOM 中。或者类似的东西至少我现在是这样认为的
    • 您应该只有一个路由器插座。我认为这就是你刚才建议的问题
    • 我遇到了完全相同的问题,我正在使用多个路由器插座,这不是主要问题,但用于暂时隐藏内部路由器插座包装器的 *ngIf 是罪魁祸首。我需要更改那里的逻辑只是为了隐藏而不是等待呈现 html。
    【解决方案3】:

    将您的导航呼叫置于ngZone。 (角度 5)

    import { NgZone } from "@angular/core";
    import { Router } from "@angular/router";
    
    constructor(
        private ngZone: NgZone,
        private router: Router
    ) {}
    
    ...
    
    this.ngZone.run( () => {
        this.router.navigate(['/profile']);
    });
    

    【讨论】:

      最近更新 更多