Reputation: 457
I'm using Angular 2.1 for a large application that has multiple sub-modules each defining sub-applications organized by feature. The top-level module configures the RouterModule with all child routes for the entire application by importing each sub-app's routes, etc. So from the sub-application's perspective, it routes relative to whatever the top-level application set the route to however the sub-application doesn't explicitly know what its top route actually is, directly.
Some of these sub-applications have summary panels for entities that are more fully defined/controlled in another sub-application. Conceptually the modules are setup like this:
Module 1:
Imports Module 2
Routes for Module 1
Component1A
App1View (contains Component1A, M2SummaryComponent)
Module 2:
Routes for Module 2 (one exported as APP2_VIEW_ROUTE, type Route)
Component2A
Component2B
M2SummaryComponent
App2View (contains Component2A, Component2B)
...etc.
I'm looking for a design pattern whereby the M2SummaryComponent can be written to link to a route within its own module while it is instantiated in Module 1's App1View without having to hard-code or re-assemble routes somehow, manually.
I had hoped this was a common-enough problem that the Angular team likely made it possible using something like router.navigate([APP2_VIEW_ROUTE, ...other params])
, where one can simply pass the route object you used to configure RouterModule pertinent to the desired path. Looking at the source code however this doesn't seem to be an option. Instead we have examples of ActivatedRoute.
The problem faced with ActivatedRoute (this.route
below) is that it will be relative to App1View. So trying to use router.navigate(['m2SpecialId'],{relativeTo: this.route})
is a non-starter since it will be relative to the path that got us to App1View, not back into module 2's preferred route to App2View.
The only other, somewhat elegant, route (pun) around these issues as far as I can tell would be to make each sub-application's module be importable as a ModuleWithProviders such that, optionally, one can inform the module of the top-level route applied to its state. Then write up helper functions that assemble URIs specific to that sub-application using its defined routes.
That feels very boiler-plate...like perhaps the solution or design pattern already exists in the framework, hence this question.
Question:
Is there a design pattern for using the Angular2 Router that would allow components imported into other modules to cleanly reference their own module's pre-configured routes while maintaining a flexible top-level route definition?
Upvotes: 3
Views: 2068
Reputation: 541
If you want to access your M2Component in M1 via link then you should use RouterModule and do not import module2 in module1.
Inside module1 use
RouterModule.forRoot([
{
path: 'module2',
loadChildren: 'path-of-module2/module2.module#Module2Module'
}
])
and in module2
RouterModule.forChild([
{
path: '',
component: Module2Component
}
])
Now you can open Module2Component from route '/module2'.
Upvotes: 5
Reputation: 457
Here's the solution I said sounds very boilerplate. It requires that you setup your sub-application modules with a pair of opaque tokens: one for your URI, and the other to guard against re-configuring that URI.
import {
NgModule,
OpaqueToken,
ModuleWithProviders,
Optional,
SkipSelf,
Inject
} from '@angular/core';
export const ROOT_URI = new OpaqueToken('ROOT_URI');
export const URI_GUARD = new OpaqueToken('URI_GUARD');
Next, you need to define your NgModule
:
@NgModule({ /** ... as before ... */ })
export class SubApp1Module {
static configure(uri: string): ModuleWithProviders {
return {
ngModule: SubApp1Module,
providers: [
{ provide: ROOT_URI, useValue: uri },
{ provide: URI_GUARD, useFactory: provideGuard,
deps: [[ Object, new Optional(), new SkipSelf() ]] }
]
};
}
constructor(@Optional() @Inject(URI_GUARD) guard: any) { /** */ }
}
The provideGuard
function:
export function provideGuard(something: any): any {
if (!something) {
return 'guarded';
} else {
throw new Error('Do not attempt to configure this module more than once.');
}
}
In your module that will be importing this one and configuring the RouterModule routes that ultimately get you into this sub-application, you'll add it to the imports: imports: [ ..., SubApp1Module.configure(SPECIAL_ROOT_URL), ...]
.
In any Component/Directive that you have in SubApp1Module, you can grab this module's ROOT_URI
through injection:
import { Inject } from '@angular/core';
import { ROOT_URI } from './whatever/local/export/location';
// Down at the constructor
constructor(@Inject(ROOT_URI) rootUri: string) {
console.log('This is my rootUri: ' + rootUri);
}
Now, this works and of course you can use @Optional
or ?
as appropriate to allow for a default value to be injected. This also works for injecting a service or factory that resolves your routes, etc. if you decide to go that route.
In some ways this feels like the Angular Way™ (i.e., do it like this). However as you can see it's also fairly involved just to allow entities within a module to route to within their own module despite being instantiated into a Component in another module without hard-coding paths, etc.
Upvotes: 0