Skip to content

feat(implementations): scaffold Angular Web SDK reference implementation#310

Open
David Nalchevanidze (nalchevanidze) wants to merge 119 commits into
mainfrom
NT-3348
Open

feat(implementations): scaffold Angular Web SDK reference implementation#310
David Nalchevanidze (nalchevanidze) wants to merge 119 commits into
mainfrom
NT-3348

Conversation

@nalchevanidze

@nalchevanidze David Nalchevanidze (nalchevanidze) commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Mission

Establish a minimal but production-shaped Angular reference implementation that future SDK
integration work can build on top of. The goal mirrors react-web-sdk: a properly structured
Angular project from day one so that adding @contentful/optimization-web integration later is
filling in the blanks, not restructuring.

This PR is scaffolding only — no SDK calls yet.

Created files

File Purpose
src/main.ts App entry point — bootstraps Angular with the app config
src/index.html HTML shell with <app-root> mount point
src/app/app.ts Root component — renders Hello World; future home of the SDK provider wrapper
src/app/app.config.ts Application providers — error listeners, zoneless change detection, router
src/app/app.routes.ts Empty route list, placeholder for future page-level routing
src/styles.css Minimal global reset
angular.json Angular CLI build and dev-server config, port 3000 to match other implementations
tsconfig.json Single strict TypeScript config (no split until tests exist)
package.json Angular 22 deps and standard implementation scripts (dev, build, typecheck, clean, serve:mocks)
pnpm-workspace.yaml Isolates the impl from the root lockfile; overrides SDK deps to local pkgs/ tarballs
.env.example Mock-safe environment variable defaults
AGENTS.md Local rules and commands for this implementation
README.md Quick start, project structure, related links

Notable decisions

  • Zoneless change detectionzone.js removed entirely; uses provideZonelessChangeDetection
  • Single tsconfig.json — the tsconfig.app.json split is only useful once a test config exists
  • No provideClientHydration — CSR only, no SSR planned
  • No provideHttpClient — nothing to fetch yet

🤖 Generated with Claude Code

Adds implementations/angular-web-sdk — an Angular 22 CSR skeleton that
serves a Hello World page and establishes the project structure for future
@contentful/optimization-web integration.

## What was added

- angular.json — Angular CLI build config (@angular/build:application),
  dev server on port 3000 (matching other implementations), production +
  development configurations, analytics disabled, packageManager set to pnpm
- package.json — Angular 22 deps, standard implementation scripts (dev,
  build, typecheck, clean, serve:mocks, launch), no zone.js (zoneless)
- tsconfig.json — single config (no tsconfig.app.json split; no tests yet),
  strict mode, ES2022 target, moduleResolution: bundler
- pnpm-workspace.yaml — sharedWorkspaceLockfile: false plus SDK tarball
  overrides so the implementation resolves local pkgs/ tarballs
- src/main.ts — bootstrapApplication(App, appConfig)
- src/app/app.ts — minimal standalone root component (Hello World)
- src/app/app.config.ts — provideBrowserGlobalErrorListeners,
  provideZonelessChangeDetection, provideRouter; no zone.js, no
  provideClientHydration (CSR only), no provideHttpClient (too early)
- src/app/app.routes.ts — empty Routes array
- src/index.html — HTML shell with <app-root>
- src/styles.css — minimal global reset
- scripts/launch-reference-app.sh — one-shot launcher: builds SDK pkgs,
  installs deps, starts mock server in background, starts Angular dev server
  in foreground, cleans up on exit
- AGENTS.md — local rules and commands
- README.md — standard repo header, quick start, manual setup, project
  structure, related links

## Key decisions

- Zoneless change detection (provideZonelessChangeDetection) — zone.js
  removed from deps and angular.json polyfills entirely
- Single tsconfig.json instead of the Angular CLI default split
  (tsconfig.json + tsconfig.app.json) — the split only pays off when a
  tsconfig.spec.json for tests is also present
- standalone: true omitted from App — redundant in Angular 19+, all
  components are standalone by default
