Можно ли использовать угловую маршрутизацию, если изменено одностраничное приложение, чтобы оба маршрута могли быть видны в разделенном виде? - PullRequest
1 голос
/ 27 марта 2019

У меня есть пример приложения, созданного Угловой шаблон VS2017 , который представляет собой одностраничное приложение с 3 маршрутами, определенными в app.module.ts

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 маршрута и контент разбит на части.Вероятно, это невозможно, но это идея, а не какая-то дополнительная боковая панель навигации.

1 Ответ

1 голос
/ 04 апреля 2019

Один из вариантов, который я попробовал, - это иметь 3 разделенные области, где в 1 из них есть выход для маршрутизатора.Другие 2 имеют в качестве содержимого компоненты counter и fetch-data.При использовании в качестве одностраничного приложения видна только 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 можно установить видимыми с помощью флажка в компоненте навигации, который выглядит ниже.Обратите внимание, что в моем случае необходимо контролировать, что компонент может быть виден только один раз в графическом интерфейсе.Это делается с помощью средств проверки подлинности для маршрутов и снятия флажков, упомянутых выше, чтобы предотвратить отображение области аплита для компонента, который уже виден в router-outlet.

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;
  }
}

Выше используется избыточность для управления изменениями состояния.Эта часть также находится ниже на случай, если кому-то интересно:

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