Skip to content

Commit 3694ac0

Browse files
serpentbladeclaude
andcommitted
docs(angular): live-compile the SearchInput example in for-angular-shops
Replaces the hand-written illustrative Rozie source + "structurally equivalent" claim with live rozie-codegen blocks pointing at the canonical examples/SearchInput.rozie. Two fences: - \`\`\`rozie-src SearchInput — inlines the real .rozie source verbatim - \`\`\`rozie-out SearchInput angular — compiles it through @rozie/core on every docs build, embeds the verbatim Angular emit Effect: the page can never drift from the compiler. If the Angular emitter ever regresses on this example, the docs build fails before the page renders. Also rewrites the hand-written "what an Angular dev typically writes today" sample to match SearchInput.rozie's actual API (placeholder / minLength / autofocus inputs, search + clear outputs, viewChild for the input ref, Subject + debounceTime for the 300ms input throttle, afterNextRender for the autofocus). Authentic side-by-side comparison of the same component, two ways to write it — same prop shape, same event surface, same behavior. Links to the working consumer at examples/consumers/angular-analogjs/src/app/AppComponent.ts which imports the same SearchInput.rozie through @analogjs/vite-plugin-angular and runs it inside a real Application Builder bundle. Verified: \`pnpm --filter @rozie/docs build\` green (the rozie-out Angular fence triggers a live compile of SearchInput.rozie on every build, fails the build if any error diagnostic is emitted). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 474025f commit 3694ac0

1 file changed

Lines changed: 76 additions & 55 deletions

File tree

docs/guide/for-angular-shops.md

Lines changed: 76 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -17,116 +17,137 @@ your existing Angular app as a standalone component. Nothing else changes.
1717

1818
### Side by side — a debounced search input
1919

20-
Hand-written Angular standalone component:
20+
This is the canonical `examples/SearchInput.rozie` file — the same one
21+
used as a working consumer in
22+
[`examples/consumers/angular-analogjs/`](https://github.com/One-Learning-Community/rozie.js/tree/main/examples/consumers/angular-analogjs/src),
23+
and the same one the [SearchInput example page](/examples/search-input)
24+
shows compiled to all six targets. The Angular output below is generated
25+
on every docs build by passing the Rozie source through the live
26+
compiler — it cannot drift.
27+
28+
#### What an Angular dev typically writes today
2129

2230
```ts
23-
// SearchInput.ts (hand-written Angular)
24-
import { Component, signal, computed, effect, input, output, DestroyRef, inject } from '@angular/core';
31+
// SearchInput.ts (hand-written Angular standalone component)
32+
import {
33+
Component, ElementRef, ViewEncapsulation,
34+
computed, effect, inject, input, output, signal, viewChild,
35+
DestroyRef, afterNextRender,
36+
} from '@angular/core';
37+
import { FormsModule } from '@angular/forms';
2538
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
2639
import { Subject, debounceTime } from 'rxjs';
2740

2841
@Component({
2942
selector: 'rz-search-input',
3043
standalone: true,
44+
imports: [FormsModule],
3145
template: `
32-
<input
33-
[value]="query()"
34-
(input)="onInput($any($event.target).value)"
35-
[placeholder]="placeholder()"
36-
/>
37-
@if (query().length > 0) {
38-
<button (click)="clear()">×</button>
39-
}
46+
<div class="search-input">
47+
<input
48+
#inputEl
49+
type="search"
50+
[placeholder]="placeholder()"
51+
[ngModel]="query()"
52+
(ngModelChange)="onInput($event)"
53+
(keydown.enter)="onSearch()"
54+
(keydown.escape)="onClear()"
55+
/>
56+
@if (query().length > 0) {
57+
<button class="clear-btn" (click)="onClear()" aria-label="Clear">×</button>
58+
} @else {
59+
<span class="hint">{{ minLength() }}+ chars</span>
60+
}
61+
</div>
4062
`,
4163
styles: [`
42-
input { padding: 0.5rem; border: 1px solid #ddd; }
43-
button { margin-left: 0.5rem; }
64+
.search-input { display: inline-flex; align-items: center; gap: 0.25rem; }
65+
input { padding: 0.25rem 0.5rem; }
66+
.clear-btn { background: none; border: none; cursor: pointer; font-size: 1.25rem; }
67+
.hint { color: rgba(0, 0, 0, 0.4); font-size: 0.85em; }
4468
`],
4569
})
4670
export class SearchInput {
4771
placeholder = input<string>('Search…');
72+
minLength = input<number>(2);
73+
autofocus = input<boolean>(false);
4874
search = output<string>();
75+
clear = output<void>();
4976

5077
protected query = signal('');
51-
private debouncer = new Subject<string>();
78+
protected isValid = computed(() => this.query().length >= this.minLength());
79+
protected inputEl = viewChild<ElementRef<HTMLInputElement>>('inputEl');
80+
5281
private destroyRef = inject(DestroyRef);
82+
private debouncer = new Subject<string>();
5383

5484
constructor() {
5585
this.debouncer.pipe(
5686
debounceTime(300),
5787
takeUntilDestroyed(this.destroyRef),
58-
).subscribe(v => this.search.emit(v));
88+
).subscribe(() => this.onSearch());
89+
90+
afterNextRender(() => {
91+
if (this.autofocus()) this.inputEl()?.nativeElement?.focus();
92+
});
5993
}
6094

