Main-thread scripts
A main-thread script restyles an element on every touch or scroll event without running Angular's change detection. Use one for drags, scroll-linked effects and other work that must keep up with a finger. It's a function that runs on the main thread with a direct handle to the native element (see Glossary).
What they add
Your template event handlers, such as (bindtap), already run on the main thread, inside the native event (see Threads). So a main-thread script doesn't save a trip between threads. It gives you four other things:
- No change detection per event. A
(bindtouchmove)handler marks its component for checking, so Angular runs change detection after every move. A main-thread script changes the element itself, and Angular does no work. - An element handle. In a main-thread script,
event.currentTargetandevent.targetareMainThread.Elementobjects withsetStyleProperty(),setAttribute(),querySelector(),animate()andinvoke(). In a template handler,$event.currentTargetis plain data. - Results from element methods.
invoke()calls a UI method and returns a Promise of its result, such as the element's size and position. - A way to the background thread.
runOnBackground()calls a function on the background thread, which has APIs the main thread lacks.
Bind a script to an event
mainThreadFn()registers the function on the main thread and returns a handle.LynxMainThreadEventprovides themainThreadBind*andmainThreadCatch*inputs. Bind them with square brackets: you pass the handle in, you don't listen with( ).catchinstead ofbindstops the event bubbling, as with template events (see Handling taps and events).- Style changes are batched. AngularLynx draws them once, after your function returns. Property names are kebab-case and values are strings.
The inputs cover tap, touchstart, touchmove, touchend, mousedown, mousemove, mouseup, longpress and scroll. animationstart, animationend, transitionend and layoutchange have a bind form only. The LynxMainThreadEvent reference lists them all.
A desktop browser fires mouse events, not touch events. To drag in the web preview, bind mainThreadBindmousemove as well. Phones never fire it, so binding both is safe.
Use the 'main thread' directive
Instead of calling mainThreadFn(), make 'main thread' the first statement of the function:
The build wraps the function in mainThreadFn() and adds the import. Both forms behave the same.
- Arrow functions and function expressions with a block body are wrapped.
'main-thread'works too. - Function declarations and class methods aren't. Write
const name = () => { 'main thread'; … }instead.
Define scripts at module level
Put main-thread scripts at the top level of a file, as above. A script is registered once, when its file loads. Both copies of your app load files in the same order, so the script gets the same ID on both threads. A call from the background thread needs that ID.
A class property, such as readonly fade = (event) => { 'main thread'; … }, works for a [mainThreadBind*] input and can read this. Avoid it anyway: every component instance registers a new function that's never released, so a component you create many times stays in memory. Its ID can also differ between the threads.
A script runs in the main-thread copy of your file. It reads that copy's module-level variables. Data from the background thread reaches it only as copied JSON arguments.
Keep state between events
A MainThreadRef holds a value that your scripts keep between events, such as where a drag started:
Each thread has its own copy of a ref, and the copies aren't synced. On the main thread, a ref works like a module-level variable that's labeled as script state.
Call the background thread
runOnBackground() runs a backgroundFn() on the background thread and returns a Promise of its result:
runOnBackground()is a global. Call it from main-thread code: a main-thread script, or a template handler. It doesn't exist on the background thread, so don't call it from a constructor orngOnInit, which run on both.- Define
backgroundFn()at module level, for the same ID reason as main-thread scripts. - Arguments and the return value are copied as JSON.
- Return a value, not a Promise. A returned Promise arrives as
{}. So you can't fetch data this way yet (see Network requests).
Call element methods uses this to reach lynx.createSelectorQuery().
Call a script from your code
LynxMainThread.runOnMainThread(handle, ...args) calls a main-thread script and returns a Promise of its result:
- From a template handler, which is already on the main thread, the script runs right away.
- From the background thread, the call crosses threads and the arguments are copied as JSON.
- Not from code that runs on both threads. A call in a constructor,
ngOnInitoreffect()runs the script twice: once from each copy of your app.
To get from the main thread to the background thread, use runOnBackground(), not runOnMainThread().
A main-thread script changes native elements behind Angular's back.
- A later binding update to the same property, such as
[style.opacity], overwrites your change. - When an element is removed and shown again, by an
@ifor by leaving and returning to a route, AngularLynx rebuilds it from Angular's state. Your changes are gone.
Use scripts for visual effects that follow a gesture. Keep anything that must last in signals.
Limitations
- No dependency injection. A module-level script has no injection context, so
inject()doesn't work in it. - A
MainThreadRefdoesn't cross threads. Passed torunOnMainThread()from the background thread, it arrives as a plain{ _wvid }object, not as the ref. - Measure with
invoke('boundingClientRect'). Elements also have an untypedgetBoundingClientRect(), but itsleftandtoparen't the element's position.
Related
mainThreadFnandLynxMainThreadEventMainThreadRef,LynxMainThreadandbackgroundFn- Threads: where your code runs
- Gestures, for pan, pinch and other gesture recognizers
- Main-thread example
