Угловое разрешение с отдельными модулями

Как я могу активировать RouteGuard или Resolve на дочерних маршрутах в другом модуле?

Пример сценария:

У меня есть приложение, которое имеет много отдельных модулей, каждый из которых определяет свои собственные маршруты. Рассмотрим эти модули, определяющие следующие маршруты:

Module1 -> ['/a', '/b', '/c']
Module2 -> ['/d', '/e', '/f']

Теперь мне нужно убедиться, что каждый маршрут в приложении имеет следующее разрешение:

resolve: { config: AppConfiguration} 

Мы можем использовать:

{ path: '',  component: AppComponent, resolve: { config: AppConfiguration}  }

Однако это ничего не дает -/ выполняет преобразователь, но /a не.

Единственный способ, которым я нашел, чтобы убедиться, что маршруты /a, /b а также /c вызовите преобразователь, если я сделаю их потомками корня следующим образом:

AppModule -> [ { path: '', component: 'MainComponent', resolver: {..}, children: [
    ...Module1Routes
    ...Module2Routes
] ]}

Но благодаря этому это означает, что приложение больше не структурировано так, как рекомендовано документацией Angular, RouterModule.forChild() больше не используется в других модулях.

Я уверен, что это довольно распространенный вариант использования - есть ли лучший способ?

1 ответ

Решение

Поскольку AppConfiguration предполагается, что это одноэлементный файл, который используется всеми маршрутами, для этого есть два общих шаблона.

Учитывая, что основным преимуществом распознавателей является доступ к данным, развернутым из асинхронного распознавателя, как activatedRoute.snapshot.data.resolverName Одним из способов является использование одноэлементного сервиса, который развернут всеми распознавателями, например:

@Injectable()
class Config {
  data: IConfig;
  data$: Observable<IConfig>;

  constructor(private http: Http) {}

  load(forceReload = false) {
    if (!this.data$ || forceReload) {
      this.data$ = this.http.get('...')
      .map(res => res.json())
      .do(data => {
        this.data = data
       })
      .publishReplay(1)
      .refCount();
    }

    return this.data$;
  }
}

load Метод возвращает наблюдаемый с кэшированным ответом сервера, который функционально равен обещанию. Но так как известно, что наблюдаемые объекты изначально используются маршрутизатором, естественно использовать их в этом контексте.

Тогда все решатели могут вернуться config.load() и убедитесь, что запрос будет выполнен только один раз (если он не называется как config.load(true)):

@Injectable()
class ConfigResolver {
  constructor(private config: Config) {}
  resolve() {
    return this.config.load();
  }
}

...
{ path: ..., component: ... resolve: { config: ConfigResolver } } 

Можно сделать код DRYer и предоставить функцию или класс для добавления config распознаватель для всех маршрутов, хотя это не рекомендуется, поскольку этот подход не совместим с AoT. В этом случае WETter лучше.

Другой способ - использовать плохо документированные APP_INITIALIZER провайдер, который подробно объясняется в этом вопросе. Пока преобразователи маршрутов откладывают изменение маршрута, APP_INITIALIZER работает в обратном направлении и откладывает инициализацию приложения.

Это мульти-провайдер и должен быть определен в корневой инжектор:

export function configInitializerFactory(config: Config) {
  return () => config.load();
}

@NgModule({
  ...
  providers: [
    ...
    Config,
    {
      provide: APP_INITIALIZER,
      useFactory: configInitializerFactory,
      deps: [Config],
      multi: true
    }
  ]
})
...

Не нужно задействовать преобразователи, потому что данные уже были разрешены во время инициализации приложения.

К счастью, data$ наблюдаемый уже разворачивается в data в Config Таким образом, разрешенные данные уже доступны для инъекций как config.data,

Другие вопросы по тегам