如果更改单页应用程序以使 2 条路由在拆分视图中都可见,是不是可以使用 Angular 路由?

Posted

技术标签:

【中文标题】如果更改单页应用程序以使 2 条路由在拆分视图中都可见,是不是可以使用 Angular 路由?【英文标题】:Can Angular Routing be used if single page application is changed so that 2 routes can be both visible w splitted view?如果更改单页应用程序以使 2 条路由在拆分视图中都可见,是否可以使用 Angular 路由? 【发布时间】:2019-03-27 10:56:52 【问题描述】:

我有一个由VS2017 Angular template 创建的示例应用程序,它是一个单页应用程序,在 app.module.ts 中定义了 3 个路由

RouterModule.forRoot([
   path: '', component: HomeComponent, pathMatch: 'full' ,
   path: 'counter', component: CounterComponent ,
   path: 'fetch-data', component: FetchDataComponent ,
])

在 app.component.html

<body>
  <app-nav-menu></app-nav-menu>
  <div class="container">
    <router-outlet></router-outlet>
  </div>
</body>

导航在 nav-menu.component.html 中被定义

<header>
  <nav class='navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3'>
    <div class="container">
      <a class="navbar-brand" [routerLink]='["/"]'>my_new_app</a>
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-label="Toggle navigation"
        [attr.aria-expanded]="isExpanded" (click)="toggle()">
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse" [ngClass]='"show": isExpanded'>
        <ul class="navbar-nav flex-grow">
          <li class="nav-item" [routerLinkActive]='["link-active"]' [routerLinkActiveOptions]=' exact: true '>
            <a class="nav-link text-dark" [routerLink]='["/"]'>Home</a>
          </li>
          <li class="nav-item" [routerLinkActive]='["link-active"]'>
            <a class="nav-link text-dark" [routerLink]='["/counter"]'>Counter</a>
          </li>
          <li class="nav-item" [routerLinkActive]='["link-active"]'>
            <a class="nav-link text-dark" [routerLink]='["/fetch-data"]'>Fetch data</a>
          </li>
        </ul>
      </div>
    </div>
  </nav>
</header>

选择计数器的正常情况如下所示(如果导航在侧面):

Home        | Counter | 
Counter (x) |         |
Fetch       |         |

在某些情况下,我需要让 2 个“主级别”组件可见,以便路由器出口中不会有 1 个组件的区域,而是将区域分成 2 条,并且 2 条路由会以某种方式处于活动状态。

Home        | Counter | Fetch |
Counter (x) |         |       |
Fetch   (x) |         |       |

可以或应该通过角度路由来完成吗? 正常使用仍然是只有 1 条路由处于活动状态,并且路由器出口区域没有被分割。

这可以通过例如使用 ngIf 并使用(切换)按钮而不是路由器链接来完成。但是,我正在开发一个非常大的应用程序,并且如果可能的话,我对使用路由很感兴趣。

链接的“重复”是关于第二个路由器出口的,与此无关。在这里,我想要实现的是主路由器插座同时有 2 条路由处于活动状态并且内容被拆分。这可能是不可能的,但这就是想法,而不是一些侧边栏辅助导航。

【问题讨论】:

Angular2 multiple router-outlet in the same template的可能重复 @OneLunchMan:这是怎么重复的?如果我理解所问的内容,那就完全不同了。 【参考方案1】:

我尝试过的一个选项是有 3 个分割区域,其中 1 个有一个路由器插座。其他 2 个将 counter 和 fetch-data 组件作为其内容。当用作单页应用程序时,只有第一个分割区域是可见的。

app.component.html

<body>
  <app-nav-menu></app-nav-menu>
  <div id="working" >
  <as-split direction="horizontal">
    <as-split-area>
      <router-outlet></router-outlet>
    </as-split-area>
    <as-split-area *ngIf="secondSplitAreaVisible">
      <app-counter-component></app-counter-component>
    </as-split-area>
    <as-split-area *ngIf="thirdSplitAreaVisible">
      <app-fetch-data></app-fetch-data>
    </as-split-area>
  </as-split>
  </div>
</body>

