Розетки с именами маршрутизатора, которые активируются один раз

Возможно ли иметь именованные розетки маршрутизатора, которые активируются один раз, а затем никогда не уничтожаются, независимо от того, какой маршрут используется в основной розетке?

Намерение состоит в том, чтобы иметь компоненты, которые сохраняются на странице (например, боковая панель), но получают преимущества маршрутизации при их начальной загрузке, такие как охранники (преобразователи) и отложенная загрузка.

Требование состоит в том, чтобы именованные выходы не влияли на UX каким-либо негативным образом, например, вводя мусорные суффиксы в URL-адрес SPA, например. (outletName:routeName), они также не должны быть случайно деактивированы. Если есть способ отключить их от роутера после первоначальной активации, это было бы уместно.

Опция skipLocationChange не может использоваться для этой цели. В этом примере /login(popup:compose) URL-адрес появляется при последовательном переходе по Contact и Login маршрутам.


person Estus Flask    schedule 16.02.2018    source источник
comment
вы можете создавать дополнительные розетки   -  person pixelbits    schedule 17.02.2018
comment
@pixelbits Да, это идея. Но как именно они должны быть определены? Я считаю, что вопрос достаточно конкретен, чтобы не считаться «слишком широким», и имеет ограниченное количество возможных качественных ответов. Я не предоставил никакого кода, потому что в этом случае он был бы универсальным. В этом примере из руководства видно, что вторичная popup розетка сохраняется при навигации по первичным розеткам, но он загрязняет URL с помощью (popup:compose).   -  person Estus Flask    schedule 17.02.2018
comment
При навигации укажите skipLocationChange: true   -  person pixelbits    schedule 17.02.2018
comment
@pixelbits Но это будет работать только для одиночной навигации на самом вторичном выходе. См. stackblitz.com/edit/angular-kfuscp. При нажатии на ссылку Contact URL-адрес не меняется, но при нажатии на Login появляется (popup:compose). Описано здесь stackoverflow.com/questions/43643780/   -  person Estus Flask    schedule 17.02.2018
comment
Я попробовал, надеясь, что компонент можно будет переместить в другой контейнер после активации (см. /а>). Сначала казалось, что это может работать, но это не так: привязки теряются в процессе. Как только функция, запрошенная в выпуске 20824, будет установлена, это может стать для вас решением. .   -  person ConnorsFan    schedule 26.05.2018
comment
@ConnorsFan Спасибо за ваше исследование, я не знал об этой проблеме. Подумайте о том, чтобы предоставить это как ответ, если хотите. Конечно, я бы хотел, чтобы эта проблема была решена, но если в настоящее время это невозможно, отрицательный ответ тоже является ответом.   -  person Estus Flask    schedule 28.05.2018


Ответы (4)


Маршрутизатору нужна информация об именованных розетках, поэтому есть хорошие шансы, что вам поможет реализация вашей собственной UrlSerializer.

Идея проста, процесс десериализации должен знать о маршрутах, которые имеют статические именованные выходы, и создавать UrlTree, который содержит именованные выходы, то есть для /login URL-адреса должны создавать те же UrlTree, что и сериализатор по умолчанию, который будет создавать для URL-адреса /login(popup:compose). При сериализации статические именованные параметры выхода не должны включаться в результирующий URL.

person kemsky    schedule 28.05.2018
comment
Это отличная идея, спасибо. Я не уверен, что есть лучший способ реконструировать дерево URL-адресов, чем я, но это сработало для меня. - person Estus Flask; 28.05.2018

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

<router-outlet name="popup" #popupRouterOutlet (activate)="processActivate($event)"></router-outlet>
<ng-container #popupContainer></ng-container>
export class AppComponent {

  @ViewChild("popupRouterOutlet", { read: ViewContainerRef }) private popupRouterOutlet: ViewContainerRef;
  @ViewChild("popupContainer", { read: ViewContainerRef }) private popupContainer: ViewContainerRef;

  constructor(public router: Router) {
  }

  processActivate(e) {
      let viewRef = this.popupRouterOutlet.detach(0);
      this.popupContainer.insert(viewRef);
      this.router.navigate([".", { outlets: { popup: null } }]);
  }
}

В обработчике событий activate компонент отсоединяется от выхода маршрутизатора, вставляется в ng-container, а выход маршрутизатора очищается. Затем компонент может оставаться в DOM, не используя больше вторичный маршрут.

Статическое содержимое компонента передается успешно, но, к сожалению, привязки не переносятся. Об этой проблеме уже сообщалось. На Angular Github был сделан запрос в выпуске 20824, чтобы разрешить перемещение компонента из одного контейнера в другой. Пока этот запрос не будет реализован, такая передача не представляется возможной.

person ConnorsFan    schedule 28.05.2018

Мы столкнулись с тем же (важным) требованием UX в нашем проекте и придумали получистое, но пока полностью функциональное решение.

Реализуйте пользовательский LocationStrategy, мы просто расширяем класс PathLocationStrategy по умолчанию и предварительно обрабатываем URL-адрес (который будет представлен пользователю/браузеру):

@Injectable()
export class OnlyPrimaryLocationStrategy extends PathLocationStrategy implements LocationStrategy {
  static readonly AUX_ROUTE_SEPERATOR = '//';

  replaceState(state: any, title: string, url: string, queryParams: string): void {
    super.replaceState(state, title, this.preprocessUrl(url), queryParams);
  }

  pushState(state: any, title: string, url: string, queryParams: string): void {
    super.pushState(state, title, this.preprocessUrl(url), queryParams);
  }

  preprocessUrl(url: string): string {
    if (url.includes(OnlyPrimaryLocationStrategy.AUX_ROUTE_SEPERATOR)) {
      if (url.split(OnlyPrimaryLocationStrategy.AUX_ROUTE_SEPERATOR).length > 2) {
        throw new Error(
          'Usage for more than one auxiliary route on the same level detected - please recheck imlementation'
        );
      }
      return url.split(OnlyPrimaryLocationStrategy.AUX_ROUTE_SEPERATOR)[0].replace('(', '');
    } else {
      return url;
    }
  }
}

Не забудьте указать его в своем модуле:

providers: [
    {
     // ...
      provide: LocationStrategy,
      useClass: OnlyPrimaryLocationStrategy,
    },
  ],

Обработка строк, очевидно, не является на 100% чистой, но она выполняет свою работу за нас — возможно, она поможет вам. Имейте в виду, что ваш URL-адрес теперь не может полностью восстановить состояние вашего маршрутизатора (очевидно).

person mitschmidt    schedule 18.11.2019

skipLocationChange опция навигации работает только для маршрутизатора, для которого она была предоставлена, тогда в URL-адресе появляется именованная розетка, например /login(foo:bar).

Можно иметь постоянную розетку маршрутизатора foo, переопределив UrlSerializer, как было предложено @kemsky:

import {
  UrlSerializer, DefaultUrlSerializer, UrlSegmentGroup, UrlTree
} from '@angular/router';

export class FooUrlSerializer extends DefaultUrlSerializer {
  serialize(tree) {
    const { foo, ...noFooChildren } = tree.root.children;
    const root = new UrlSegmentGroup(tree.root.segments, noFooChildren);
    const noFooTree = Object.assign(new UrlTree(), tree, { root });

    return super.serialize(noFooTree);
  }
}

...
providers: [{ provide: UrlSerializer, useClass: FooUrlSerializer }, ...]
...
person Estus Flask    schedule 28.05.2018