如果更改了单页应用程序,那么可以使用角度路由,以便在拆分视图中同时显示2条路由吗?

时间:2019-03-27 10:56:52

标签: angular angular-routing

我有一个由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并使用(toggle)按钮而不是routerlinks来完成。但是,我正在开发一个非常大的应用程序,如果可能,我对使用路由感兴趣。

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

1 个答案:

答案 0 :(得分:0)

我尝试的一个选项是设置3个拆分区域,其中1个具有路由器出口。另外两个具有计数器和获取数据组件作为其内容。当用作单页应用程序时,仅第一个分割区域可见。

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中仅可见一次。这是通过对路由使用身份验证保护并禁用上述复选框来防止显示路由器出口中已经可见的组件的分隔区域来实现的。

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"]'>
              <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管理状态更改。该部分也位于下方,以防万一有人感兴趣:

nav-menu.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;
        }
    }
}