其他 2 个可以通过导航组件中的复选框设置为可见,如下所示。请注意,在我的情况下,必须控制组件只能在 GUI 中可见一次。这是通过对路由使用 auth 保护并禁用上述复选框来完成的,以防止显示在 router-outlet 中已经可见的组件的 aplit 区域。

导航菜单.component.html:

<header>
  <nav class='navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3'>
    <div class="container">
      <a class="navbar-brand" [routerLink]='["/"]'>my_new_app</a>
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-label="Toggle navigation"
              [attr.aria-expanded]="isExpanded" (click)="toggle()">
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse" [ngClass]='"show": isExpanded'>
        <ul class="navbar-nav flex-grow">
          <li class="nav-item" [routerLinkActive]='["link-active"]'>
              <a class="nav-link text-dark" [routerLink]='["/home"]'><mat-checkbox [(ngModel)]="firstChecked" (change)="toggleTab('home')" [disabled]="firstDisabled"></mat-checkbox>Home</a>
          </li>
          <li class="nav-item" [routerLinkActive]='["link-active"]' [ngStyle]="'border-bottom' : secondChecked || secondActive ? '2px solid' : '0px' ">
            <a class="nav-link text-dark" [routerLink]='["/counter"]'>
            <mat-checkbox [(ngModel)]="secondChecked" (change)="toggleTab('counter', secondChecked)" [disabled]="secondActive"></mat-checkbox>Counter</a>
          </li>
          <li class="nav-item" [routerLinkActive]='["link-active"]' [ngStyle]="'border-bottom' : thirdChecked || thirdActive ? '2px solid' : '0px' ">
            <a class="nav-link text-dark" [routerLink]='["/fetch-data"]'><mat-checkbox [(ngModel)]="thirdChecked" (change)="toggleTab('fetch-data', thirdChecked)" [disabled]="thirdActive"></mat-checkbox>Fetch data</a>
          </li>
        </ul>
      </div>
    </div>
  </nav>
</header>

app.module.ts 路由定义

