创建一个根据条件加载特定组件的Routes路径 [英] Create a Routes path that loads a specific component based on condition

查看:84
本文介绍了创建一个根据条件加载特定组件的Routes路径的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想为相似的结构化路径加载不同的组件.

手段:

example.com/whatever-post-slug->加载帖子组件
example.com/whatever-page-slug->加载页面组件
example.com/hello-i-am-a-string->加载帖子组件(因为 slug属于帖子)
example.com/about-us->加载页面组件(因为slug属于页面)

我为什么要这样做?

一个人可能会想到:嘿,为什么不定义两个带前缀的不同路径,这是为什么呢?

答案是:我正在创建(通过开源)Angular WordPress主题,我希望路由可以由用户决定,而不必强制使用任何硬编码结构.

在多种可能性之一中,Posts and Pages(如果您不了解WordPress,只知道这是我要使用独立的Modules和Components的两种不同的内容类型)可能会在根级别共享信息./p>

乍一看,我不知道该信息块是属于帖子还是页面还是其他东西(因此UrlMatcher在这里无济于事).

我是否想到了解决方案?

是的,三个.

第一个解决方案

我可以创建一个包装器组件,该组件将为包罗万象的路线加载,然后在该组件内部执行以下操作:

<whatever-a-component *ngIf="showComponentA()"><whatever-a-component>
<whatever-b-component *ngIf="showComponentB()"></whatever-b-component>

然后让包装器组件执行所有逻辑.

这会在游戏中添加一个额外的中间组件.

第二个解决方案

将解析器用于处理包,然后执行解析器中的所有逻辑.

问题是,我需要订阅http通信才能知道我正在处理的是哪种内容类型,并且由于Resolver resolve()方法需要返回一个Observable,因此在那儿订阅是不好的./p>

当然,如果我返回一个可观察到的占位符并等待一段时间,就可以了,就像这样:

// ... inside resolve()
// Force an observable return to allow suscriptions take their time
return Observable.create(observer => {
  setTimeout(() => {
    console.log("Resolver observable is done");
    observer.complete();
  }, 15000);
});

...或者如果我通过mergeMap()用管道传输订阅,并从订阅中获得结果,则返回EMPTY.

一旦我取回数据,就可以设置新的路由,包括必须指向其特定组件的当前路径.

这对我来说似乎不是一个很干净的方法.

第三种解决方案

只需加载一个常规的Dispatcher Component,它将在OnInit()进行所有检查,然后使用{ skipLocationChange: true }导航到秘密"组件特定的url,因此用户将具有正确的路线以及正确的组件将被加载.

但是,这再次在游戏中添加了一个额外的中间组件.

我认为这是最干净的解决方案,因为在应用程序路由"模块中,我可以执行以下操作:

{
path: 'wp-angular/view-post/:id',
loadChildren: () => import('./view-post/view-post.module').then(m => m.ViewPostModule)
},
{
path: 'wp-angular/view-page/:id',
loadChildren: () => import('./view-page/view-page.module').then(m => m.ViewPageModule)
}

因此,只有当用户实际访问这两种内容类型之一时,我才会延迟加载那些模块.

此外,如果用户随后访问相同类型的第二个内容,则该内容类型组件将已经可用.

我可以使用{ skipLocationChange: true }的事实将使按预期方式保留路径成为可能.

此外,这允许显示导航负载反馈,而无需订阅路由器事件.

问题

你会怎么做,为什么?

也许我缺少一些神奇的Angular功能,该功能允许以直接的方式做到这一点.

解决方案

一旦遇到类似的问题,我就这样解决:

通过使用Angular的useFactory提供程序提供RouterModule的路由,可以使用处理应加载哪些组件的模块.

代码可能是这样的:

//HandlerModule

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    RouterModule
  ],
  providers: [
    {
      provide: ROUTES,
      useFactory: configHandlerRoutes,
      deps: [CustomService],
      multi: true
    }
  ]
})

export class HandlerModule {}

export function configHandlerRoutes(customService: CustomService) {
  let routes: Routes = [];
  if (customService.whatever()) {
    routes = [
      {
        path: '', component: AComp
      }
    ];
  } else {
    routes = [
      {
        path: '', component: BComp
      }
    ];
  }
  return routes;
}

然后在您的AppRoutingModule中,路径"的模块将成为HandlerModule:

// AppRoutingModule

 {
    path: '',
    loadChildren: () => import('app/handler/handler.module').then(mod => mod.HandlerModule)
}

在CustomService之后,当提供方法.whatever()的值更改时,您必须更新Router.config,因为应用程序将仅加载首次加载的组件.这是因为HandlerModule中useFactory提供程序使用的函数"configHandlerRoutes"仅在我们第一次导航到"路径时执行,此后,Angular Router已经知道他必须加载哪个组件.

最后,您必须在CustomService中完成

:

  export class CustomService {
  private whateverValue: boolean;
  constructor(private router: Router) {
  }

  public whatever(): boolean {
    return this.whateverValue;
  }

  public setWhatever(value: boolean): void {
    const previous = this.whateverValue;
    this.whateverValue = value;
    if (previous === this.whateverValue) {
      return;
    }
    const i = this.router.config.findIndex(x => x.path === '');
    this.router.config.splice(i, 1);
    this.router.config.push(
      {path: '', loadChildren: () => import('app/handler/handler.module').then(mod => mod.HandlerModule)}
    );
  }
}

