Add Observables


In this document, we setup the Tuple Observable from VortexJS. The Field and Office services use this to request and receive data updates from the Logic service.

We’ll use the term “devices” interchangeably with Field/Office.

This is a one directional data flow once the initial request has been made, the Server will send updates to the Field/Office with out the Field/Office services polling for it.

In the example setup, the Field/Office Services proxy Observable requests/responses between the Logic Service and Field/Office devices. The Proxy on the Field/Office service is aware of all the Field/Office App devices that want to observe the data, the Logic Service only knows that the Field/Office Service is observing the data.


The Field/Office devices don’t and can’t talk directly to the Logic Service.

The TupleDataObservableHandler class provides the “observable” functionality, It receives request for data by being sent a TupleSelector. The TupleSelector describes the Tuple type and some conditions of the data the observer wants.


  1. Instant and efficient data updates, data immediately sent to the devices with out the devices congesting bandwidth with polls.


  1. There is no support for updates.


In this document, our plugin will observe updates made to the table created in Adding a StringInt Table via the admin web app.

This is the order:

  1. Add the Observable scaffolding for the project.
  2. Add the Logic Service side Tuple Provider
  3. Tell the Admin TupleLoader to notifyDeviceInfo the Observable when it makes updates.
  4. Add a new Angular component to observe and display the data.

Server Service Setup

Add Package tuple_providers

The tuple_providers python package will contain the classes that generate tuple data to send via the observable.

Create the peek_plugin_tutorial/_private/server/tuple_providers package, with the commands

mkdir peek_plugin_tutorial/_private/server/tuple_providers
touch peek_plugin_tutorial/_private/server/tuple_providers/

Add File

The creates the Observable, registers the tuple providers (they implement TuplesProviderABC)

TupleProviders know how to get the Tuples.

Create the file peek_plugin_tutorial/_private/server/ and populate it with the following contents.

from vortex.handler.TupleDataObservableHandler import TupleDataObservableHandler

from peek_plugin_tutorial._private.PluginNames import tutorialFilt
from peek_plugin_tutorial._private.PluginNames import tutorialObservableName

