Working with the Registry
Overview
Flexibility to override the default type of a widget’s children provides a powerful configuration option when it comes to using and customizing widgets with any web application. Additionally, as web applications grow, the physical size of the resources required to load the application becomes increasingly critical. Keeping the size of resources required to load a web application as small as possible ensures that the application can provide an optimal performance for all users. To help with these challenges, Dojo provides a concept of a registry
that can be used to achieve both of these goals in a simple and effective manner, without intruding on the existing development experience.
In this tutorial, we will start with an application that uses concrete widget classes and request all assets when the application first loads. First we will swap all the concrete widget references to load the widgets from a registry
. Then we will create a new widget that will be lazily loaded when the worker card is clicked the first time.
Prerequisites
You can download the demo project and run npm install
to get started.
The @dojo/cli
command line tool should be installed globally. Refer to the Dojo local installation article for more information.
You also need to be familiar with TypeScript as Dojo uses it extensively.
The default registry
Create a default registry.
The first step is to create a registry
that will be made available to the application by passing the instance as the registry
property on the projector
.
Add the Registry
import to the main.ts
module.
import { Registry } from '@dojo/framework/widget-core/Registry';
Now, create a Registry
instance.
const registry = new Registry();
And finally pass the registry
to the renderer's mount
function.
r.mount({ domNode: document.querySelector('my-app') as HTMLElement, registry });
Registries Everywhere!
A registry
can also be used to define an injector that can be used to provide context for responsibilities such as state injection and routing. To learn more, take a look at the container tutorial and routing tutorial .
At the moment we haven’t affected the application, however we now have a handle to a registry
where we can start to define widgets. Once the widgets are defined in the registry
, they will be available through the application and can be used by switching the concrete class in w()
with the registry
label.
Add the widget imports to main.ts
.
import Button from '@dojo/widgets/button';
import TextInput from '@dojo/widgets/text-input';
import Banner from './widgets/Banner';
import WorkerForm from './widgets/WorkerForm';
import WorkerContainer from './widgets/WorkerContainer';
import Worker from './widgets/Worker';
Then define widgets in the registry
after creating the registry.
registry.define('dojo-button', Button);
registry.define('dojo-text-input', TextInput);
registry.define('banner', Banner);
registry.define('worker', Worker);
registry.define('worker-form', WorkerForm);
registry.define('worker-container', WorkerContainer);
In the next section we will use the registry label in our render functions.
Using registry items
Now that the registry
has been made available to the application and the widgets have been defined, we can use the label instead of the widget classes across the application.
Use registry labels in App.ts
's render function.
protected render() {
return v('div', [
w<Banner>('banner', {}),
w<WorkerForm>('worker-form', {
formData: this._newWorker,
onFormInput: this._onFormInput,
onFormSave: this._addWorker
}),
w<WorkerContainer>('worker-container', {
workerData: this._workerData
})
]);
}
Notice that we are passing a generic type to the w()
function call, this is because when using a registry
label it is unable to infer the properties interface. As a result the type falls back to the default, WidgetProperties
interface. Passing the generic tells w()
the type of widget the label represents in the registry
and will correctly enforce the widget’s properties interface.
Use registry labels in WorkerContainer.ts
's render function.
const workers = workerData.map((worker, i) => w<Worker>('worker', {
key: `worker-${i}`,
...worker
}));
Use registry labels in WorkerForm.ts
's render function.
protected render() {
const {
formData: { firstName, lastName, email }
} = this.properties;
return v('form', {
classes: this.theme(css.workerForm),
onsubmit: this._onSubmit
}, [
v('fieldset', { classes: this.theme(css.nameField) }, [
v('legend', { classes: this.theme(css.nameLabel) }, [ 'Name' ]),
w<TextInput>('dojo-text-input', {
key: 'firstNameInput',
label: 'First Name',
labelHidden: true,
placeholder: 'First name',
value: firstName,
required: true,
onInput: this.onFirstNameInput
}),
w<TextInput>('dojo-text-input', {
key: 'lastNameInput',
label: 'Last Name',
labelHidden: true,
placeholder: 'Last name',
value: lastName,
required: true,
onInput: this.onLastNameInput
})
]),
w<TextInput>('dojo-text-input', {
label: 'Email address',
type: 'email',
value: email,
required: true,
onInput: this.onEmailInput
}),
w<Button>('dojo-button', { }, [ 'Save' ])
]);
}
Next, we will create a widget that is lazily loaded when needed!
Lazy loading widgets
First we need to extract the _renderBack
function from Worker.ts
into a new widget, WorkerBack.ts
, and then add the new widget to the registry.
Add the following code in WorkerBack.ts
import { WidgetBase } from '@dojo/framework/widget-core/WidgetBase';
import { v } from '@dojo/framework/widget-core/d';
import { theme, ThemedMixin } from '@dojo/framework/widget-core/mixins/Themed';
import * as css from '../styles/workerBack.m.css';
export interface WorkerBackProperties {
firstName?: string;
lastName?: string;
email?: string;
timePerTask?: number;
tasks?: string[];
}
@theme(css)
export default class WorkerBack extends ThemedMixin(WidgetBase)<WorkerBackProperties> {
protected render() {
const {
firstName = 'firstName',
lastName = 'lastName',
email = 'unavailable',
timePerTask = 0,
tasks = []
} = this.properties;
return [
v('img', {
classes: this.theme(css.imageSmall),
src: 'https://dojo.io/tutorials/resources/worker.svg'
}),
v('div', {
classes: this.theme(css.generalInfo)
}, [
v('div', {
classes : this.theme(css.label)
}, ['Name']),
v('div', [`${lastName}, ${firstName}`]),
v('div', {
classes: this.theme(css.label)
}, ['Email']),
v('div', [`${email}`]),
v('div', {
classes: this.theme(css.label)
}, ['Avg. Time per Task']),
v('div', [`${timePerTask}`])
]),
v('div', [
v('strong', ['Current Tasks']),
v('div', tasks.map((task) => {
return v('div', { classes: this.theme(css.task) }, [ task ]);
}))
])
];
}
}
Add w
import to Worker.ts
import { v, w } from '@dojo/framework/widget-core/d';
Add WorkerBack
import to Worker.ts
import WorkerBack from './WorkerBack';
Update the _renderBack
function to use the WorkerBack
registry items in Worker.ts
private _renderBack() {
const {
firstName = 'firstName',
lastName = 'lastName',
email = 'unavailable',
timePerTask = 0,
tasks = []
} = this.properties;
return v('div', {
classes: this.theme(css.workerBack),
onclick: this.flip
}, [
this._isFlipped
? w<WorkerBack>('worker-back', {
firstName,
lastName,
email,
timePerTask,
tasks })
: null
]);
}
Use Before You Define
The registry
is designed to mirror the behavior and API of custom elements wherever possible. One neat feature is that a registry item can be used before it is defined, and once defined, widgets that use the registry
will automatically re-render!
Now we need to add the registry
definition for WorkerBack.ts
to lazily load when the worker is clicked. Instead of adding a concrete widget class, we add a function that, when called, dynamically imports the widget and returns a promise that returns the widget. This function will not be called until the first time the application tries to use the widget label as part of the usual render
cycle. Initially, before the widget has loaded, nothing will be rendered. Once it has loaded, any widgets that use the lazy widget will automatically re-render.
There are two ways to register a widget in a registry, the first is to define the item in the global registry as demonstrated main.ts
. This method makes the widget available to the entire application, if the widget is only needed by a single widget then the registry item can be defined using the @registry
decorator from @dojo/framework/widget-core/decorators/registry
.
Add the import for the @registry
decorator Worker.ts
import { registry } from '@dojo/framework/widget-core/decorators/registry';
Add the registry item using the @registry
decorator in Worker.ts
@registry('worker-back', () => import ('./WorkerBack'))
Now that the WorkerBack
widget is defined to load lazily, running the application with the browser developer tools open should show that the WorkerBack.js
file is only loaded when a worker card is clicked on for the first time:
Auto Bundling Support
To fully support lazy loading, the Dojo CLI build command will automatically bundle any lazily defined widgets into their own separate file! To learn more, take a look at the build command tutorial.
Summary
In summary, the Registry is a powerful tool for decoupling components from their point of usage. Removing the complexity of dealing with when or how a widget is loaded from the end user means they can author widgets in the same familiar way, while also leveraging the benefits of lazy loading and extensibility as hopefully shown in this tutorial.
If you would like, you can download the completed demo application from this tutorial.