RouterModule.forRoot([
   path: 'home', component: HomeComponent, canActivate: [AuthGuard],
   path: 'counter', component: CounterComponent, canActivate: [AuthGuard] ,
   path: 'fetch-data', component: FetchDataComponent, canActivate: [AuthGuard],
   path: '', redirectTo: '/home', pathMatch: 'full' 

和授权守卫:

@Injectable(
  providedIn: 'root',
)
export class AuthGuard implements CanActivate 
  subscription;
  outletUrl: string;
  secondSplitAreaVisible: boolean = false;
  thirdSplitAreaVisible: boolean = false;

  constructor(
    private router: Router,
    private ngRedux: NgRedux<IAppState>,
    private actions: TabActions) 
      this.subscription = ngRedux.select<string>('outletUrl')
        .subscribe(newUrl => this.outletUrl = newUrl);    // <- New
        this.subscription = ngRedux.select<boolean>('secondOpen') // <- New
        .subscribe(newSecondVisible => this.secondSplitAreaVisible = newSecondVisible);    // <- New
        this.subscription = ngRedux.select<boolean>('thirdOpen') // <- New
        .subscribe(newThirdVisible => this.thirdSplitAreaVisible = newThirdVisible);    // <- New
  

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean 
    if (state.url === '/counter' && this.secondSplitAreaVisible) 
      return false;
    
    if (state.url === '/fetch-data' && this.thirdSplitAreaVisible) 
      return false;
    
    return true;
  

以上使用 redux 来管理状态更改。该部分也在下面,以防有人感兴趣:

导航菜单.component.ts

@Component(
  selector: 'app-nav-menu',
  templateUrl: './nav-menu.component.html',
  styleUrls: ['./nav-menu.component.css']
)
export class NavMenuComponent 
  firstChecked: boolean = false;
  secondChecked: boolean = false;
  thirdChecked: boolean = false;

  firstDisabled: boolean = true;
  secondActive: boolean = false;
  thirdActive: boolean = false;

  constructor(
    private ngRedux: NgRedux<IAppState>,
    private actions: TabActions,
    private router: Router) 
    router.events.subscribe((event) => 
      if (event instanceof NavigationEnd) 
        this.ngRedux.dispatch(this.actions.setOutletActiveRoute(event.url));
        if (event.url.includes('counter')) 
          this.secondActive = true;
          this.thirdActive = false;
          this.firstChecked = false;  
        
        else if (event.url.includes('fetch')) 
          this.thirdActive = true;
          this.secondActive = false;
          this.firstChecked = false;          
        
        else 
          // home
          this.secondActive = false;
          this.thirdActive = false;
          this.firstChecked = true;
        
      
    );
  

  isExpanded = false;

  collapse() 
    this.isExpanded = false;
  

  toggle() 
    this.isExpanded = !this.isExpanded;
  

  toggleTab(name: string, isChecked : boolean)  
    this.ngRedux.dispatch(this.actions.toggleSplitArea( splitArea : name, isVisible: isChecked));
  

app.component.ts

@Component(
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
)
export class AppComponent implements OnDestroy 
  title = 'app';
  secondSplitAreaVisible: boolean = false;
  thirdSplitAreaVisible: boolean = false;

  subscription;

  constructor(
    private ngRedux: NgRedux<IAppState>,
    private actions: TabActions) 
      this.subscription = ngRedux.select<boolean>('secondOpen')
        .subscribe(newSecondVisible => 
          this.secondSplitAreaVisible = newSecondVisible;
        );    
        this.subscription = ngRedux.select<boolean>('thirdOpen')
        .subscribe(newThirdVisible => 
          this.thirdSplitAreaVisible = newThirdVisible;
        ); 
  

  ngOnDestroy()                  
    this.subscription.unsubscribe();
   

app.actions.ts

@Injectable()
export class TabActions 
  static TOGGLE_SPLIT_AREA = 'TOGGLE_SPLIT_AREA';
  static SET_OUTLET_ACTIVE_ROUTE = 'SET_OUTLET_ACTIVE_ROUTE';

  toggleSplitArea(splitAreaToggle: SplitAreaToggle): SplitAreaToggleAction 
    return  
        type: TabActions.TOGGLE_SPLIT_AREA, 
        splitAreaToggle 
    ;
  

  setOutletActiveRoute(url: string) : SetOutletActiveRouteAction 
    return  
        type: TabActions.SET_OUTLET_ACTIVE_ROUTE,
        url
    ;
  

store.ts

export interface IAppState  
    outletUrl : string;
    secondOpen : boolean;
    thirdOpen : boolean;
;

export const INITIAL_STATE: IAppState = 
    outletUrl: 'home',
    secondOpen : false,
    thirdOpen : false
;

export function rootReducer(lastState: IAppState, action: Action): IAppState 
    switch(action.type) 
        case TabActions.SET_OUTLET_ACTIVE_ROUTE: 
            const setRouteAction = action as SetOutletActiveRouteAction;
            const newState: IAppState = 
                ...lastState,
                outletUrl: setRouteAction.url
            
            return newState;
        
        case TabActions.TOGGLE_SPLIT_AREA: 
            const splitToggleAction = action as SplitAreaToggleAction;
            console.log('rootreducer splitareatoggle:' + splitToggleAction.splitAreaToggle.splitArea);
            if (splitToggleAction.splitAreaToggle.splitArea === 'counter') 
                const newState: IAppState = 
                    ...lastState,
                    secondOpen: splitToggleAction.splitAreaToggle.isVisible
                
                return newState;
            
            else 
                const newState: IAppState = 
                    ...lastState,
                    thirdOpen: splitToggleAction.splitAreaToggle.isVisible
                
                return newState;
            
        
        default : 
            return lastState;
        
    

【讨论】:

以上是关于如果更改单页应用程序以使 2 条路由在拆分视图中都可见,是不是可以使用 Angular 路由?的主要内容,如果未能解决你的问题,请参考以下文章

使用地址栏在单页应用程序中路由用户

Backbone.js 路由而不更改 url

VueJs:使用 vue-router 的两个独立且独立的路由/视图

保护私有反应组件

在拆分视图中更改主视图不会更新纵向弹出框

Vue 单页应用(spa)前端路由实现原理