When an Unqork module runs inside Embedded UI, the module and the host page compete for control of the browser's address bar. The module's routing system expects to own the URL and update it as end-users navigate through module screens. Without intervention, those URL changes would push the host page to a different address, breaking the host application's routing and browser history. The navigation guard system resolves this conflict by intercepting URL change attempts from the module and keeping the host page's address bar stable throughout the session.
This reference covers how navigation guards work, how link interception operates, and how Shadow DOM and CSS isolation function in Embedded UI.
Why Navigation Guards Are Needed
Angular's internal routing system assumes it owns the browser's URL bar. In embedded mode, URL mutations must be suppressed so the host page's address remains unchanged. Without these guards, Angular would change the host page URL every time the embedded module navigates internally, breaking the host application.
$browser Decorator
The $browser service controls how Angular reads and sets the browser URL. In embed mode, this service is modified so URL changes from Angular routing never reach the host page's address bar.
Field: $browser.url() | Type: Angular service decorator
The $browser service is decorated during angular.module('unqorkEmbed').config(). All SET calls are silently ignored. GET calls pass through normally.
flowchart LR
A["Angular calls $browser.url(newUrl)"] --> B{"Embed mode?"}
B -->|"Yes"| C["No-op (URL not changed)"]
B -->|"No"| D["Normal URL update"]
Document-Level Link Interception
A click event listener on document intercepts <a> tag clicks originating from inside the Shadow DOM. Links outside the Shadow DOM (in the host page) continue to work normally.
When an end-user clicks a link inside the embedded module:
- The default navigation is prevented.
- A
unqork-embed-navigateCustomEvent is dispatched onwindowwith thehrefinevent.detail.
The listener also blocks navigations where the href attribute starts with views/ or contains undefined—patterns that indicate Angular template paths or unresolved route expressions that would navigate the host page to an invalid URL if not intercepted.
Shadow DOM and CSS Isolation
Shadow DOM creates a private boundary around the embedded module's DOM and CSS. Styles from the host page cannot reach inside, and styles from the module cannot leak out.
Shadow DOM Structure
<unqork-app> ← Custom element (light DOM)
#shadow-root (open) ← Shadow boundary
<link rel="stylesheet" href="..."> ← Injected CSS
<style>...</style> ← Scoped overrides
<div id="centauri-root"> ← Angular bootstrap target
<div ui-view> ← UI-Router viewport
...module content...
</div>
</div>
The outermost <unqork-app> element is a placeholder in the host page's regular DOM. The shadow root attached to it creates a boundary: styles and DOM from the host page cannot reach inside, and styles from the module cannot affect the host page. Stylesheets are injected at the top of the shadow root so all styling scopes to the module. Below the stylesheets, the module's content tree mounts and renders module screens as end-users navigate.
CSS Loading
Styles are loaded into the Shadow DOM from two sources:
| Source | endpoint | Description |
|---|---|---|
| Platform styles | /fbu/uapi/styles |
Returns the environment's base CSS, including Unqork platform styles. |
| Customer CSS | Environment-specific | Returns the customer-branded stylesheet for that environment. |
Both are fetched and injected as <link> or <style> elements inside the Shadow DOM. They do not affect the host page.
Scoped Overrides
Certain styles are overridden for the embed context. For example, the workflow loader uses position: absolute instead of position: fixed to stay contained in the custom element boundary.
Limitations
Some browser behaviors cannot be fully controlled by the navigation guard system. Review these limitations before building integrations that depend on browser history or fixed-position layouts.
| Limitation | Description |
|---|---|
position: fixed |
elements styled position: fixed inside Shadow DOM are relative to the viewport, not the shadow host. Modals and loaders must use position: absolute to stay in the embedded module's boundary. |
| Font-face declarations | @font-face rules from the host page do not cross the Shadow DOM boundary. Fonts must be re-declared inside the shadow or loaded globally on the host page. |
| Global event listeners | window.addEventListener is shared across the shadow boundary. events registered on window inside the embedded module are visible to the host page. |
| Browser back and forward buttons | Navigation guards block URL changes, so browser history is not updated. The back and forward buttons affect the host page history, not the embedded module. |
| Deep linking | The embedded module always starts fresh. Deep linking into a specific module state is not supported. |
Changelog
| Date | Change |
|---|---|
| 2026-06-15 | Renamed "Link Interception" to "Document-Level Link Interception"; added step-by-step explanation of link click behavior and CustomEvent dispatch. |
| 2026-06-10 | Expanded introduction paragraph to clarify the URL conflict between the embedded module and host page; added explanatory paragraph following the Shadow DOM structure code block. |
| 2026-06-09 | Editorial pass: renamed heading "The Problem" to "Why Navigation Guards Are Needed"; added introductory descriptions to $browser Decorator, Shadow DOM and CSS Isolation, and Limitations sections; expanded table descriptions for non-experts. |
| 2026-04-21 | Initial publication. |