Advanced configuration
Reactivity
When the FacilMap server sends an event to the client that an object has been created, changed or deleted, the client emits the event and also persists it in its properties. So you have two ways to access the map data: By listening to the map events and persisting the data somewhere else, or by accessing the properties on the Client object.
If you are using a UI framework that relies on a change detection mechanism (such as Vue.js or Angular), facilmap-client provides a way to make its properties reactive. Internally, the client maintains a state
object (for any properties related to the client itself) and a data
object (for any received map objects). These two objects are stored as private properties on the client object. All public properties of the client object are just getters that return the data from the two private objects. The client modifies these two objects consistently in the following way:
- When the client object is constructed, it constructs the private
state
anddata
objects by callingthis.object = makeReactive(object)
. - When the client sets a property inside the
state
anddata
objects, it does so by callingthis._set(this.object, key, value)
. This includes setting nested properties. - When the client deletes a property inside the
state
anddata
objects, it does so by callingthis._delete(this.object, key)
. This includes deleting nested properties.
You can override the _makeReactive
, _set
and _delete
methods to make the private properties (and as a consequence the public getters) of facilmap-client reactive. This way your UI framework will detect changes to any properties on the client, and you can reference values like client.mapData.name
, client.disconnected
and client.loading
in your UI components.
Note that client always replaces whole objects rather than updating individual properties. For example, when a new version of the map settings arrives, client.mapData
is replaced with a new object, or when a new marker arrives, client.markers[markerId]
is replaced with a new object. This makes deep watches unnecessary in most cases.
Vue.js 3
class ReactiveClient extends Client {
_makeReactive(object) {
return Vue.reactive(object);
}
}
Vue.js 2
class ReactiveClient extends Client {
_set(object, key, value) {
Vue.set(object, key, value);
}
_delete(object, key) {
Vue.delete(object, key);
}
}
Angular.js
class ReactiveClient extends Client {
_set(object, key, value) {
$rootScope.$apply(() => {
object[key] = value;
});
}
_delete(object, key) {
$rootScope.$apply(() => {
delete object[key];
});
}
}
React
class ObservableClient extends Client {
_observers = new Set();
subscribe(callback) {
this._observers.add(callback);
return () => {
this._observers.delete(callback);
};
}
_triggerObservers() {
for (const observer of this._observers) {
observer();
}
}
_set(object, key, value) {
object[key] = value;
this._triggerObservers();
}
_delete(object, key) {
delete object[key];
this._triggerObservers();
}
}
function useClientObserver(client, selector) {
React.useSyncExternalStore(
(callback) => client.subscribe(callback),
() => selector(client)
);
}
const MarkerInfo = ({ client, markerId }) => {
const marker = useClientObserver(client, (client) => client.markers[markerId]);
return (
<div>
Marker name: {marker?.name}
</div>
);
}
Keep in mind that React’s useSyncExternalStore
will rerender the component if the resulting object reference changes. This means one the one hand that you cannot use this example implementation on a higher up object of the client (such as client
itself or client.markers
), as their identity never changes, causing your component to never rerender. And on the other hand that you should avoid using it on objects created in the selector (such as returning [client.mapData.id, client.mapData.name]
in order to get multiple values at once), as it will cause your component to rerender every time the selector is called.