HowToUseAService
How to use a service
Section titled âHow to use a serviceâServices are one of the central mechanisms of code sharing in an Open Pioneer Trails client application. Instead of using global variables (or singletons), services are started per-application instance.
Builtin dependency injection support allows services to declare their dependencies on other services. The framework automatically starts all required services in their correct order and injects references where needed.
Services can be used from other services or from UI components.
Using a service from a React component
Section titled âUsing a service from a React componentâIn this section, we will customize the empty appâs UI (in src/apps/empty).
At the time of this writing, the framework does not contain a lot of builtin services we could use for our example, so the following use case is somewhat constructed.
Consider an app embedded as a custom element into some site. It can be useful to change the elementâs attributes from inside the application, for example to allow the surrounding site to apply different styles to the element (e.g. by using attribute selectors (opens in a new tab)).
Our objective in this section is to add a custom attribute to the component by pressing a button in our appâs UI.
By default, services or UI components inside the app do not get a reference to the appâs custom element.
However, the builtin service providing "runtime.ApplicationContext" can be used to achieve that goal.
Thus, we edit our appâs build.config.mjs to state that the UI requires a reference of the service:
import { defineBuildConfig } from "@open-pioneer/build-support";
export default defineBuildConfig({ i18n: ["en"], ui: { references: ["sample-package.Greeter", "runtime.ApplicationContext"] }});The package providing the implementation of a service must be declared as a dependency in the package.json.
Luckily, the empty app already depends on the @open-pioneer/runtime package, so we donât have to do anything in this case.
React components can use hooks (opens in a new tab) to interact with the Open Pioneer Trails framework.
One of which is the useService hook, we will use below.
We extend the UI of the empty app to add our custom class:
import { Container, Heading, Text, chakra, Button, VStack } from "@chakra-ui/react";import { useIntl, useService } from "open-pioneer:react-hooks";import { Greeter, SimpleUiComponent } from "sample-package";import { ApplicationContext } from "@open-pioneer/runtime";import { useState } from "react";
export function AppUI() { const intl = useIntl(); const greeter = useService<Greeter>("sample-package.Greeter"); // (1) const appCtx = useService<ApplicationContext>("runtime.ApplicationContext");
// (2) const [clickCount, setClickCount] = useState(0); const onButtonClicked = () => { const newCount = clickCount + 1; setClickCount(newCount);
// (3) const host = appCtx.getHostElement(); host.setAttribute("data-clicked", String(newCount)); };
return ( <Container> <Heading as="h1" size="lg"> {intl.formatMessage({ id: "heading" })} </Heading> <Text pt={5}>{intl.formatMessage({ id: "text" })}</Text> <Text pt={5}> This messages comes from the sample package{"'"}s greeter service: {greeter.greet()} </Text> <chakra.div mt={5}> <SimpleUiComponent textToShow="This text is rendered inside the sample UI-Component 'SimpleUiComponent'"></SimpleUiComponent> </chakra.div> <VStack pt={2}> <Button onClick={onButtonClicked}>Click me</Button> <Text>I have been clicked {clickCount} times.</Text> </VStack> </Container> );}- (1)
Fetches a reference to the service using the
useServicehook from"open-pioneer:react-hooks". - (2)
Reactâs
useState(opens in a new tab) is used to manage the componentâs click count. - (3) Applies the new click count to the applicationâs host element.
The custom attribute will now be present when you inspect your app after pressing the button:

Using a service from another service
Section titled âUsing a service from another serviceâFor this example, we will build upon the example from previous section.
We will move the logic of updating the elementâs attribute into a service.
The new service will reference "runtime.ApplicationContext", and the UI will be changed to reference our new service instead.
To define our new service - which we will call AttributeService - we create a services.ts file in our application package.
When searching for the implementation of a service, the framework will try to import it from a file called <PACKAGE_NAME>/services.ts (or .js) by default.
If the file does not exist, or if it does not contain a matching export, an error will be generated.
Thus, make sure to add an export in there.
We will create the class for our service:
export class AttributeService { // TODO}To register the service with the framework, we must edit the build.config.mjs:
import { defineBuildConfig } from "@open-pioneer/build-support";
export default defineBuildConfig({ i18n: ["en"], services: { // (1) AttributeService: { // (2) provides: "empty.AttributeService", // (3) references: { ctx: "runtime.ApplicationContext" } } }, ui: { references: ["sample-package.Greeter", "runtime.ApplicationContext"] }});-
(1)
Declares the new service. The name here (left to the":") must match theexportfrom theservices.ts. -
(2)
Declares the interfaces provided by the new service. Interface names can be chosen arbitrarily, but they should not collide. It is a good practice to choose a prefix similar (or equal) to the package name. -
(3)
References the interface"runtime.ApplicationContext". The service object will be injected by the framework into the classâ constructor asctx(the name can be freely chosen).
Next, we will fill in the implementation of AttributeService:
import { ServiceOptions, ApplicationContext } from "@open-pioneer/runtime";
interface References { ctx: ApplicationContext;}
const CLASS_NAME = "my-custom-class";
export class AttributeService { private _ctx: ApplicationContext;
// (1) constructor(options: ServiceOptions<References>) { // (2) this._ctx = options.references.ctx; }
// (3) updateAttribute(count: number) { const host = this._ctx.getHostElement(); host.setAttribute("data-clicked", String(count)); }}-
(1)
The framework uses theoptionsparameter to inject all service options, including thereferences.We use a
Referencesinterface to declare which types (and names) to expect. This should match the configuration in yourbuild.config.mjs. Inside theReferencesinterface, we use theApplicationContexttype directly from the runtime package. You should consult the documentation of a package to find out which TypeScript type(s) correspond to the interfaces you want to reference.Note that this is only needed if youâre using TypeScript. When youâre using JavaScript, just use the
optionsparameter directly. -
(2)
This stores the injected service instance into a property of our own service instance. The namectxis the same as the name of the reference in thebuild.config.mjs. -
(3) The implementation of
updateAttribute()was moved from the React component.
Finally, we will update our UI to use our new service.
In the build.config.mjs, the UI now requires the interface "empty.AttributeService":
import { defineBuildConfig } from "@open-pioneer/build-support";
export default defineBuildConfig({ // ... services ... ui: { references: ["sample-package.Greeter", "empty.AttributeService"] }});And our UI will now call the methods of our AttributeService:
import { Container, Heading, Text, chakra, Button, VStack } from "@chakra-ui/react";import { useIntl, useService } from "open-pioneer:react-hooks";import { Greeter, SimpleUiComponent } from "sample-package";import { useState } from "react";import { AttributeService } from "./services";
export function AppUI() { const intl = useIntl(); const greeter = useService<Greeter>("sample-package.Greeter"); // (1) const attributeService = useService("empty.AttributeService") as AttributeService;
// (2) const [clickCount, setClickCount] = useState(0); const onButtonClicked = () => { const newCount = clickCount + 1; setClickCount(newCount); attributeService.updateAttribute(newCount); };
return ( <Container> <Heading as="h1" size="lg"> {intl.formatMessage({ id: "heading" })} </Heading> <Text pt={5}>{intl.formatMessage({ id: "text" })}</Text> <Text pt={5}> This messages comes from the sample package{"'"}s greeter service: {greeter.greet()} </Text> <chakra.div mt={5}> <SimpleUiComponent textToShow="This text is rendered inside the sample UI-Component 'SimpleUiComponent'"></SimpleUiComponent> </chakra.div> <VStack pt={2}> <Button onClick={onButtonClicked}>Click me</Button> <Text>I have been clicked {clickCount} times.</Text> </VStack> </Container> );}-
(1)
We updated the interface name.The return value from
useServicehas to be casted manually to the expected type in this case, otherwise we would receiveunknown. This is because we have not registered the interface type with the framework (which can be okay for internal services and simple examples). The document How to create a service goes into more detail.Note that this is only needed if youâre using TypeScript.
-
(2)
The body of the click handler now calls our service.
After following these steps, your applicationâs external behavior will be unchanged: the attribute will still be updated.
TypeScript integration
Section titled âTypeScript integrationâSee TypeScript Integration for how a package supporting TypeScript can associate service types with their interface names.