就是这样.我在示例中使用了"路径,但您可以使用任何所需的路径.

此外,如果要加载模块而不是组件,则可以使用相同的方法.

如果您想再参考一下,这里是一篇文章,他们使用相同的方法:open source) Angular WordPress theme and I want the routing to be something the user decides, without forcing any hardcoded structure.

In one of the multiple possibilities, Posts and Pages (if you don't know WordPress, just know those are two distinct content types which I want to use independent Modules and Components for) may share slugs at a root level.

And at a first glance, I cannot know if the slug belongs to a post or a page or something else (so UrlMatcher cannot help here).

Did I think of a solution?

Yes, of three.

First solution

I could create a Wrapper Component that will load for the catchall route, and then, inside this component do something like:

<whatever-a-component *ngIf="showComponentA()"><whatever-a-component>
<whatever-b-component *ngIf="showComponentB()"></whatever-b-component>

And let the Wrapper Component do all the logic.

This adds an extra intermediate Component into the game.

Second solution

Use a resolver for the catchall and then do all the logic in the resolver.

The problem is, I need to subscribe to an http communication to know what kind of content type I am dealing with, and since the Resolver resolve() method needs to return an Observable, it's not nice to subscribe in there.

Of course, it works, if I return a placeholder observable that waits for a while, like so:

// ... inside resolve()
// Force an observable return to allow suscriptions take their time
return Observable.create(observer => {
  setTimeout(() => {
    console.log("Resolver observable is done");
    observer.complete();
  }, 15000);
});

... or if I pipe my subscription with mergeMap() and return EMPTY when I get the results from my subscription.

And once I get my data back I can set new routes including the current path that must point to its specific component.

That does not seem a very clean approach to me.

Third solution

Just load a normal Dispatcher Component that will do all the checkings at OnInit() and then navigate to a "secret" component-specific url but using { skipLocationChange: true }, so the user will have the correct route and also the correct component will be loaded.

But again, this adds an extra intermediate component into the game.

I think this is the cleanest solution, since at the App Routing module I can do something like:

{
path: 'wp-angular/view-post/:id',
loadChildren: () => import('./view-post/view-post.module').then(m => m.ViewPostModule)
},
{
path: 'wp-angular/view-page/:id',
loadChildren: () => import('./view-page/view-page.module').then(m => m.ViewPageModule)
}

So, I will have those modules lazy loaded only if the user actually visits one of those two content types.

Also, that content type component will already be available if the user then visits a second content of the same type.

And the fact that I can use { skipLocationChange: true } will make it possible to keep the path as expected.

Plus, this allows displaying navigation loading feedback without the need to subscribe to the Router Events.

Question

What would you do and why?

Maybe I am missing some magic Angular feature that allows doing this in a straight-to-the-point way.

解决方案

Once I had a similar problem and I resolved like this:

You can use a Module that handles what component should be load by providing the ROUTES of the RouterModule using the useFactory provider of Angular.

The code could be something like this:

// HandlerModule

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    RouterModule
  ],
  providers: [
    {
      provide: ROUTES,
      useFactory: configHandlerRoutes,
      deps: [CustomService],
      multi: true
    }
  ]
})

export class HandlerModule {}

export function configHandlerRoutes(customService: CustomService) {
  let routes: Routes = [];
  if (customService.whatever()) {
    routes = [
      {
        path: '', component: AComp
      }
    ];
  } else {
    routes = [
      {
        path: '', component: BComp
      }
    ];
  }
  return routes;
}

Then in your AppRoutingModule the module of the path '' is going to be the HandlerModule:

// AppRoutingModule

 {
    path: '',
    loadChildren: () => import('app/handler/handler.module').then(mod => mod.HandlerModule)
}

After in the CustomService you have to update the Router.config when the value that provides the method .whatever() changes because the application will only load the component that had loaded the first time. This is because the function "configHandlerRoutes" use by the useFactory provider in the HandlerModule is only executed the first time we navigate to the "" path and after that, the Angular Router already know which component he has to load.

In conclusion in the CustomService you have to do:

  export class CustomService {
  private whateverValue: boolean;
  constructor(private router: Router) {
  }

  public whatever(): boolean {
    return this.whateverValue;
  }

  public setWhatever(value: boolean): void {
    const previous = this.whateverValue;
    this.whateverValue = value;
    if (previous === this.whateverValue) {
      return;
    }
    const i = this.router.config.findIndex(x => x.path === '');
    this.router.config.splice(i, 1);
    this.router.config.push(
      {path: '', loadChildren: () => import('app/handler/handler.module').then(mod => mod.HandlerModule)}
    );
  }
}

That's it. I used the "" path for the example but you can use any path you want.

Also, you can use the same approach if you want to load Modules instead of components.

If you want another reference here is an article where they use the same approach: https://medium.com/@german.quinteros/angular-use-the-same-route-path-for-different-modules-or-components-11db75cac455

这篇关于创建一个根据条件加载特定组件的Routes路径的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