Возьмите случай использования, когда у нас есть приложение angular 7
, которое показывает автомобили марок.Вот наши компоненты и соответствующий путь маршрута в скобках:
BrandListComponent
(/brands
) перечислит все доступные бренды.Когда вы нажимаете на марку, она перенаправляет вас к деталям. BrandDetailComponent
(/brands/:brandId
) покажет вам информацию о марке и кнопку для доступа к списку автомобилей марки. CarListComponent
(/brands/:brandId/cars
) отобразит все автомобили выбранной марки и даст вам возможность получить информацию о конкретном автомобиле. У вас нет идентификатора автомобиля и только его имя. CarSummaryComponent
(/brands/:brandId/cars/:carName
) показать сводку по выбранному автомобилю.Необходимо иметь brandId
и carName
, чтобы иметь возможность вызывать данные из API: /brands/42/cars?filter[name]=mx5
Если мы возьмем навигацию пользователя, доступ к нашему сайту, URL будут вследующий порядок:
/brands
/brands/42
/brands/42/cars
/brands/42/cars/mx5/summary
И вы можете получить данные о mx5 только в том случае, если знаете, что это для марки 42 (поскольку название mx5 может быть и от другой марки), и это поведение предназначено .
А теперь представьте, что пользователь не хочет всегда проходить эти 4 шага, чтобы получить сводную информацию о своем автомобиле и добавить /brands/42/cars/mx5/summary
к своему любимому.
Когда он получает к нему доступ, нам нужно получитьтекущий параметр carName (mx5), который прост, потому что он из нашего текущего маршрута, так:
export class CarSummaryComponent implements OnInit {
constructor(private route: ActivatedRoute) { }
ngOnInit() {
console.log(this.route.snapshot.params); // {carName: 'mx5'}
}
}
Но как я могу получить brandId, не связывая CarSummaryComponent
и его родительский маршрут: this.route.snapshot.parent.parent.params
Я пытался положиться на queryParams
из RouterLink
, но я считаю нецелесообразным использовать его в моем случае, так как он добавляет параметрэто значит, что у вас уже есть URL-адрес, поэтому вы дублируете информацию: /brands/42/cars/mx5?brandId=42
Так что я ищу наилучшее ожидание для получения рекурсивной информации, установленной во время всей маршрутизации.Я видел, что когда вы делаете прямой доступ к .../summary
, он все равно выполняет конструктор каждого родителя, так что, возможно, решение начинается здесь, но создание службы для хранения параметра не является элегантным выбором для меня, и кто-то объяснил это правильно:https://github.com/angular/angular/issues/10248#issuecomment-312660931
На данный момент я могу рассказать о решении проблемы:
- Как-то использовать систему распознавания маршрутизации (получить знания о ней в данный момент).Кстати, он мне, вероятно, понадобится для проверки необходимого ввода (поскольку я использую маршрутизацию вместо @Input).
- Полагайтесь на новое состояние
RouterLink
(но как его настроить программнобез выполнения navigate ()?)
Я хотел бы создать «чистый» компонент, как я использовал @ Input / @ Output, но полагаясь на данные из маршрутизации.И даже лучше, я, вероятно, попытаюсь создать свой собственный декоратор из найденного решения, чтобы он был похож на чистый компонент с @RouteInput() brandId: number;
, например.
Если у вас есть другое / лучшее решение, я был бы рад выслушатьих!
И спасибо за потраченное время, чтобы помочь:)
РЕДАКТИРОВАТЬ : вот решение, которое я реализовал на данный момент, но я не могу сделатьвнедрение зависимостей непосредственно внутри декоратора, поэтому нам нужно объявить ActivatedRoute
внутри конструктора с правильным именем (route
).
Во-вторых, тип должен всегда быть строковым, но вы не можете вывести его из декоратора, поэтому, если кто-то напишет carName: {name: string}
приложение во время сбоя во время выполнения, а не во время компиляции.
Последнее, но не менее важное, производительность может стать реальной проблемой, поскольку он переписывает ngOnInit для каждого @RouteInput
и создает новый вызов функции, а для каждого @RouteInput
он также проходит весь маршрут, который не изменился.
Это первый черновик, но он работает:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
function findInRoute(route: ActivatedRoute, propertyKey: string) {
const value: string = route.snapshot.params[propertyKey];
if (!route.parent) {
return value;
}
return findInRoute(route.parent, propertyKey) || value;
}
// It will be exported in another file
function RouteInput(name?: string) {
return (target: any, propertyKey: string) => {
const ngOnInit = target.ngOnInit || (() => {});
target.ngOnInit = function() {
if (this.route instanceof ActivatedRoute) {
const value = findInRoute(this.route, name || propertyKey);
if (value) {
this[propertyKey] = value;
}
}
ngOnInit.bind(this)();
};
};
}
@Component({
selector: 'app-car-summary',
templateUrl: './car-summary.component.html',
styleUrls: ['./car-summary.component.scss']
})
export class CarSummaryComponent implements OnInit {
@RouteInput() brandId: string; // Type should be inferred from the RouteInput
@RouteInput() carName: string;
@RouteInput() test: string;
constructor(private route: ActivatedRoute) {
}
ngOnInit() {
console.log(this); // {brandId: '42', carName: 'mx5', route: {...}}
}
}