Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(all): add external routing guide #1474

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions docs/advanced/routing/external-routing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# External Routing

It is possible to resolve routes dynamically based on platform data. Daffodil enables this behavior with [`@daffodil/external-router`](../../../libs/external-router/README.md).

## Getting Started

### Configuration

`@daffodil/external-router` needs information about how to construct the route. This info can be provided as an argument to [`DaffExternalRouterModule#forRoot`](../../../libs/external-router/src/external-router.module.ts).

#### Insertion Strategy

If routes need to be added at specific points in the router config, the `@daffodil/external-router` configuration exposes [`DaffTypeRoutePair#insertionStrategy`](../../../libs/external-router/src/external-router.module.ts). The insertion strategy can be used to customize the behavior for adding routes.

> See [`@daffodil/external-router` configuration](../../../libs/external-router/guides/configuration.md).

### Route Capture
damienwebdev marked this conversation as resolved.
Show resolved Hide resolved

Guard your wildcard route with [`DaffExternalRouterExistenceGuard`](../../../libs/external-router/routing/src/guard/existence.guard.ts). This will attempt to resolve routes with your platform of choice. If a particular route is resolved by the platform then the guard will invoke the [`DaffExternalRouter`](../../../libs/external-router/src/router/router.service.ts) service and add it to the local route configuration.

For the remainder of the session, subsequent navigations to that path will bypass the wildcard route and be handled directly by the added route.

## Example

The following example demonstrates configuring the external router to accept externally resolved routes of type `product`. Routes resolved to the `product` type will be added to the Angular router config with `MyProductPageComponent` and `DaffProductPageUriResolver`.

The configuration also specifies that in the event an external URL cannot be resolved, the router will be redirected to the `not-found` route.

For example the resolvable route, i.e. a URI the backend platform will recognize and resolve, `product/my-product-name.html` would follow this workflow, given the configurations below:

1. **The User Initializes a Navigation**: A user triggers Angular to route to the `product/my-product-name.html`.
2. **The Route is Processed By Angular Router**: This route is not defined statically in the angular configuration of the app, so it gets caught by the wildcard (`**`) route and gets directed to the `DaffExternalRouterExistenceGuard`.
3. **The `DaffExternalRouterExistenceGuard`**: The "entry point"; where the magic happens.
1. The `DaffExternalRouterExistenceGuard` verifies with the backend platform (through the `DaffExternalRouterDriver`) that `product/my-product-name.html` is indeed a resolvable route and that this particular route resolves to a product.
2. Upon successful verification, the `DaffExternalRouterExistenceGuard` dynamically inserts (through the `DaffExternalRouter`) the `product/my-product-name.html` route into the list of Angular routes using the `productRouteConfig` with which `@daffodil/external-router` has been configured.
3. The Angular router is instructed to reroute to the same route now that `product/my-product-name.html` will be recognized by the Angular Router.
4. **The Route is Processed By Angular Router Again**: The `product/my-product-name.html` route is now directly present in the routing config and will therefore be recognized. Routing will proceed as usual: using the `DaffProductPageUriResolver` to resolve the product data and rendering that data with the `MyProductPageComponent`.

<!-- TODO(griest024): change import path for product routing subpackage -->
`app.module.ts`
lderrickable marked this conversation as resolved.
Show resolved Hide resolved
```ts
import {DaffExternalRouterModule, DaffTypeRoutePair} from '@daffodil/external-router'
import {DaffProductPageUriResolver} from '@daffodil/product/state'

const productRouteConfig: DaffTypeRoutePair = {
type: 'product',
route: {
component: MyProductPageComponent,
..., // additional route config, but without path
resolve: {
product: DaffProductPageUriResolver
}
}
}

@NgModule({
imports: [
DaffExternalRouterModule.forRoot(
{failedResolutionPath: 'not-found'},
[productRouteConfig]
),
],
})
class AppModule {}
```

The following section demonstrates guarding the wildcard route with `DaffExternalRouterExistenceGuard` in order to capture externally resolved URLs and begin the external router process.

`app-routing.module.ts`
```ts
import {DaffExternalRouterExistenceGuard} from '@daffodil/external-router/routing'

@NgModule({
imports: [
RouterModule.forRoot([
{ // navigates here when an external URL fails resolution
path: 'not-found',
...
},
...,
// routes will be inserted here by default
{
path: '**',
canActivate: [DaffExternalRouterExistenceGuard]
}
])
],
exports: [
RouterModule
]
})
export class AppRoutingModule {}
```

#### Final Router Config

If the app is navigated to `mydomain.com/gameboy` and that URL was externally resolved by the platform, the resulting routing config in the above example would resemble:

