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-c63productname 将是 top-case-32l-rosso-passioneproduct 将是 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"=".*?"})

我想为这条路线设置两个处理程序:

这里的问题当然是第一个处理程序具有可变数量的子目录。我能想到的是用结尾 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
}];

Stackblitz demo

除了在你的路由中使用 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 显示了一个更有趣的场景,根据需要使用延迟加载的模块。但是和我上面讨论的没什么不同。