Vue enables an alternative dependency injection pattern using the provide/inject properties in addition to Vue.use
. Injected resources are accessible by any descendent component of a providing ancestor component using a string or symbol identifier.
This repo contains a proof of concept for dependency injection of a simple router using provide/inject.
The project depends on the following packages
- vue-class-component,
- vue-property-decorator and
- vue-rx (+ @rxjs/compat)
- @babel/plugin-proposal-class-properties
The first two packages allow us to write Vue components using the ES5 class syntax and the proposed decorator syntax for compactness and clarity. E.g. we can write
// ancestor component
export default class Ancestor extends Vue {
// ...
@Provide(RESOURCE) resource = resource;
// ...
}
// descendant component
export default class Descendant extends Vue {
// ...
@Inject(RESOURCE) resource;
// ...
}
instead of
// ancestor component
export default {
// ...
provide: {
[RESOURCE]: resource
},
// ...
}
// descendant component
export default {
// ...
inject: {
resource: RESOURCE
},
// ...
}
Rx was used to create a route change observable to broadcast route change events.
AppComponent
provides a simple router component using the @Provide
decorator. The symbol ROUTER
uniquely identifies the router dependency.
export default class App extends Vue {
// Make the router service injectable in descendant components.
@Provide(ROUTER) router = router;
/* ... */
}
The app view consists of a simple navigation bar and a content box.
<div id="app">
<nav>
<ul>
<li><router-link to="/hello">hello</router-link></li>
<li><router-link to="/bye">bye</router-link></li>
</ul>
</nav>
<main>
<router-outlet basePath="/" :routerTable="routerTable">
Loading...
</router-outlet>
</main>
</div>
In the navigation bar, we use RouterLinkComponent
(selected by router-link
) to trigger navigation changes on click. The content box contains a RouterOutletComponent
(selected by router-outlet
) which dynamically renders components using Vue's <component>
. A router table is passed to the outlet (binding :routerTable="routerTable"
, content see below)
{
'hello': HelloComponent
, 'bye': ByeComponent
, '': HelloComponent
}
We pass this to the injected router's register(basePath, routerTable)
method upon creation so that it will process it during path resolution.
export default class RouterOutletComponent extends Vue {
@Inject(ROUTER) router;
/* ... */
@Prop(Object) routerTable;
/* ... */
created() {
this.router.register(this.basePath, this.routerTable);
this.routeChangesSubscription = this.router.routeChanges.subscribe(this.routeChanged);
}
/* ... */
}
Route change events are broadcasted by the router using the routeChanges
observable. RouterOutletComponent
subscribes to this and updates its activeComponent
property for route changes matching its basePath
property exactly.
export default class RouterOutletComponent extends Vue {
/* ... */
@Prop(String) basePath;
/* ... */
activeComponent = null;
/* ... */
routeChanged({path, basePath, subPath, component}) {
// Ignore route changes not meant for this outlet.
if (basePath === this.basePath) {
console.info(`route-change (path: ${path}, base-path: ${basePath}, sub-path: ${subPath}): ${component.name}Component`);
this.activeComponent = component;
}
}
}
A minimal router was implemented using Vue's <component>
for dynamic component creation. The router is injected into both the RouterLinkComponent
and RouterOutletComponent
instances using the symbol ROUTER
.
The basic routing event flow is as follows:
- A
RouterLinkComponent
instance handles a click event by callingnavigateTo
on the injected router
export default class RouterLinkComponent extends Vue {
/* ... */
@Inject(ROUTER) router;
clicked() {
// Tell the router to navigate to the path passed via the "to" props.
this.router.navigateTo(this.to);
}
}
- The injected router resolves the component and if successful emits an event using
routeChanges
- All
RouterOutletComponent
instances receive the route change event and conditionally rerender their component (see methodrouteChanged
above)
- Clone this repository
- Install dependencies
npm install
- Start the server
npm run serve
- Enjoy the bountiful choice of links ;)
You should see the vue logo and a message "Hello there!" or "Bye!" depending on which link you clicked. The console log will contain additional information about the matched path, sub path and component.