```ts
const routesWithAddedRoute = [
{
path: 'not-found',
...
},
...,
{
path: 'gameboy',
component: MyProductPageComponent,
resolve: {
product: DaffProductPageUriResolver
},
..., // additional route config
}
// insertion point
{
path: '**',
canActivate: [DaffExternalRouterExistenceGuard]
}
]
```
139 changes: 139 additions & 0 deletions docs/advanced/routing/url-resolution.drawio
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<mxfile>
damienwebdev marked this conversation as resolved.
Show resolved Hide resolved
<diagram id="YwZfv90DbyRDtOZjBhPG" name="Page-1">
<mxGraphModel dx="1614" dy="790" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="2" target="4">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="2" target="3">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="170" y="70"/>
<mxPoint x="170" y="160"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="2" value="User Navigation" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="40" y="40" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="10" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="3">
<mxGeometry relative="1" as="geometry">
<mxPoint x="260" y="237" as="targetPoint"/>
<Array as="points">
<mxPoint x="260" y="237"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="3" value="Route Doesn't Exist" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="200" y="130" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="4" target="8">
<mxGeometry relative="1" as="geometry">
<mxPoint x="350" y="70" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="4" value="Route Exists" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="190" y="40" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="8" value="Navigate to Existing Route" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="360" y="40" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="23" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.383;entryY=-0.017;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="11" target="22">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="11" value="DaffExternalRouterExistenceGuard" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="200" y="240" width="220" height="60" as="geometry"/>
</mxCell>
<mxCell id="12" value="Platform Resolution" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="200" y="430" width="290" height="90" as="geometry"/>
</mxCell>
<mxCell id="17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="15" target="18">
<mxGeometry relative="1" as="geometry">
<mxPoint x="420" y="440" as="targetPoint"/>
<Array as="points">
<mxPoint x="260" y="580"/>
<mxPoint x="260" y="580"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="20" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="15" target="19">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="260" y="590"/>
<mxPoint x="400" y="590"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="32" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="15" target="31">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="260" y="590"/>
<mxPoint x="540" y="590"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="15" value="Specific Platform Driver" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="210" y="490" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="21" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.175;entryY=1.033;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="18" target="2">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="18" value="Add Resolved Route to Angular Configuration" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="200" y="610" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="19" value="Route to 404" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="340" y="610" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="44" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.875;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="22" target="28">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="45" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="22" target="12">
<mxGeometry relative="1" as="geometry">
<Array as="points"/>
</mxGeometry>
</mxCell>
<mxCell id="22" value="Nornalize Route to Primary Outlet + Query Params" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="200" y="330" width="290" height="60" as="geometry"/>
</mxCell>
<mxCell id="27" value="&lt;span&gt;/some/path/to/route.html(secondary:some/path)?query=1#fragment&lt;/span&gt;" style="shape=document;whiteSpace=wrap;html=1;boundedLbl=1;" vertex="1" parent="1">
<mxGeometry x="590" y="300" width="520" height="80" as="geometry"/>
</mxCell>
<mxCell id="28" value="/some/path/to/route.html?query=1#fragment" style="shape=document;whiteSpace=wrap;html=1;boundedLbl=1;direction=west;" vertex="1" parent="1">
<mxGeometry x="590" y="350" width="520" height="80" as="geometry"/>
</mxCell>
<mxCell id="34" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="31" target="33">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="31" value="Redirect" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="480" y="610" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="41" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="33" target="40">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="33" value="Replace Primary Outlet with Resolved Outlet" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="485" y="710" width="110" height="60" as="geometry"/>
</mxCell>
<mxCell id="43" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="37">
<mxGeometry relative="1" as="geometry">
<mxPoint x="60" y="100" as="targetPoint"/>
<Array as="points">
<mxPoint x="120" y="670"/>
<mxPoint x="60" y="670"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="37" value="Return w/ Response Code" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="60" y="710" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="42" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="40" target="37">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="40" value="Add Resolved Route to Angular Configuration" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="280" y="710" width="120" height="60" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
75 changes: 68 additions & 7 deletions libs/external-router/guides/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,87 @@ The External Router exposes configuration via a configuration object passed to t

An array of `DaffTypeRoutePair`s can be passed as the second argument to `forRoot`. They can also be provided through DI using the `daffProvideRouteResolvableByType` function.

## Insertion Strategy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a stackblitz example.


`@daffodil/external-router` inserts routes before the wildcard route by default. More complex insertion behavior can be customized using [`DaffTypeRoutePair#insertionStrategy`](../../../libs/external-router/src/external-router.module.ts).

Note that if the route is inserted into a nested tree then additional work will be needed to ensure that the URL and inserted route's final path match.

`app.module.ts`
```ts
import {daffInsertRouteBeforeWildCardStrategy} from '@daffodil/external-router'

const insertRouteToProductFirstChild = (routeToInsert: Route, routes: Routes) => routes.map(route =>
route.path === 'product'
? {
...route,
children: [
routeToInsert,
...route.children
]
}
: route
)

@NgModule({
imports: [
DaffExternalRouterModule.forRoot({
failedResolutionPath: 'your-custom-error-path',
failedResolutionPath: 'not-found',
}, [
{
type: 'some-type',
route: { redirectTo: '/' },
insertionStrategy: (route: Route, routes: Routes) => [
...routes,
route
]
type: 'product',
route: {
component: MyProductPageComponent
},
insertionStrategy: (routeToInsert: Route, routes: Routes) =>
// insert a route to redirect to your nested route
daffInsertRouteBeforeWildCardStrategy(
{
path: routeToInsert.path,
redirectTo: `product/${routeToInsert.path}`
},
insertRouteToProductFirstChild(routeToInsert, routes)
)
}
]),
],
})
class AppModule {}
```

`app-routing.module.ts`
```ts
import {DaffExternalRouterExistenceGuard} from '@daffodil/external-router/routing'

@NgModule({
imports: [
RouterModule.forRoot([
{
path: 'not-found',
...
},
{
path: 'product',
children: [
// product routes will be inserted here
...
]
},
...,
// all other route types are inserted here by default
{
path: '**',
canActivate: [DaffExternalRouterExistenceGuard]
}
])
],
exports: [
RouterModule
]
})
export class AppRoutingModule {}
```

## Configuration Options

For further information beyond the documentation here, see `DaffExternalRouterConfiguration`.
Expand Down