6195
protected onInput(value: string) {
6296
this.query.set(value);
6397
this.debouncer.next(value);
6498
}
6599

66-
protected clear() {
100+
protected onSearch() {
101+
if (this.isValid()) this.search.emit(this.query());
102+
}
103+
104+
protected onClear() {
67105
this.query.set('');
68-
this.search.emit('');
106+
this.clear.emit();
69107
}
70108
}
71109
```
72110

73-
The same component in Rozie:
111+
#### The same component in Rozie
74112

75-
```rozie
76-
<rozie name="SearchInput">
113+
```rozie-src SearchInput
114+
```
77115

78-
<props>
79-
{
80-
placeholder: { type: String, default: 'Search…' },
81-
}
82-
</props>
83-
84-
<data>
85-
{ query: '' }
86-
</data>
87-
88-
<script>
89-
const clear = () => { $data.query = ''; $emit('search', '') }
90-
</script>
91-
92-
<template>
93-
<input
94-
:value="$data.query"
95-
@input.debounce(300)="$data.query = $event.target.value; $emit('search', $data.query)"
96-
:placeholder="$props.placeholder"
97-
/>
98-
<button r-if="$data.query.length > 0" @click="clear">×</button>
99-
</template>
100-
101-
<style>
102-
input { padding: 0.5rem; border: 1px solid #ddd; }
103-
button { margin-left: 0.5rem; }
104-
</style>
105-
106-
</rozie>
116+
#### What the Rozie compiler emits for the `angular` target
117+
118+
This block is compiled live by `@rozie/core` on every docs build. The
119+
source above is the input; the code below is the verbatim output.
120+
121+
```rozie-out SearchInput angular
107122
```
108123

109-
The Rozie compiler emits an Angular standalone component that's
110-
**structurally equivalent** to the hand-written version above — the same
111-
`signal()` / `input()` / `output()` / `inject(DestroyRef)` / `effect()`
112-
machinery, with `@for` / `@if` blocks in the template. You don't see it
124+
The Rozie source is roughly a third the size and reads top-to-bottom. The
125+
compiled output is **structurally equivalent** to the hand-written
126+
version — the same `signal()` / `input()` / `output()` / `viewChild()` /
127+
`inject(DestroyRef)` / `ngAfterViewInit` machinery, with `@for` / `@if`
128+
blocks and `FormsModule`-backed `r-model` lowering. You don't see it
113129
during authoring. You import it normally:
114130

115131
```ts
116132
// app.component.ts
117133
import { Component } from '@angular/core';
118-
import { SearchInput } from './SearchInput'; // compiled from SearchInput.rozie
134+
import SearchInput from './SearchInput.rozie'; // .rozie → standalone component
119135

120136
@Component({
121137
standalone: true,
122138
imports: [SearchInput],
123-
template: `<rz-search-input (search)="onSearch($event)" />`,
139+
template: `<rozie-search-input (search)="onSearch($event)" />`,
124140
})
125141
export class AppComponent {
126142
onSearch(query: string) { /**/ }
127143
}
128144
```
129145

146+
The working consumer lives at
147+
[`examples/consumers/angular-analogjs/src/app/AppComponent.ts`](https://github.com/One-Learning-Community/rozie.js/tree/main/examples/consumers/angular-analogjs/src/app/AppComponent.ts)
148+
— it imports the same `SearchInput.rozie` shown above and runs the
149+
component inside a real Angular 19+ Application Builder bundle.
150+
130151
## What you don't have to write anymore
131152

132153
Rozie quietly does the Angular ceremony you'd otherwise hand-roll:

0 commit comments

Comments
 (0)