Несколько маршрутизаторов-розеток теряют параметры URL-адреса

Я создаю приложение angular 6, которое должно иметь два конкретных router-outlet:

<router-outlet name="navbar"></router-outlet>
<router-outlet></router-outlet>

Приложение управляется токеном, который указан в URL:

http://my.app/*token*/dashboard

Итак, у меня есть эта конкретная маршрутизация:

const routes: Routes = [
    {
        path: ':token', component: CoreComponent, children: [
            { path: 'dashboard', component: DashboardComponent },
            { path: 'app1', component: App1Component },
            { path: 'app2', component: App2Component },
            { path: '', component: NavbarComponent, outlet: 'navbar' }
        ]
    }
];

@NgModule({
    imports: [
        CommonModule,
        RouterModule.forRoot(routes)
    ],
    exports: [RouterModule],
    declarations: []
})

В каждом компоненте я извлекаю параметр маркера из URL-адреса и выполняю некоторые проверки.

constructor(public route: ActivatedRoute,
    public router: Router) {

}

ngOnInit() {
    this.route.params.subscribe(params => {
        let _token: string = params['token'];
        // do something
    });
}

Он отлично работает в NavbarComponent, но я не могу получить параметр в других компонентах. Это похоже на то, что параметр теряется после его обработки в навигационной панели.

Я до сих пор не нашел надежной документации об этом явлении.

Вот пример на stackblitz

https://stackblitz.com/edit/angular-skvpsp?file=src%2Fapp%2Fdashboard.component.ts

И введите этот тестовый URL-адрес: https://angular-skvpsp.stackblitz.io/2345abcf/inbox См. консоль для получения дополнительной информации.

Есть идеи? Спасибо.

[ИЗМЕНИТЬ]

Перепробовал много решений, но так и не нашел правильного ответа.

Я видел, что есть свойство данных, которое можно использовать в модуле маршрутизации. Я проверю это.


person Dams    schedule 31.08.2018    source источник


Ответы (1)


Предварительная оценка

Итак, после некоторой отладки я нашел проблему.
Извините за длинный ответ, я просто добавил несколько советов для вашего кода после решения вашей проблемы.

TL;DR

Я подготовил для вас оптимизированный Stackblitz, проверьте его, чтобы увидеть все изменения!

https://stackblitz.com/edit/stackoverflow-question-52109889

Исходная проблема

Посмотрите на свои маршруты:

const routes: Routes = [
{
    path: ':token', component: AppComponent, children: [
        { path: 'dashboard', component: DashboardComponent },
        { path: 'inbox', component: InboxComponent },
        { path: '', component: NavbarComponent, outlet: 'navbar' }
    ]
}
];

