npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@profiscience/knockout-contrib-router-plugins-component

v2.0.17

Published

[![Version][npm-version-shield]][npm] [![Dependency Status][david-dm-shield]][david-dm] [![Peer Dependency Status][david-dm-peer-shield]][david-dm-peer] [![Dev Dependency Status][david-dm-dev-shield]][david-dm-dev] [![Downloads][npm-stats-shield]][npm-sta

Downloads

145

Readme

router.plugins.component

Version Dependency Status Peer Dependency Status Dev Dependency Status Downloads

Sets the component for a route. Intended for use with dynamic imports for intuitive code-splitting/lazy-loading of views.

Usage

import { Route, componentRoutePlugin } from '@profiscience/knockout-contrib'

Route.usePlugin(componentRoutePlugin)

// RECOMMENDED USAGE
new Route('/', {
  component: () => ({
    template: import('./template.html'),
    viewModel: import('./viewModel'),
  }),
})

Anonymous Components

Annymous components are registered/unregistered by the router as-needed. Simply pass the component configuration you would pass to Knockout.

new Route('/', {
  component: {
    template: 'Hello, World!',
  },
})

By default, the component will be registered using a incrementing GUID (__router_view_${i}__). You may specify the name to register the component as by providing the optional name property.

new Route('/', {
  component: {
    name: 'hello-view',
    template: 'Hello, World!',
  },
})

NOTE: Custom component loaders will NOT be used.

NOTE: Non-class viewModels are supported, but not recommended. See the caveats section below.

NOTE: If your viewModel can be instantiated with new, the instance will be accessible on ctx.component.viewModel after the beforeRender queue completes. Before this, it will be a promise that resolves its eventual value. i.e.

// after plugin execution, before beforeRender queue completes
ctx.component = Promise<{ viewModel }>

// after beforeRender queue completes, all subsequent middleware lifecycle stages (afterRender, beforeDispose, afterDispose)
ctx.component = { viewModel }

Named Components

Named components are components that are already registered with Knockout.

ko.components.register('hello-component', { template: 'Hello, World!' })

new Route('/', {
  component: 'hello-component',
})

Using Accessors

If you need more control, you may use an accessor function. This function recieves the route context as its first and only argument and returns either of the above configurations, optionally promised.

new Route('/', {
  component: (ctx) => ({ template: 'Hello, World!' }),
})

API

Several normalization passes are done on the supplied configuration to attempt to handle whatever you throw at it. The full type signature is...

type MaybePromise<T> = T | Promise<T>
type MaybeDefaultExport<T> = T | { default: T }
type MaybeAccessor<A, T> = T | ((A) => T)
type MaybeLazy<T extends {}> = MaybePromise<
  { [P in keyof T]: MaybePromise<MaybeDefaultExport<T[P]>> }
>
interface IRoutedViewModelConstructor {
  new (ctx: Context & IContext): any
}
type IAnonymousComponent = {
  name?: string
  template: string
  viewModel?: IRoutedViewModelConstructor
}
interface IRouteConfig {
  component?: IRouteComponentConfig
}
type IRouteComponentConfig =
  | MaybeAccessor<Context & IContext, MaybePromise<string>>
  | MaybeAccessor<
      Context & IContext,
      MaybePromise<MaybeLazy<IAnonymousComponent>>
    >

Caveats / Subtleties

Implicit Default Imports

Take the following...

new Route('/', {
  component: async () => ({
    viewModel: await import('./viewModel'),
  }),
})

In this case, viewModel.(ts|js) exports the viewModel constructor as default. But, depending on a few factors (your bundling/transpilation setup, and if there are named exports as well as the default), the way to access the viewModel constructor can vary at runtime. In some cases, the import call will return a promise that resolves the constructor, in other cases it will return a promise that resolves an object with a default property containing the constructor (Promise<{ default: ViewModel }>). Rather than requiriring you to figure out when/where to append .then((imports) => imports.default) or something similar all over the place, if an object with a .default property is resolved, the contents of that default property will be hoisted.

Implicit Async/Await

If your configuration is an object with promised value, you may forgo wrapping the accessor function in async/await. Promised values will be resolved and aggregated into a new object before registering the component.

new Route('/', {
  component: () => ({
    template: import('./template.html'),
    viewModel: import('./viewModel'),
  }),
})

"Unable to instantiate viewModel. This may cause unexpected behavior. See caveats/subtleties in documentation."

When using anonymous components, the router prefers to instantiate the viewModel itself and register components using that instance, i.e. ko.components.register('__router_view_1__', { viewModel: { instance } }). It does this in order to attach the viewModel instance to the context — as ctx.component.viewModel — to provide opportunities to interop with other middleware/plugins. See the router.plugins.init + model.builders.data packages to better understand why this is desireable, as they make use of this.

Additionally, by providing the router with direct access to the viewModel constructor (and thus instance), it is able to control the timing of the dispose method on the viewModel (if any), helping to prevent weird timing issues (and adding support for asynchronous disposal via promises!).

You are seeing this warning because you have provided a a) viewModel that is something other than a class which can be called with new — a createViewModel factory perhaps or b) a named component. In either case, the router is unable to instantiate the viewModel in a predictable manner and falls back to allowing Knockout to instantiate the viewModel at render instead of before render. If you are not using any middleware/plugins that perform introspection on the viewModel instance (i.e. access ctx.component.viewModel), you can safely ignore this warning. Disable it permanently with the following...

import { disableUninstantiableViewModelWarning } from '@profiscience/knockout-contrib-router-plugins-component'

disableUninstantiableViewModelWarning()