- No SDK integration yet — added only when the public Angular surface is ready
- Root package.json gets implementation:angular-web-sdk shortcut matching
  the pattern of other implementations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t script

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Not present in other implementation READMEs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add CONFIG InjectionToken with hardcoded mock defaults (grows per feature)
- Wire app routes: / → HomeComponent, /page-two → PageTwoComponent
- Extract app root template to app.html, add nav links and router-outlet
- Add stub HomeComponent and PageTwoComponent
- Update REQUIREMENTS.md with feature-oriented progress table
- Remove .env.example (no longer needed with hardcoded defaults)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…sses

- Rename home.component.ts → home.ts, class HomeComponent → Home
- Rename page-two.component.ts → page-two.ts, class PageTwoComponent → PageTwo
- Update app.routes.ts imports accordingly
- Add naming and modern Angular patterns rules to AGENTS.md
- Add implementation:angular-web-sdk shortcut to root package.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add design system to styles.css: nav, typography, card, entry grid, utility panel
- Add RouterLinkActive to nav with active class and exact match on home link

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add @contentful/optimization-web dependency (resolved via pnpm-workspace override)
- Add SDK config fields to CONFIG token (clientId, sdkEnvironment, urls, logLevel)
- Create Optimization service with module-level singleton and graceful error handling
- Wire page tracking via Router NavigationEnd — fires on every route change incl. initial load
- Inject Optimization in App root to force instantiation on startup
- Mark features 1 and 2 as done in REQUIREMENTS.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…(feature 18)

- Add contentful and @contentful/rich-text-types dependencies
- Add Contentful fields to CONFIG token (spaceId, token, environment, host, basePath)
- Create ContentfulClient service wrapping CDA with sdk.withOptimizationLocale()
- Add types/contentful.ts — typed entry skeleton and RichTextDocument
- Add utils/type-guards.ts — isRecord and isEntry helpers
- Mark feature 18 as done in REQUIREMENTS.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…scenarios (features 3-6)

- Add OptimizationResolver service wrapping sdk.resolveOptimizedEntry with baseline fallback
- Add ContentEntry component with auto-tracking (data-ctfl-* attributes) and manual tracking
  (enableElement/clearElement via effect + OnDestroy) and all three click scenarios
  (direct/descendant/ancestor)
- Wire Home page to fetch all entries on init and pass selectedOptimizations down so entries
  re-resolve on profile changes
- Bridge SDK plain-function subscribe protocol to RxJS Observable for toSignal compatibility

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ual differentiation

- Add variant/baseline badge and green left-border accent to entry cards
- Restructure home page with page header, stat panel, and section headers
- Tune spacing: tighter entry-grid gap, larger section margins, bottom padding

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ide (features 9-10)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eature 17)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…(feature 15)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ew event (feature 19)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n REQUIREMENTS.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tionMode to entry.ts, remove unused resolver.with()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ublic surface, move isRecord to app-local util

- Rename sdk/utils/index.ts → sdk/utils.ts (no index indirection)
- Remove isRecord, fromSdkObservable, NgContentfulOptimizationInstance,
  isMergeTagEntry, EntryMeta, ResolvedData, ResolvedEntryView from SDK exports
- Add app/utils.ts with isRecord for app-layer use
- Use typed entry.sys.contentType.sys.id directly in isNestedContentEntry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Follows Angular's provide* convention — hides the token and makes
app.config.ts read as a list of feature setup calls.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…token + mapping fn

inject moves into the helper; the mapping function stays pure and
app.config.ts no longer needs to call inject directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rom public exports

Token is an implementation detail; provideContentfulOptimizationConfig
is the only public entry point consumers need.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…zationConfig from public exports

Neither is used outside the SDK itself.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…provideContentfulOptimizationConfig

provideContentfulOptimizationConfig now takes a plain object.
CONFIG injection token was a needless middleman between env vars
and the SDK config.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…NTRIES to FIXTURES

Hardcoded entry IDs are seed data, not configuration. Remove the now-empty
config/ directory.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ructuring