Проблемы с этими маршрутами и комментариями

  1. Angular использует параметры пути. Таким образом, параметр :token передается только AppComponent.
    Возможное решение ниже.
  2. Несвязанный, но тоже запах кода: AppComponent внутри маршрутизатора
    Маршрутизатор заполняет теги <router-outlet> компонентами на основе URL-адреса. Поскольку AppComponent уже является корневым компонентом, вы загружаете AppComponent в RouterOutlet самого себя.
  3. Ваша панель навигации работает только потому, что вы позволяете ей соответствовать ВСЕМ URL-адресам.
    Поскольку вы поместили ее ниже пути :token в качестве дочернего маршрута, она также получает параметр :token, и поскольку ваша логика для ее чтения находится внутри BasePage.ts ` он также может прочитать его.
    Таким образом, ваша панель навигации работает только случайно.

Простейшие решения

Самое простое решение — зайти в BasePage.ts и изменить следующий код

this.queryParams = this.route.snapshot.queryParams
this.routeParams = this.route.snapshot.params;  

to

this.queryParams = this.route.parent.snapshot.queryParams
this.routeParams = this.route.parent.snapshot.params;  

Вот документация для класса ActivatedRoute:
https://angular.io/guide/router#activated-route

Еще несколько советов

Интегрируйте ссылку на приложение для Stackblitz в качестве маршрута по умолчанию

Вместо того, чтобы публиковать свой Stackblitz и ссылку для вызова внутри stackblitz, добавьте свою ссылку внутри stackblitz в качестве маршрута по умолчанию. Просто добавьте этот маршрут в массив маршрутов, и ваш Stackblitz автоматически загрузит нужный маршрут:

{ path: '', pathMatch: 'full', redirectTo: '2345abcf/inbox' }

ActivatedRoute.params и ActivatedRoute.queryParam устарели.

Вместо этого используйте ActivatedRoute.paramMap и ActivatedRoute.queryParamMap.
См. официальные документы angular: https://angular.io/guide/router#activated-route

Максимально используйте наблюдаемые версии paramMaps.

Вы должны использовать paramMaps как Observable практически каждый раз вместо snapshot.paramMaps.

Проблема со снимком заключается в следующем:

  • Маршрутизатор активирует компонент 1
  • Например, ParamSnapshot красный в ngOnInit
  • Маршрутизатор активирует Component2
  • Router activates Component1 again, but with another path param.
    2 Problems:
    • ParamSnapshot is immutable, so the new value would not be accessible
    • ngOnInit больше не запускается, так как компонент уже находится в dom.

Создавайте свои маршруты, используя атрибут routerLink

См.: https://angular.io/guide/router#router-links. позволяет избежать возможных ошибок при построении маршрутов вручную. Обычно маршрут с двумя точками-маршрутизаторами выглядит так:

https://some-url.com/home(conference:status/online)

/home — это основной маршрут, а (conference:status/online) говорит, что <router-outlet name=‚conference‘> должен загрузить маршрут /status/online.

Поскольку вы опускаете последнюю часть для второго выхода, не сразу понятно, как angular будет обрабатывать маршруты.

Не используйте вторую розетку роутера только для навигации

Это делает вещи более сложными, чем они должны быть. Вам не нужно перемещаться по навигации отдельно от контента, не так ли? Если вам не нужна отдельная навигация, проще включить ваше меню напрямую в шаблон AppComponent.

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

Используйте либо базовый класс, либо службу angular

Вы предоставляете свою BasePage как услугу Angular через

@Injectable({
    providedIn: ‚root‘
})

Использование внедряемого сервиса в качестве базового класса кажется очень странным замыслом. Я не могу придумать причину, я хотел бы использовать это. Либо я использую BaseClass как BaseClass, который работает независимо от angular. Или я использую угловой сервис и внедряю его в компоненты, которым нужна функциональность сервиса.

Примечание. Вы не можете просто создать службу, которая вводит Router и ActivatedRoute, потому что она получит корневые объекты этих компонентов, которые пусты. Angular генерирует собственный экземпляр ActivatedRoute для каждого компонента и внедряет его в компонент.

Решение. См. «Лучшее решение: TokenComponent с Token Service» ниже.

Лучшее решение: TokenComponent с Token Service

Лучшее решение, которое я могу придумать прямо сейчас, — это создать TokenComponent вместе с TokenService. Вы можете просмотреть рабочую копию этой идеи на Stackblitz:

https://stackblitz.com/edit/angular-l1hhj1

Теперь важные фрагменты кода, на случай, если Stackblitz будет удален.

Мой TokenService выглядит так:

import { OnInit, Injectable } from '@angular/core';
import { Observable, ReplaySubject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class TokenService {

 /**
  * Making the token an observable avoids accessing an 'undefined' token.
  * Access the token value in templates with the angular 'async' pipe. 
  * Access the token value in typescript code via rxjs pipes and subscribe.
  */
  public token$: Observable<string>;

  /**
   * This replay subject emits the last event, if any, to new subscribers.
   */
  private tokenSubject: ReplaySubject<string>;

  constructor() {
    this.tokenSubject = new ReplaySubject(1);
    this.token$ = this.tokenSubject.asObservable();
  }

  public setToken(token: string) {
    this.tokenSubject.next(token);
  }

}

И этот TokenService используется в TokenComponent в ngOnInit:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { TokenService } from './token.service';

@Component({
  selector: 'app-token',
  template: `
  <p>Token in TokenComponent (for Demonstration): 
     {{tokenService.token$ | async}}
  </p>
  <!--
      This router-outlet is neccessary to hold the child components
      InboxComponent and DashboardComponent
  -->
  <router-outlet></router-outlet>
    `
})
export class TokenComponent implements OnInit {


  public mytoken$: Observable<string>;

  constructor(
    public route: ActivatedRoute,
    public router: Router,
    public tokenService: TokenService
  ) {

  }

  ngOnInit() {
    this.route.paramMap.pipe(
      map((params: ParamMap) => params.get('token')),
      // this 'tap' - pipe is only for demonstration purposes
      tap((token) => console.log(token))
    )
      .subscribe(token => this.tokenService.setToken(token));
  }

}

Наконец, конфигурация маршрута, чтобы склеить все это вместе:

const routes: Routes = [
  {
    path: ':token', component: TokenComponent, children: [
      { path: '', pathMatch: 'full', redirectTo: 'inbox'},
      { path: 'dashboard', component: DashboardComponent },
      { path: 'inbox', component: InboxComponent },
    ]
  },
  { path: '', pathMatch: 'full', redirectTo: '2345abcf/inbox' }
];

Как получить доступ к токену сейчас

  1. Вставьте TokenService туда, где вам нужен токен.

  2. Когда вам это нужно в шаблоне, получите к нему доступ с помощью асинхронного канала следующим образом:

     <p>Token: {{tokenService.token$ | async}} (should be 2345abcf)</p>
    
  3. Когда вам нужен токен внутри машинописного текста, получите доступ к нему, как к любому другому наблюдаемому (пример в компоненте «Входящие»)

     this.tokenService.token$
     .subscribe(token => console.log(`Token in Inbox Component: ${token}`));
    

Я надеюсь, это поможет вам. Это был интересный вызов! :)

person Benjamin Jesuiter    schedule 13.09.2018
comment
@Dams не стесняйтесь спрашивать меня, нужна ли вам помощь с моим демонстрационным кодом! ;) - person Benjamin Jesuiter; 13.09.2018
comment
большое спасибо! Я не думал, что получу совет или ответ. Я новичок в angular, поэтому у меня все еще есть вредные привычки. В любом случае, я буду тщательно применять все ваши советы и внимательно читать ваш код, чтобы понять его. Чтобы объяснить, что я хотел сделать: я просто хотел разделить панель навигации и другое содержимое. Поскольку я выполняю некоторые проверки токена, я не хотел делать это при каждом обновлении. Не думал, что смогу сделать это прямо в appcomponent. Если у вас есть очень хорошие уроки по angular 6, я с радостью приму :) Еще раз спасибо! вы сделали мой день! - person Dams; 14.09.2018
comment
@Dams Хорошо, что я смог тебе помочь! :) Да, у меня есть хорошая рекомендация для изучения Angular 6: посмотрите этот курс Максимлиана Шварцмюллера на Udemy: Angular 6 (ранее Angular 2) — полное руководство udemy.com/the-complete-guide-to-angular-2/learn/v4/overview - person Benjamin Jesuiter; 15.09.2018