Tutorial: Build a todo app
Build a todo list that runs in the web preview and on your phone. You'll add tasks, tick them off, delete them and scroll through them.
This is the finished app. You'll build it in seven steps and check the result after each one.
What you'll learn
- How to build a screen from Lynx elements such as
<view>,<text>and<input> - Where Tailwind and CSS layout differ on Lynx
- How to render a list from a signal with
@forand@empty - How to handle taps and typing with
(bindtap),(catchtap)and(bindinput) - How to make a long list scroll with
<scroll-view>
Before you start
You need the app from Quick Start, created with Tailwind. Keep npm start running and the web preview open. If you set up Lynx Explorer, keep it open too. Each save reloads both.
You'll edit one file: src/app/app.ts. If you said no to Tailwind, add it with npx ng generate @blotch/angular-lynx:add-tailwind, then restart npm start.
1. Show a title
Replace everything in src/app/app.ts with this:
A Lynx app has no HTML. Its templates use Lynx elements instead, and on a phone each one becomes a native UI control.
- A
<view>is a box, like a<div>. A<text>holds text, like a<span>. - Text renders only inside
<text>. Text placed directly in a<view>doesn't show up, and you get no error. imports: [LYNX_ELEMENTS]lets Angular type-check Lynx elements and their attributes. Every component that uses them needs it.
The starter's <router-outlet> and <scroll-view> are gone. This app has one screen, and <scroll-view> comes back in step 7. See Elements and layout for every element.
You should see: two lines of plain text, "Todos" above "2 remaining".
2. Style it with Tailwind
Replace the template with this:
Tailwind classes work as on the web. The Lynx preset in tailwind.config.ts limits them to the CSS that Lynx supports. See Tailwind CSS.
Layout is where Lynx differs from the browser:
- A
<view>stacks its children vertically by default. Lynx's defaultdisplayislinear, and there's noblockorinline. Children stretch to the full width. - A row needs
flex. The card has bothflexandflex-row.flex-rowalone does nothing, because it only setsflex-direction, which linear layout ignores. flex-1lets the label fill the space between the square and the ✕.
CSS on Lynx lists every difference from browser CSS.
You should see: a bold "Todos" heading, a gray count and a white card. The card holds an empty square, "Learn AngularLynx" and a red ✕, side by side.
3. List the todos from a signal
At the top of the file, import signal and add a Todo type:
Give the class a todos signal:
In the template, replace the card with a @for loop over todos():
@for and @empty work as in web Angular. The template reads the signal with todos(), so Angular re-renders the list whenever the signal changes. The field is readonly because it always holds the same signal, even as the signal's value changes.
You should see: two cards, "Learn AngularLynx" and "Build an app". To check the empty state, change the array to [], and you'll see 🎉 "All done!". Put the two todos back before you continue. The count is still fixed text until step 6.
4. Toggle and delete with taps
Replace the card inside @for with this one:
Add two methods to the class, below todos:
Lynx calls a click a tap, and its event names start with bind or catch:
bindtapbubbles: the tap also reaches the parent elements, as DOM events do.catchtapstops the tap at this element, like callingstopPropagation().
The ✕ sits inside the card, and the card toggles on tap. With catchtap, tapping ✕ deletes the todo without also running the card's toggle(). See Handling taps and events.
update() returns a new array instead of changing the old one, so the signal sees a new value. These handlers only change signals, which is always safe. Some work, such as router navigation, has to wait until the handler ends: see the setTimeout rule.
You should see: tapping a card turns its square indigo with a ✓ and strikes through the label. Tap again to undo. Tap ✕ to delete a todo, and delete both to see "All done!".
5. Add todos from a text field
Add this row to the template, between the count and @for:
Then add draft, #nextId, onInput() and addTodo() to the class, around todos:
<input> is Lynx's text field. It fires bindinput on every keystroke, instead of the DOM input event.
- The text is in
event.detail.value, notevent.target.value. Angular types$eventas a plainEvent, soonInput()casts it toCustomEvent<{ value: string }>to readdetail. [value]="draft()"writes the signal back into the field. That's howaddTodo()clears it.#nextIdis an ES private field. It's plain state that the template never reads, so it doesn't need to be a signal.
For validation and larger forms, use Angular's reactive forms instead. See Forms.
You should see: a text field and an indigo "Add" button side by side. Type a task and tap "Add": it joins the bottom of the list, and the field clears.
6. Count what's left
Add computed to the @angular/core import:
Add a remaining signal to the class, below #nextId:
Replace the fixed "2 remaining" text with it:
computed() makes a signal from other signals. It recalculates when todos changes, and Angular re-renders the count. AngularLynx apps are zoneless, so signals are what tell Angular to update the screen. See Signals and change detection.
You should see: the count drop when you tick a todo, and rise when you add one.
7. Scroll a long list
Add a dozen todos. The ones past the bottom of the screen are out of reach, because the <view> doesn't scroll.
Replace the outer <view> with a <scroll-view>, and wrap the content in a <view> that keeps the padding:
- Only
<scroll-view>and<list>scroll. Lynx draws each element as a native UI control. On iOS and Android, only dedicated scroll controls scroll, and a<view>isn't one. That's whyoverflow: scrolldoes nothing. - A
<scroll-view>needs a fixed height, hereh-screen. Without one, it grows to fit its content and has nothing to scroll. scroll-orientation="vertical"sets the direction.
For hundreds of rows, use <list> instead. It reuses rows as they scroll out of view. See <scroll-view> for its other attributes.
You should see: the same app, with a list that scrolls. Drag it on the phone, or scroll it with your mouse in the web preview.
Your src/app/app.ts now matches the file in the preview at the top of this page. If something looks wrong, compare the two.
Next steps
You've built a complete Lynx screen with Angular. Where to go from here:
- Coming from web Angular: what else differs from a browser app, and which Angular features work.
- Routing and navigation: add more screens and move between them.
- UI Components: ready-made buttons, inputs, dialogs and more, copied into your app by the
@blotch/dolanCLI. - Run on iOS and Android: turn your app into a real iOS or Android app.
