We are currently grappling with a unique challenge in our Angular application that we inherited. The issue revolves around a system where a digital input is meant to control both the backlight and the displayed page. Essentially, if the digital input signal is activated, the backlight should illuminate, allowing for normal user interaction with the application.
However, when the digital input signal is inactive, the application should present a blank screen and turn off the backlight entirely, eliminating any possibility of blind user interaction with an unintended page.
The backend process manages the backlight functionality and communicates information to the frontend (both processes run on the same device) via Angular routing. Nonetheless, the frontend retains full autonomy over what content is displayed on the web page.
Our current observation reveals that toggling the digital input signal rapidly sometimes results in the backlight turning on while displaying a blank page. This occurrence prompts us to consider whether there might be a race condition within the event delivery mechanism operating through the router.
In terms of code implementation, we have an effect that monitors the digital input signal status from the backend indirectly, resembling the following snippet:
@Effect({ dispatch: false })
receiveDigitalInputEvent$ = this.actions.pipe(
ofType(AppActionTypes.RECEIVE_DIGITAL_INPUT),
map((action: AppActions.ReceiveDigitalInput) => action.payload),
withLatestFrom(this.store.pipe(select(getRouterState))),
tap(([data, routerState]) => {
const currUrl = routerState ? routerState.state.url : '';
const { isDigitalInputSet, someOtherStuff } = data;
this.forceNavigateIfNeeded(isDigitalInputSet, currUrl);
})
);
forceNavigateIfNeeded(isDigitalInputSet: boolean, currUrl) {
if (isDigitalInputSet && currUrl.endsWith('/blank')) {
this.router.navigateByUrl('/welcome');
else if (! isDigitalInputSet && ! currUrl.endsWith('/blank')) {
this.router.navigateByUrl('/blank');
}
}
Essentially, upon receiving a digital input event:
- If the input signal is active and the screen is blank, the application navigates to the welcome screen.
- If the input signal is deactivated and the current screen is not the blank one, the application switches to the blank screen mode.
Albeit functioning correctly under normal circumstances, the fast transitions seem to trigger anomalies.
While my familiarity with Angular internals is limited, I suspect that incoming events may be queued for routing delivery as part of the system's mechanism (especially considering the backend pushes data to the frontend using web sockets).
Upon inspecting the Angular source code, I noted that navigateByUrl
operates using promises, potentially enabling the code to proceed to the next message queue item before the page change takes effect. Seeking validation or correction of my assumptions, I suggest the following scenario:
- The digital input deactivates, prompting the backend to dispatch an "OFF" message to the frontend and disable the backlight.
- The digital input reactivates swiftly, signaling the backend to send an "ON" message back to the frontend and re-enable the backlight.
- The frontend processes the "OFF" message and, since it detects being on a non-blank page, triggers a
navigateByUrl('/blank')
operation which initiates a promise. - Before the aforementioned promise fulfills (and the
routerState.state.url
transitions from non-blank to blank), processing of the "ON" message begins. Consequently, with an active "ON" message but a stale non-blank page, theforceNavigateIfNeeded()
function remains dormant. - The ongoing
navigateByUrl('/blank')
promise concludes, resulting in the actual transition to a blank page.
This sequence reproduces the problematic situation described previously, characterized by a blank screen illuminated by the backlight.
Is such behavior feasible within the Angular/Typescript framework? It appears to hinge on the rapid queuing and execution of incoming messages in a single-threaded fashion alongside potential delays in front-end navigation timing and page state updating.
I am seeking guidance on the plausibility of this scenario and possible solutions to address it efficiently.
My conjecture is that pausing within forceNavigateIfNeeded()
to await promise completion may not be advisable, but I remain unsure about alternative approaches.