Manage Dynamic Data with a Service

When extending Geocortex Web, you may find yourself creating multiple components that have a shared concern, like a data source, or a REST endpoint call. Shared concerns like this present a good use case for creating a custom service. Implementing a custom service in Geocortex Web allows you to implement logic and shared resources that are available on startup to any component. Custom Services are also the recommended way of registering implementations for custom commands and operations.

Prerequisites

Overview

In this article, we will build a custom service that mocks a periodic fetch of data from a REST endpoint and stores that data for consumption by components. This service will populate a list of news items which will be consumed by a news feed component.

Create and Register the Service

Create a new folder structure with the following files. These files define a new service and export it from the CustomService folder.

  • src/services/CustomDataService/CustomDataService.ts
  • src/services/CustomDataService/index.ts
src/services/CustomDataService/CustomDataService.ts
import { ServiceBase } from "@vertigis/web/services";
export default class CustomDataService extends ServiceBase {}

Every service needs to be registered with the Web SDK in order to be discovered and initialized.

Add a call to registerService in src/index.ts.

export default function(registry: LibraryRegistry) {
...
registry.registerService({
id: "custom-service",
getService: (config) => new CustomService(config),
})
...
}

Mock a Sporadically Updated Data Source

The purpose of this service will be to create a mock data source for the a news feed component. We can mock a sporadically updated news feed data source, simulating what a real news feed, with data arriving at unknown intervals, might behave like. This service will expose a single public property, newsItems which will act as a dynamic data source for the news feed component.

src/services/CustomDataService/CustomDataService.ts
import { ServiceBase } from "@vertigis/web/services";
import Collection from "esri/core/Collection";
const newsItems = [
"New fire hydrant installed at Main and 5th.",
"Pipe burst at 4th and Broadview",
"Fire hydrant reported as needs maintenance by citizen.",
"Crack in road on the Johnson Street Bridge",
"Fire Hydrant vandalized in front of City Hall",
];
export default class CustomDataService extends ServiceBase {
private _curIndex = 0;
newsItems: Collection<string> = new Collection<string>();
private async _fetchRecentNews() {
// REST request for a news item would go here.
return newsItems[this._curIndex++ % newsItems.length];
}
// In a real application, you would want to subscribe to a
// data source and not use polling.
private _startPollingForNewItems() {
const fetchNewsLoop = async () => {
const newNews = await this._fetchRecentNews();
let curTime = new Date().toTimeString().split("GMT")[0];
this.newsItems.unshift(`${curTime}- ${newNews}`);
setTimeout(fetchNewsLoop, Math.random() * 6000);
};
fetchNewsLoop();
}
protected _onInitialize(): Promise<void> {
super._onInitialize();
this._startPollingForNewItems();
return;
}
}

Consume the Data in the Component

Now that we have the news feed data being populated by a service, we need to consume that data in the news feed component. Following the best practices for implementing components, the news item data should be managed by the news feed model. Component Models can inject services as properties, and doing so allows us to directly access the dynamically updated newsItems property on the service.

src/components/NewsFeed//NewsFeedModel.ts
import { ComponentModelBase, serializable } from "@vertigis/web/models";
import Collection from "esri/core/Collection";
import CustomDataService from "../../services/CustomDataService";
import { inject } from "@vertigis/web/services";
@serializable
export default class NewsFeedModel extends ComponentModelBase {
@inject("custom-data-service")
customDataService: CustomDataService | undefined;
/**
* Collection of items to display in the news feed
*/
get newsItems(): Collection<string> {
if (
this.customDataService &&
this.customDataService.newsItems !== undefined
) {
return this.customDataService.newsItems;
} else {
this.messages.commands.ui.displayNotification.execute({
message:
"Could not retrieve news items from dynamic data source.",
});
}
}
}

Complete Example

Finally, we can bring it all together, and add the news item component and styling to the application. The news feed component watches for changes on the news feed model's newsItems property, which is in turn linked to the custom service's newsItems property. In this way, we've built a service which exposes shared data to components. Multiple news feed components could be created, and they would all rely on a single copy of the news items. For more information on how the news feed component was built, see the article on custom components and linking app config to custom components.

note

This example uses Geocortex Web layout components

src/component/NewsFeed/NewsFeed.tsx
import React from "react";
import {
LayoutElement,
LayoutElementProperties,
} from "@vertigis/web/components";
import "./NewsFeed.css";
import { NewsFeedModel } from ".";
import { useWatchCollectionAndRerender } from "@vertigis/web/ui/hooks";
import List from "@vertigis/web/ui/List";
import ListItem from "@vertigis/web/ui/ListItem";
import TitleBar from "@vertigis/web/ui/TitleBar";
import "./NewsFeed.css";
import Typography from "@vertigis/web/ui/Typography";
export default function NewsFeed(
props: LayoutElementProperties<NewsFeedModel>
) {
const { model } = props;
useWatchCollectionAndRerender(model.newsItems);
return (
<LayoutElement {...props}>
<List className="news-item-list">
<TitleBar text="Recent News" />
{model.newsItems.toArray().map((news, idx) => (
<ListItem key={idx}>
<Typography>{news}</Typography>
</ListItem>
))}
</List>
</LayoutElement>
);
}

Next Steps

Check out the Component Reference

Take a deep dive into components in the Geocortex Web SDK

Check out the Service Reference

Take a deep dive into services in the Geocortex Web SDK

Learn more about Referencing Services in Components

Learn the dependency injection pattern for referencing services