Pass data from your native app

Your native app can hand values to Angular. InitData goes to one LynxView when the native app opens your bundle. GlobalData is shared by every LynxView, and can change while the app runs. Data processors reshape InitData before Angular sees it.

InitDataGlobalData
SentWhen the native app opens the bundle, with updates laterAny time, to every LynxView
Typical valuesA user ID, the item a screen should showThe theme, the language, safe-area insets
Read it withLynxInitDataLynxGlobalData

This page involves native code, like the rest of Embedding.

Read InitData

src/app/app.ts
import { Component, computed, inject } from '@angular/core';
import { LYNX_ELEMENTS, LynxInitData } from '@blotch/angular-lynx';

@Component({
  selector: 'app-root',
  imports: [LYNX_ELEMENTS],
  template: `<text>User: {{ userId() }}</text>`,
})
export class App {
  readonly #initData = inject(LynxInitData);
  readonly userId = computed(
    () => this.#initData.initData()['userId'] ?? 'guest',
  );
}
  • initData() is a signal holding the whole object.
  • Give each field a fallback. initData() is {} when no native app sent any, such as in Lynx Explorer or the web preview.
  • No setup. LynxInitData is provided in the root injector.

Read GlobalData

src/app/banner.ts
import { Component, computed, inject } from '@angular/core';
import { LYNX_ELEMENTS, LynxGlobalData } from '@blotch/angular-lynx';

@Component({
  selector: 'app-banner',
  imports: [LYNX_ELEMENTS],
  template: `<text>Platform: {{ platform() }}</text>`,
})
export class Banner {
  readonly #globalData = inject(LynxGlobalData);
  readonly platform = computed(
    () => this.#globalData.globalData()['platform'] ?? 'unknown',
  );
}

globalData() is an empty object when the native app sends no GlobalData. For the theme and the language, use the services in Theme and language instead.

Send the data from native code

Your native app sends InitData when it loads the bundle into a LynxView, and GlobalData whenever it likes.

On iOS, in Swift:

let initData = LynxTemplateData(dictionary: ["userId": "user_42"])
lynxView.updateGlobalProps(with: LynxTemplateData(dictionary: ["theme": "Dark"]))
lynxView.loadTemplate(fromURL: bundleUrl, initData: initData)

// Later:
lynxView.updateData(with: ["userId": "user_43"])

On Android, in Kotlin:

lynxView.updateGlobalProps(mapOf<String, Any>("theme" to "Dark"))
lynxView.renderTemplateUrl(bundleUrl, mapOf<String, Any>("userId" to "user_42"))

// Later:
lynxView.updateData(mapOf<String, Any>("userId" to "user_43"))
  • Lynx calls GlobalData "global props", hence updateGlobalProps.
  • Updates show up right away. updateData merges the fields you send into the current InitData, and initData() changes to match. updateGlobalProps does the same for globalData(), and for the theme and language services.
  • Set GlobalData before loading the bundle, so it's there when your app starts.
  • Newer Lynx APIs load bundles with a LynxLoadMeta object instead. Lynx's Using Data from Host Platform guide shows them.

Reshape InitData with data processors

A data processor is a function that turns the data the native app sends into the shape your app wants. Register processors in main.ts, before bootstrapApplication():

src/main.ts
import {
  bootstrapApplication,
  registerDataProcessors,
} from '@blotch/angular-lynx';
import { App } from './app/app';
import { appConfig } from './app/app.config';

registerDataProcessors({
  // Runs on InitData unless the native code names another processor.
  // Here it renames the native app's `user_id` field.
  defaultDataProcessor: (raw) => ({
    userId: String(raw['user_id'] ?? 'guest'),
  }),
  // Run only when the native code names them.
  dataProcessors: {
    refreshFeed: (data) => ({ ...data, feedRefreshed: true }),
  },
});

bootstrapApplication(App, appConfig);
  • Register before bootstrapping, so the first data that arrives is already processed.
  • Named processors run when native code names one in an update: updateData(with: data, processorName: "refreshFeed") in Swift, or updateData(data, "refreshFeed") in Kotlin.
  • A processor that throws gives {} instead of crashing the app.

Fake the data while developing

While you develop in Lynx Explorer or the web preview, no native app sends your app its data. Fill in sample values yourself in main.ts, before bootstrapApplication():

src/main.ts
import { isDevMode } from '@angular/core';
import {
  bootstrapApplication,
  registerDataProcessors,
} from '@blotch/angular-lynx';
import { App } from './app/app';
import { appConfig } from './app/app.config';

// InitData: start from sample values, and let real ones win.
registerDataProcessors({
  defaultDataProcessor: (raw) =>
    isDevMode() ? { userId: 'user_42', ...raw } : raw,
});

// GlobalData: LynxGlobalData reads it when it's created.
if (isDevMode() && typeof lynx !== 'undefined') {
  const host = lynx as any;
  host.__globalProps = { theme: 'Dark', ...host.__globalProps };
}

bootstrapApplication(App, appConfig);
  • Fake InitData in a data processor. Lynx hands your app its InitData as the page starts, which replaces anything you set on lynx. The processor runs on that data first.
  • Set GlobalData before bootstrapping, because LynxGlobalData reads it when it's created.
  • isDevMode() is false in production builds, so the fake values never ship.
  • The spread keeps real values, if a native app does send some.

The data-flow example sets fake values the same way, in its src/main.ts.

What the generated apps pass

The apps ng build --platform makes are host apps too. They pass:

AppInitDataGlobalData
iOSNoneplatform ('ios'), theme ('Dark' or 'Light'), and the safe-area insets safeAreaTop, safeAreaBottom, safeAreaLeft, safeAreaRight
AndroidNoneNone

To pass anything else, eject or embed the app in your own.

Theme and language

Two GlobalData values have services of their own. Use them instead of reading globalData():

They apply those defaults and check the values for you. Have your native app send theme and appLocale under exactly those names.

Type the data

By default, initData() and globalData() are typed as Record<string, unknown>. Describe your fields with module augmentation:

src/lynx-data.d.ts
// This export makes the file a module, so the block below adds to
// @blotch/angular-lynx's types instead of replacing them.
export {};

declare module '@blotch/angular-lynx' {
  interface InitData {
    userId: string;
  }
  interface GlobalData {
    theme: 'Dark' | 'Light';
    appLocale: string;
  }
  // The data as native code sends it, before your data processor runs.
  interface InitDataRaw {
    user_id: string;
  }
}

Now initData().userId is a string, with no casting.