Angular 通配符路由中的正斜杠
Angular forward slash in wildcard route
我有一个像下面这样的 URL,我需要为它实现路由处理程序:
/shop/et--zubehoer-c16/zubehoer-c48/top-cases--taschen-c63/top-case-32l-rosso-passione-p64
遵循以下逻辑:
/shop/{path}/{productname}-p{product}
因此,path
是一个包含偶数正斜杠的可变类别结构,在本例中为 et--zubehoer-c16/zubehoer-c48/top-cases--taschen-c63
。 productname
将是 top-case-32l-rosso-passione
而 product
将是 64
.
此路由逻辑存在于 Symfony 后端中,我现在将其替换为 Angular。因此,我需要实现相同的逻辑。这是 Symfony 路由定义:
// Product detail pages
@Route("/{path}/{productname}-p{product}", name="shop-detail", defaults={"path"=""}, requirements={"path"=".*?", "productname"="[\w-]+", "product"="\d+"})
// Category pages
@Route("/{path}", name="shop-listing", defaults={"path"=""}, requirements={"path"=".*?"})
我想为这条路线设置两个处理程序:
- 一个处理所有类别页面的页面,例如``
/shop/
/shop/et--zubehoer-c16/
/shop/et--zubehoer-c16/zubehoer-c48/top-cases--taschen-c63/
- 处理产品详细信息页面的页面,因此以
pXXX
结尾的页面,其中 p
表示它是产品详细信息页面,例如
/shop/et--zubehoer-c16/zubehoer-c48/top-cases--taschen-c63/top-case-32l-rosso-passione-p64
这里的问题当然是第一个处理程序具有可变数量的子目录。我能想到的是用结尾 p{product}
来区分这两者。如果 URL 中存在,则应调用产品详细信息处理程序,否则应调用类别处理程序。
第一次尝试使用以下方法失败:
const routes: Routes = [{
path: 'shop',
children: [{
path: '',
pathMatch: 'full',
loadChildren: () => import('product-listing').then(m => m.ProductListingModule),
}, {
path: '**/p:id',
loadChildren: () => import('product-detail').then(m => m.ProductDetailModule),
}]
}, {
path: '**',
component: NotFoundComponent
}];
除了在你的路由中使用 path
属性,你可以使用记录不完整的 matcher
属性(在 the docs 上查看)。你可能没有听说过它,因为它并不常见。但基本上你提供了一个函数来获取路径段(实际上,一个 UrlSegment
数组 => 每个 UrlSegment
包含一个 path
属性引用由 path.split('/')
).如果匹配器函数 returns null
,则表示您没有找到匹配项。如果是 returns 路径段数组,则表示匹配。
因此,您可以将匹配器定义为:
// matches /shop/{path}/{productName}-p{product}
export function productMatcher(url: UrlSegment[]) {
// The path must start with 'shop' and have more than 1 segment
if(!url || url.length < 2 || url[0] !== 'shop') {
return null;
}
// The last segment is supposedly your product
const productSegment = url[url.length - 1].path;
// The 'g' option (global search) is mandatory for the
// regex.exec(...) below to work right
const regex = /([a-zA-z0-9-]+)(-p)(\d+)$/g;
// If it doesn't match the regex, it's not a product: it's a category
if (!regex.test(productSegment)) {
return null;
}
// To the regex.exec(...) function work right, you must reset the index
// because it was messed up by regex.test(...) function
regex.lastIndex = 0;
const m: string[] = regex.exec(productSegment);
// If there are matches, m is different from null
if (m) {
const [whole, productName, _, product] = m;
const category = url
.slice(0, url.length - 1)
.map(x => x.path)
.join('/');
// Return the original segments, and add some parameters
// to be grabbed from the paramMap.
return {
consumed: url,
posParams: {
category: new UrlSegment(category, {}),
productName: new UrlSegment(productName, {}),
product: new UrlSegment(product, {})
}
};
}
return null;
}
然后,在你的路由配置中:
const routes: Routes = [
{
matcher: productMatcher,
component: ProductComponent
}
];
并且在组件中:
constructor(route: ActivatedRoute) {
route.paramMap.subscribe(m => {
this.productName = m.get("productName");
this.product = m.get("product");
this.category = m.get("category");
});
}
以类似的方式,您可以为类别构建匹配器。查看产品匹配器,如果我们在其细分中找到术语 shop
,并且它不是产品,则它必须是类别(至少根据问题文本中提到的条件。
// matches /shop/{path}
export function categoryMatcher(url: UrlSegment[]) {
if(!(url && url.length && url[0].path === 'shop')) {
return null;
}
// Just '/shop'
if (url.length === 1) {
return return {
consumed: url,
posParams: {
category: new UrlSegment('', {}),
}
};
}
const lastSegmentPath = url[url.length - 1].path;
// Every category (except shop) finish with a dash followed by a
// letter different from "p" followed by one or more numbers
const categoryRegex = /(-[a-oq-zA-OQ-Z])(\d+)$/g;
if (!categoryRegex.test(lastSegmentPath)) {
return null;
}
const category = url
.map(x => x.path)
.join("/");
return {
consumed: url,
posParams: {
category: new UrlSegment(category, {}),
}
};
}
并且您可以将匹配器添加到您的路由器配置中:
const routes: Routes = [
{
matcher: productMatcher,
component: ProductComponent
},
{
matcher: categoryMatcher,
component: CategoriesComponent
}
];
这里的顺序无关紧要,因为两个匹配器都会验证是否有产品在做出决定的路径中。
基于上面的暴露,你可以为所欲为。 Stackblitz demo 显示了一个更有趣的场景,根据需要使用延迟加载的模块。但是和我上面讨论的没什么不同。
我有一个像下面这样的 URL,我需要为它实现路由处理程序:
/shop/et--zubehoer-c16/zubehoer-c48/top-cases--taschen-c63/top-case-32l-rosso-passione-p64
遵循以下逻辑:
/shop/{path}/{productname}-p{product}
因此,path
是一个包含偶数正斜杠的可变类别结构,在本例中为 et--zubehoer-c16/zubehoer-c48/top-cases--taschen-c63
。 productname
将是 top-case-32l-rosso-passione
而 product
将是 64
.
此路由逻辑存在于 Symfony 后端中,我现在将其替换为 Angular。因此,我需要实现相同的逻辑。这是 Symfony 路由定义:
// Product detail pages
@Route("/{path}/{productname}-p{product}", name="shop-detail", defaults={"path"=""}, requirements={"path"=".*?", "productname"="[\w-]+", "product"="\d+"})
// Category pages
@Route("/{path}", name="shop-listing", defaults={"path"=""}, requirements={"path"=".*?"})
我想为这条路线设置两个处理程序:
- 一个处理所有类别页面的页面,例如``
/shop/
/shop/et--zubehoer-c16/
/shop/et--zubehoer-c16/zubehoer-c48/top-cases--taschen-c63/
- 处理产品详细信息页面的页面,因此以
pXXX
结尾的页面,其中p
表示它是产品详细信息页面,例如/shop/et--zubehoer-c16/zubehoer-c48/top-cases--taschen-c63/top-case-32l-rosso-passione-p64
这里的问题当然是第一个处理程序具有可变数量的子目录。我能想到的是用结尾 p{product}
来区分这两者。如果 URL 中存在,则应调用产品详细信息处理程序,否则应调用类别处理程序。
第一次尝试使用以下方法失败:
const routes: Routes = [{
path: 'shop',
children: [{
path: '',
pathMatch: 'full',
loadChildren: () => import('product-listing').then(m => m.ProductListingModule),
}, {
path: '**/p:id',
loadChildren: () => import('product-detail').then(m => m.ProductDetailModule),
}]
}, {
path: '**',
component: NotFoundComponent
}];
除了在你的路由中使用 path
属性,你可以使用记录不完整的 matcher
属性(在 the docs 上查看)。你可能没有听说过它,因为它并不常见。但基本上你提供了一个函数来获取路径段(实际上,一个 UrlSegment
数组 => 每个 UrlSegment
包含一个 path
属性引用由 path.split('/')
).如果匹配器函数 returns null
,则表示您没有找到匹配项。如果是 returns 路径段数组,则表示匹配。
因此,您可以将匹配器定义为:
// matches /shop/{path}/{productName}-p{product}
export function productMatcher(url: UrlSegment[]) {
// The path must start with 'shop' and have more than 1 segment
if(!url || url.length < 2 || url[0] !== 'shop') {
return null;
}
// The last segment is supposedly your product
const productSegment = url[url.length - 1].path;
// The 'g' option (global search) is mandatory for the
// regex.exec(...) below to work right
const regex = /([a-zA-z0-9-]+)(-p)(\d+)$/g;
// If it doesn't match the regex, it's not a product: it's a category
if (!regex.test(productSegment)) {
return null;
}
// To the regex.exec(...) function work right, you must reset the index
// because it was messed up by regex.test(...) function
regex.lastIndex = 0;
const m: string[] = regex.exec(productSegment);
// If there are matches, m is different from null
if (m) {
const [whole, productName, _, product] = m;
const category = url
.slice(0, url.length - 1)
.map(x => x.path)
.join('/');
// Return the original segments, and add some parameters
// to be grabbed from the paramMap.
return {
consumed: url,
posParams: {
category: new UrlSegment(category, {}),
productName: new UrlSegment(productName, {}),
product: new UrlSegment(product, {})
}
};
}
return null;
}
然后,在你的路由配置中:
const routes: Routes = [
{
matcher: productMatcher,
component: ProductComponent
}
];
并且在组件中:
constructor(route: ActivatedRoute) {
route.paramMap.subscribe(m => {
this.productName = m.get("productName");
this.product = m.get("product");
this.category = m.get("category");
});
}
以类似的方式,您可以为类别构建匹配器。查看产品匹配器,如果我们在其细分中找到术语 shop
,并且它不是产品,则它必须是类别(至少根据问题文本中提到的条件。
// matches /shop/{path}
export function categoryMatcher(url: UrlSegment[]) {
if(!(url && url.length && url[0].path === 'shop')) {
return null;
}
// Just '/shop'
if (url.length === 1) {
return return {
consumed: url,
posParams: {
category: new UrlSegment('', {}),
}
};
}
const lastSegmentPath = url[url.length - 1].path;
// Every category (except shop) finish with a dash followed by a
// letter different from "p" followed by one or more numbers
const categoryRegex = /(-[a-oq-zA-OQ-Z])(\d+)$/g;
if (!categoryRegex.test(lastSegmentPath)) {
return null;
}
const category = url
.map(x => x.path)
.join("/");
return {
consumed: url,
posParams: {
category: new UrlSegment(category, {}),
}
};
}
并且您可以将匹配器添加到您的路由器配置中:
const routes: Routes = [
{
matcher: productMatcher,
component: ProductComponent
},
{
matcher: categoryMatcher,
component: CategoriesComponent
}
];
这里的顺序无关紧要,因为两个匹配器都会验证是否有产品在做出决定的路径中。
基于上面的暴露,你可以为所欲为。 Stackblitz demo 显示了一个更有趣的场景,根据需要使用延迟加载的模块。但是和我上面讨论的没什么不同。