def makeTupleDataObservableHandler(ormSessionCreator):
    """" Make Tuple Data Observable Handler

    This method creates the observable object, registers the tuple providers and then
    returns it.

    :param ormSessionCreator: A function that returns a SQLAlchemy session when called

    :return: An instance of :code:`TupleDataObservableHandler`

    tupleObservable = TupleDataObservableHandler(

    # Register TupleProviders here

    return tupleObservable

Edit File

We need to update, it will initialise the observable object when the Plugin is started.

Edit the file peek_plugin_tutorial/_private/logic/

  1. Add this import at the top of the file with the other imports:

    from .TupleDataObservable import makeTupleDataObservableHandler
  2. Add this line after the docstring in the start() method:

    tupleObservable = makeTupleDataObservableHandler(self.dbSessionCreator)

The observable for the Logic Service is setup now. We’ll add a TupleProvider later.

Field Service Setup

Add File

The creates the Observable Proxy. This class is responsible for proxying observable data between the devices and the Logic Service.

It reduces the load on the logic service, providing the ability to create more client (Field/Office, for example) services to scale Peek out for more users, or speed up responsiveness for remote locations.

TupleProviders know how to get the Tuples.

Create the file peek_plugin_tutorial/_private/field/ and populate it with the following contents.

from peek_plugin_base.PeekVortexUtil import peekServerName
from peek_plugin_tutorial._private.PluginNames import tutorialFilt
from peek_plugin_tutorial._private.PluginNames import tutorialObservableName
from vortex.handler.TupleDataObservableProxyHandler import TupleDataObservableProxyHandler

def makeDeviceTupleDataObservableProxy():
    return TupleDataObservableProxyHandler(observableName=tutorialObservableName,

Edit File

We need to update, it will initialise the observable proxy object when the Plugin is started.

Edit the file peek_plugin_tutorial/_private/field/

  1. Add this import at the top of the file with the other imports:

    from .DeviceTupleDataObservableProxy import makeDeviceTupleDataObservableProxy
  2. Add this line after the docstring in the start() method:


Field App Setup

Now we need to edit the Angular module in the field-app and add the providers:

Edit File tutorial.module.ts

Edit the tutorial.module.ts Angular module for the tutorial plugin to add the provider entry for the Observer service.

Edit the file peek_plugin_tutorial/_private/-app/tutorial.module.ts:

  1. Add the following imports:

    // Import the required classes from VortexJS
    import {
    } from "@synerty/vortexjs";
    // Import the names we need for the
    import {
    } from "@peek/peek_plugin_tutorial/_private";
  2. After the imports, add this function

    export function tupleDataObservableNameServiceFactory() {
        return new TupleDataObservableNameService(
            tutorialObservableName, tutorialFilt);
  3. Finally, add this snippet to the providers array in the @NgModule decorator

    TupleDataObserverService, TupleDataOfflineObserverService, {
        provide: TupleDataObservableNameService,
        useFactory: tupleDataObservableNameServiceFactory

It should look similar to the following:


import {
} from "@synerty/vortexjs";

import {
} from "@peek/peek_plugin_tutorial/_private";


export function tupleDataObservableNameServiceFactory() {
    return new TupleDataObservableNameService(
        tutorialObservableName, tutorialFilt);

    providers: [
        TupleDataObserverService, TupleDataOfflineObserverService, {
            provide: TupleDataObservableNameService,
export class TutorialModule {


At this point, all of the observable setup is done. It’s much easier to work with the observable code from here on.

Add Tuple Provider

Add File

The Observable will be sent a TupleSelector that describes the data the sender wants to subscribe to.

Tuple Selectors have two attributes :

  1. A name, the name/type of the Type
  2. And a selector, this allows the subscriber to observe a filtered set of tuples.

The loads data from the database, converts it to a VortexMsg and returns it.

A VortexMsg is a bytes python type. It’s a serialised and compressed payload. A Payload is the Vortex transport container.

Create the file peek_plugin_tutorial/_private/server/tuple_providers/ and populate it with the following contents.

from txhttputil.util.DeferUtil import deferToThreadWrap
from typing import Union

from twisted.internet.defer import Deferred

from vortex.Payload import Payload
from vortex.TupleSelector import TupleSelector
from vortex.handler.TupleDataObservableHandler import TuplesProviderABC

from import StringIntTuple

class StringIntTupleProvider(TuplesProviderABC):
    def __init__(self, ormSessionCreator):
        self._ormSessionCreator = ormSessionCreator

    def makeVortexMsg(self, filt: dict,
                      tupleSelector: TupleSelector) -> Union[Deferred, bytes]:
        # Potential filters can be placed here.
        # val1 = tupleSelector.selector["val1"]

        session = self._ormSessionCreator()
            tasks = (session.query(StringIntTuple)
                # Potentially filter the results
                # .filter(StringIntTuple.val1 == val1)

            # Create the vortex message
            return Payload(filt, tuples=tasks).makePayloadEnvelope().toVortexMsg()


Edit File

Edit the python module, and register the new StringIntTupleProvider tuple provider.

Edit the file peek_plugin_tutorial/_private/server/

  1. Add the following imports:

    from .tuple_providers.StringIntTupleProvider import StringIntTupleProvider
    from import StringIntTuple
  2. Find the line # Register TupleProviders here and add this line after it:


Admin Update Notify

This section notifies the observable when an admin updates a StringIntTuple via the Admin service/UI.

This setup of the admin editing data, and having it change on Field/Office devices won’t be the only way the observable is notified, however, it is a good setup for admin configurable items in dropdown lists, etc.

Edit File

Edit the file to accept the tupleObservable argument and notifyDeviceInfo the observable when an update occurs.

Edit the file peek_plugin_tutorial/_private/server/admin_backend/

Add the import:

from vortex.TupleSelector import TupleSelector
from vortex.handler.TupleDataObservableHandler import TupleDataObservableHandler
from vortex.sqla_orm.OrmCrudHandler import OrmCrudHandlerExtension

Insert the following class, after the class definition of class __CrudHandeler

class __ExtUpdateObservable(OrmCrudHandlerExtension):
    """ Update Observable ORM Crud Extension

    This extension is called after events that will alter data,
    it then notifies the observer.

    def __init__(self, tupleDataObserver: TupleDataObservableHandler):
        self._tupleDataObserver = tupleDataObserver

    def _tellObserver(self, tuple_, tuples, session, payloadFilt):
        selector = {}
        # Copy any filter values into the selector
        # selector["lookupName"] = payloadFilt["lookupName"]
        tupleSelector = TupleSelector(StringIntTuple.tupleName(),
        return True

    afterUpdateCommit = _tellObserver
    afterDeleteCommit = _tellObserver

Update the instance of handler class


def makeStringIntTableHandler(dbSessionCreator):


def makeStringIntTableHandler(tupleObservable, dbSessionCreator):

In the makeStringIntTableHandler method, insert this line just before the return handler

handler.addExtension(StringIntTuple, __ExtUpdateObservable(tupleObservable))

Edit File admin_backend/

Edit admin_backend/ to take the observable parameter and pass it to the tuple provider handlers.

Edit file peek_plugin_tutorial/_private/server/admin_backend/

Add the import:

from vortex.handler.TupleDataObservableHandler import TupleDataObservableHandler

Add the function call argument:


def makeAdminBackendHandlers(dbSessionCreator):


def makeAdminBackendHandlers(tupleObservable: TupleDataObservableHandler,

Pass the argument to the makeStringIntTableHandler(...) method:


yield makeStringIntTableHandler(dbSessionCreator)


yield makeStringIntTableHandler(tupleObservable, dbSessionCreator)

Edit File

We need to update, to pass the new observable

Edit the file peek_plugin_tutorial/_private/server/, Add tupleObservable to the list of arguments passed to the makeAdminBackendHandlers() method:




       makeAdminBackendHandlers(tupleObservable, self.dbSessionCreator))

The tuple data observable will now notifyDeviceInfo its observers when an admin updates the StringInt data.

Add Field View

Finally, lets add a new component to the Field screen.

Add Directory string-int

The string-int directory will contain the Angular component and views for our stringInt page.

Create the diretory peek_plugin_tutorial/_private/field-app/string-int with the command:

mkdir peek_plugin_tutorial/_private/field-app/string-int

Add File string-int.component.mweb.html

The string-int.component.mweb.html file is the web app HTML view for the Angular component string-int.component.ts.

This is standard HTML with Angular directives.

Create the file peek_plugin_tutorial/_private/field-app/string-int/string-int.component.mweb.html and populate it with the following contents.

<div class="container">
    <Button class="btn btn-default" (click)="mainClicked()">Back to Main</Button>

    <table class="table table-striped">
            <tr *ngFor="let item of stringInts">

Add File string-int.component.ts

The string-int.component.ts is the Angular Component that will be another route within the Tutorial plugin.

Create the file peek_plugin_tutorial/_private/field-app/string-int/string-int.component.ts and populate it with the following contents.

import {Component} from "@angular/core";
import {Router} from "@angular/router";
import {StringIntTuple, tutorialBaseUrl} from "@peek/peek_plugin_tutorial/_private";
import { NgLifeCycleEvents } from "@synerty/peek-plugin-base-js"
import {
} from "@synerty/vortexjs";

    selector: 'plugin-tutorial-string-int',
    templateUrl: 'string-int.component.mweb.html',
export class StringIntComponent extends NgLifeCycleEvents {

    stringInts: Array<StringIntTuple> = [];

    constructor(private tupleDataObserver: TupleDataObserverService,
                private router: Router) {

        // Create the TupleSelector to tell the obserbable what data we want
        let selector = {};
        // Add any filters of the data here
        // selector["lookupName"] = "brownCowList";
        let tupleSelector = new TupleSelector(StringIntTuple.tupleName, selector);

        // Setup a subscription for the data
        let sup = tupleDataObserver.subscribeToTupleSelector(tupleSelector)
            .subscribe((tuples: StringIntTuple[]) => {
                // We've got new data, assign it to our class variable
                this.stringInts = tuples;

        // unsubscribe when this component is destroyed
        // This is a feature of NgLifeCycleEvents
        this.onDestroyEvent.subscribe(() => sup.unsubscribe());


    mainClicked() {


Edit File tutorial.module.ts

Edit the tutorial.module.ts, to include the new component and add the route to it.

Edit peek_plugin_tutorial/_private/field-app/tutorial.module.ts:

  1. Add the StringIntComponent import with the imports at the top of the file:

    import {StringIntComponent} from "./string-int/string-int.component";
  2. Insert the following as the first item in array pluginRoutes:

        path: 'stringint',
        component: StringIntComponent
  3. Add the StringIntComponent to the declarations in the @NgModule decorator:

    declarations: [...,
        ], ...
  4. Add the following to the Routes section:

            path: 'stringint',
            component: StringIntComponent

    so it looks like below:

    export const pluginRoutes: Routes = [
            path: 'stringint',
            component: StringIntComponent

At this point field is all setup, we just need to add some navigation buttons.

Edit File tutorial.component.mweb.html

Edit the web HTML view file, tutorial.component.mweb.html and insert a button that will change Angular Routes to our new component.

Edit file peek_plugin_tutorial/_private/field-app/tutorial.component.mweb.html, Insert the following just before the last closing </div> tag:

<Button class="btn btn-default"
        [routerLink]="['/peek_plugin_tutorial/stringint']">My Jobs >


  1. Open the field web app
  2. Tap the Tutorial app icon
  3. tap the “String Ints” button
  4. Expect to see the string ints data.
  5. Update the data from the Admin service UI
  6. The data on the field app will immediately change.

Offline Observable

The Synerty VortexJS library has an TupleDataOfflineObserverService, once offline storage has been setup, (here Add Offline Storage), the offline observable is a dropin replacement.

When using the offline observable, it will:

  1. Queue a request to observe the data, sending it to the field service
  2. Query the SQL db in the browser/mobile device, and return the data for the observer. This provides instant data for the user.

When new data is sent to the the observer (Field/Office service) from the observable (Field service), the offline observer does two things:

  1. Notifies the subscribers like normal
  2. Stores the data back into the offline db, in the browser / app.

Edit File string-int.component.ts

TupleDataOfflineObserverService is a drop-in replacement for TupleDataObserverService.

Switching to use the offline observer requires two edits to string-int.component.ts.

Edit file peek_plugin_tutorial/_private/field-app/string-int/string-int.component.ts.

Add the import for the TupleDataOfflineObserverService:

import {TupleDataOfflineObserverService} from "@synerty/vortexjs";

Change the type of the tupleDataObserver parameter in the component constructor, EG,


constructor(private tupleDataObserver: TupleDataObserverService, ...) {


constructor(private tupleDataObserver: TupleDataOfflineObserverService, ...) {

That’s it. Now the String Int data will load on the device, even when the Vortex between the device and the field service is offline.

Add More Observables

This was a long tutorial, but the good news is that you don’t have to repeat all this every time. Here are the steps you need to repeat to observe more data, altering them to suit of course.

Create the Python tuples, either Adding a StringInt Table or Add File

Add the TypeScript tuples, Edit File plugin_package.json.

Add a Logic service tuple provider, Add Tuple Provider

Then, add the Field, Office or Admin side, add the views and Angular component, Add Field View.