Destructuring import.meta.env breaks at runtime — Vite replaces
import.meta.env.FOO statically but does not define import.meta.env
as a real object.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… takeUntilDestroyed in EventLog

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ose resolveMergeTag via NgContentfulEntry, wrap trackView on NgContentfulOptimization

- NgContentfulOptimizationResolver removed from public SDK index
- RichText now uses NgContentfulEntry.resolveMergeTag instead of injecting resolver directly
- NgContentfulOptimization.trackView() wraps sdk.trackView so page-two never touches raw sdk

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d guards

SDK construction is synchronous in Angular DI — a thrown error fails
bootstrap entirely, so sdk can never be undefined at runtime. Remove the
try/catch, error field, and all sdk === undefined fallback branches.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…visibility

Remove trivial setConsent/identify/trackView/reset wrappers from NgContentfulOptimization —
callers use optimization.sdk directly, which is clearer in a reference implementation.
Move compound reset+page call into ControlPanel where it belongs as app-specific logic.

Mark isLive, lockSnapshot, clearSnapshot private on NgContentfulEntry; resolveEntry
private on NgContentfulOptimizationResolver; drop unused globalLiveUpdates$ observable.
Simplify NgContentfulClient by collapsing client+localizedClient into one resolvedClient field
and removing the stale sdk !== undefined guard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… entry fetching

Use Angular resource() in Home and PageTwo to eliminate OnInit, manual loading signals,
and void async fire-and-forget. Extract FIXTURES.home.ids so the fetch ID list is defined
once in fixtures.ts rather than derived inline at call sites. Both pages now use a Map
for consistent entry lookup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Static methods are inaccessible from Angular templates; replace with a
protected readonly field pointing to FIXTURES.home.clickScenarios.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ce loader

Mirror the home page pattern: derive ids from module-level constants so
the fetch list is defined once and auto and manual stay single-ID references.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ding aliases

trackConversion was passed as a bare callback losing `this` context; arrow field
fixes the binding. Remove redundant loading signal aliases — templates reference
entries.isLoading() directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Never imported or used anywhere; resolveMergeTag on NgContentfulEntry
covers the use case directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…to NgContentfulEntry

The resolver was only ever used by NgContentfulEntry. Merge resolveEntry,
resolveMergeTag, and their helpers directly into NgContentfulEntry and
delete the resolver service.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NgContentfulLiveUpdates is app-level UI plumbing, not an SDK concern.
Move it from src/sdk/services/ to src/app/ and remove it from the
sdk public index.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ices, trim config

Move NgContentfulLiveUpdates to src/app/services/ where it belongs as app-level
UI plumbing. Remove tag/toggleSelector from NgContentfulOptimizationConfig — they
were app-specific DOM selectors with no SDK meaning. Drop the now-unused
togglePreviewPanel method and its helper. previewPanel.nonce stays in SDK config
as it is passed to the SDK panel attachment call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…entfulEntry

NgContentfulEntry no longer injects NgContentfulLiveUpdates. It accepts
a plain Signal<boolean> for liveUpdates via with(). ContentEntry computes
isLive (preview panel + global toggle + per-entry override) and passes it
down — matching the pattern of every other SDK implementation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rename title to framework-neutral, trim Prerequisites, collapse verbose
verification steps, and remove Angular/React implementation references.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Align REQUIREMENTS.md with jira ticket structure: rename entry resolution
and tracking sections, nest click scenarios under attribute-based tracking,
update consent wording. Add jira.md with categorised acceptance criteria.
Update home.html tracking section descriptions to clarify tracking-only scope.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tion

Implement OnDestroy to unsubscribe the router subscription, call sdk.destroy()
to flush queues and stop entry interaction tracking, and reset the module-level
singleton so the SDK can be recreated cleanly if needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Preview panel overrides are handled entirely by the panel itself — no app
code needed. Blocked events are already absent from eventStream by design.
Remove all TODO markers and clarify explanations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ions

Reorganise into Core setup, Tracking, Entry resolution, Live updates, and
Content sections. Merge verification steps into description paragraphs.
Remove Prerequisites section and jira.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ine confirm steps

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants