import { Origin } from 'aurelia-metadata';
import { relativeToFile } from 'aurelia-path';
import { NavigationInstruction, RouteConfig, RouteLoader, Router } from 'aurelia-router';
import { CompositionEngine, customElement, inlineView, useView, CompositionContext } from 'aurelia-templating';
import { RouterViewLocator } from './router-view';
import { Container } from 'aurelia-dependency-injection';
/**@internal exported for unit testing */
export class EmptyClass { }
inlineView('')(EmptyClass);
/**
* Default implementation of `RouteLoader` used for loading component based on a route config
*/
export class TemplatingRouteLoader extends RouteLoader {
/**@internal */
static inject = [CompositionEngine];
/**@internal */
compositionEngine: CompositionEngine;
constructor(
compositionEngine: CompositionEngine
) {
super();
this.compositionEngine = compositionEngine;
}
/**
* Resolve a view model from a RouteConfig
* Throws when there is neither "moduleId" nor "viewModel" property
* @internal
*/
resolveViewModel(router: Router, config: RouteConfig): Promise {
return new Promise((resolve, reject) => {
let viewModel: string | null | Function;
if ('moduleId' in config) {
let moduleId = config.moduleId;
if (moduleId === null) {
viewModel = EmptyClass;
} else {
// this requires container of router has passes a certain point
// where a view model has been setup on the container
// it will fail in enhance scenario because no viewport has been registered
moduleId = relativeToFile(moduleId, Origin.get(router.container.viewModel.constructor).moduleId);
if (/\.html/i.test(moduleId)) {
viewModel = createDynamicClass(moduleId);
} else {
viewModel = moduleId;
}
}
return resolve(viewModel);
}
// todo: add if ('viewModel' in config) to support static view model resolution
reject(new Error('Invalid route config. No "moduleId" found.'));
});
}
/**
* Create child container based on a router container
* Also ensures that child router are properly constructed in the newly created child container
* @internal
*/
createChildContainer(router: Router): Container {
const childContainer = router.container.createChild();
childContainer.registerSingleton(RouterViewLocator);
childContainer.getChildRouter = function() {
let childRouter: Router;
childContainer.registerHandler(
Router,
() => childRouter || (childRouter = router.createChild(childContainer))
);
return childContainer.get(Router);
};
return childContainer;
}
/**
* Load corresponding component of a route config of a navigation instruction
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
loadRoute(router: Router, config: RouteConfig, navInstruction: NavigationInstruction): Promise {
return this
.resolveViewModel(router, config)
.then(viewModel => this.compositionEngine.ensureViewModel({
viewModel: viewModel,
childContainer: this.createChildContainer(router),
view: config.view || config.viewStrategy,
router: router
} as CompositionContext));
}
}
/**@internal exported for unit testing */
export function createDynamicClass(moduleId: string) {
const name = /([^\/^\?]+)\.html/i.exec(moduleId)[1];
class DynamicClass {
$parent: any;
bind(bindingContext: any) {
this.$parent = bindingContext;
}
}
customElement(name)(DynamicClass);
useView(moduleId)(DynamicClass);
return DynamicClass;
}