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.

  1. import { Registry } from '@dojo/framework/widget-core/Registry';

Now, create a Registry instance.

  1. const registry = new Registry();

And finally pass the registry to the renderer's mount function.

  1. 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.

  1. import Button from '@dojo/widgets/button';
  2. import TextInput from '@dojo/widgets/text-input';
  3. import Banner from './widgets/Banner';
  4. import WorkerForm from './widgets/WorkerForm';
  5. import WorkerContainer from './widgets/WorkerContainer';
  6. import Worker from './widgets/Worker';

Then define widgets in the registry after creating the registry.

  1. registry.define('dojo-button', Button);
  2. registry.define('dojo-text-input', TextInput);
  3. registry.define('banner', Banner);
  4. registry.define('worker', Worker);
  5. registry.define('worker-form', WorkerForm);
  6. 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.

  1. protected render() {
  2. return v('div', [
  3. w<Banner>('banner', {}),
  4. w<WorkerForm>('worker-form', {
  5. formData: this._newWorker,
  6. onFormInput: this._onFormInput,
  7. onFormSave: this._addWorker
  8. }),
  9. w<WorkerContainer>('worker-container', {
  10. workerData: this._workerData
  11. })
  12. ]);
  13. }

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.

  1. const workers = workerData.map((worker, i) => w<Worker>('worker', {
  2. key: `worker-${i}`,
  3. ...worker
  4. }));

Use registry labels in WorkerForm.ts's render function.

  1. protected render() {
  2. const {
  3. formData: { firstName, lastName, email }
  4. } = this.properties;
  5. return v('form', {
  6. classes: this.theme(css.workerForm),
  7. onsubmit: this._onSubmit
  8. }, [
  9. v('fieldset', { classes: this.theme(css.nameField) }, [
  10. v('legend', { classes: this.theme(css.nameLabel) }, [ 'Name' ]),
  11. w<TextInput>('dojo-text-input', {
  12. key: 'firstNameInput',
  13. label: 'First Name',
  14. labelHidden: true,
  15. placeholder: 'First name',
  16. value: firstName,
  17. required: true,
  18. onInput: this.onFirstNameInput
  19. }),
  20. w<TextInput>('dojo-text-input', {
  21. key: 'lastNameInput',
  22. label: 'Last Name',
  23. labelHidden: true,
  24. placeholder: 'Last name',
  25. value: lastName,
  26. required: true,
  27. onInput: this.onLastNameInput
  28. })
  29. ]),
  30. w<TextInput>('dojo-text-input', {
  31. label: 'Email address',
  32. type: 'email',
  33. value: email,
  34. required: true,
  35. onInput: this.onEmailInput
  36. }),
  37. w<Button>('dojo-button', { }, [ 'Save' ])
  38. ]);
  39. }

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

  1. import { WidgetBase } from '@dojo/framework/widget-core/WidgetBase';
  2. import { v } from '@dojo/framework/widget-core/d';
  3. import { theme, ThemedMixin } from '@dojo/framework/widget-core/mixins/Themed';
  4. import * as css from '../styles/workerBack.m.css';
  5. export interface WorkerBackProperties {
  6. firstName?: string;
  7. lastName?: string;
  8. email?: string;
  9. timePerTask?: number;
  10. tasks?: string[];
  11. }
  12. @theme(css)
  13. export default class WorkerBack extends ThemedMixin(WidgetBase)<WorkerBackProperties> {
  14. protected render() {
  15. const {
  16. firstName = 'firstName',
  17. lastName = 'lastName',
  18. email = 'unavailable',
  19. timePerTask = 0,
  20. tasks = []
  21. } = this.properties;
  22. return [
  23. v('img', {
  24. classes: this.theme(css.imageSmall),
  25. src: 'https://dojo.io/tutorials/resources/worker.svg'
  26. }),
  27. v('div', {
  28. classes: this.theme(css.generalInfo)
  29. }, [
  30. v('div', {
  31. classes : this.theme(css.label)
  32. }, ['Name']),
  33. v('div', [`${lastName}, ${firstName}`]),
  34. v('div', {
  35. classes: this.theme(css.label)
  36. }, ['Email']),
  37. v('div', [`${email}`]),
  38. v('div', {
  39. classes: this.theme(css.label)
  40. }, ['Avg. Time per Task']),
  41. v('div', [`${timePerTask}`])
  42. ]),
  43. v('div', [
  44. v('strong', ['Current Tasks']),
  45. v('div', tasks.map((task) => {
  46. return v('div', { classes: this.theme(css.task) }, [ task ]);
  47. }))
  48. ])
  49. ];
  50. }
  51. }

Add w import to Worker.ts

  1. import { v, w } from '@dojo/framework/widget-core/d';

Add WorkerBack import to Worker.ts

  1. import WorkerBack from './WorkerBack';

Update the _renderBack function to use the WorkerBack registry items in Worker.ts

  1. private _renderBack() {
  2. const {
  3. firstName = 'firstName',
  4. lastName = 'lastName',
  5. email = 'unavailable',
  6. timePerTask = 0,
  7. tasks = []
  8. } = this.properties;
  9. return v('div', {
  10. classes: this.theme(css.workerBack),
  11. onclick: this.flip
  12. }, [
  13. this._isFlipped
  14. ? w<WorkerBack>('worker-back', {
  15. firstName,
  16. lastName,
  17. email,
  18. timePerTask,
  19. tasks })
  20. : null
  21. ]);
  22. }

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

  1. import { registry } from '@dojo/framework/widget-core/decorators/registry';

Add the registry item using the @registry decorator in Worker.ts

  1. @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.

lazy loading gif

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.