feat(web-sdk_angular): Web SDK + Angular reference implementation [NT-3465]#316
Open
David Nalchevanidze (nalchevanidze) wants to merge 204 commits into
Open
feat(web-sdk_angular): Web SDK + Angular reference implementation [NT-3465]#316David Nalchevanidze (nalchevanidze) wants to merge 204 commits into
David Nalchevanidze (nalchevanidze) wants to merge 204 commits into
Conversation
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>
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>
…ut (features 13-14) - Add RichTextRenderer component with renderer-map approach (no switch/enum comparison) - Add MergeTagPipe and isMergeTagEntry type guard for inline merge tag resolution - Wire rich text + merge tag detection into ContentEntry with hasMergeTag computed - Add rich text, merge tag, manual, and click scenario badges to entry cards - Reorganise card header: IDs (base/var/exp) on left, badges on right, both top-aligned Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add NestedContentEntry and NestedContentItem components with forwardRef self-recursion - Each level resolves its own SDK variant independently via OptimizationResolver - Home page routes nestedContent entries to NestedContentEntry instead of ContentEntry - Add nested badge style and indented border for child levels - Reduce badge font size for a more compact card header Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…features 11+16) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…MENTS.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Contributor
|
lint is failing btw |
…anges Remove contentfulLocales and withOptimizationLocale() following the SDK's locale ownership shift (#317). App now passes locale directly to CDA requests. Rename NgContentfulLiveUpdates to NgLiveUpdates. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Client.loadEntries 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>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…GENTS.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…iles Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ass names to components Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…omponents Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…esolved tracking Remove generateMeta helper, inline meta object directly in resolved computed. Drop isRecord/typeof guards for selectedOptimization in favour of optional chaining. Track mergeTagResolved accurately via the resolver callback rather than hasMergeTag check; true if any merge tag resolved, false if callback fired but none resolved, undefined if no merge tag nodes present. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…m @contentful/rich-text-types Replace unknown-typed resolve function with typed RichTextNode union (Block | Inline | Text), removing redundant isRecord guards. Use satisfies for structural compatibility without unsafe casts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove resolveRichTextMergeTags wrapper by inlining into resolveEntryMergeTags. Drop redundant isEntry guard in _variant since _entry is already typed as Entry | undefined. Keep isRecord(node.data) only where required by the unknown-typed recursive resolver. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… service Only used in entry.ts, so no reason to export it from utils. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
EntryMeta was only ever accessed via .meta, never used standalone. Flatten the fields directly onto ResolvedEntryView and update all callsites. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tContentfulEntry function Drop @Injectable class, providers array, and post-construction .with() setup in favour of a plain inject-pattern function that takes signals upfront and returns the resolved signal directly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eUpdates to isLive Order members per Angular style guide across EntryCard, ControlPanel, and NgLiveUpdates. Remove redundant inline section comments from ControlPanel. Rename injectContentfulEntry param liveUpdates -> isLive since it receives the resolved boolean signal, not the raw input. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…redundant guards Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…edEntryView to ResolvedEntry and align field names Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…g boolean signal Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lTracking Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ffect Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ng boolean Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tly in template Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s CSS modifier directly Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… and experienceId→optimizationId Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements a complete client-side SPA reference implementation of the Angular adapter SDK integration path using public APIs only, against shared mocks and fixtures. All behavior runs entirely on the client — no SSR, no server-side rendering, no hydration.
Constraint (REFREQ-20): No local shims, casts, or adapter logic mask a missing SDK capability. All SDK interaction goes through public package APIs.
What this implements
Core setup
sdk.destroy()on teardown/) and Page Two (/page-two); page event fires on every SPA route change; SDK not re-initialised across navigations; event history preservedidentify()andreset()transition profile state; both persist across reloads via SDK storagesdk.withOptimizationLocale()before every fetch'boolean'flag via the SDK flag state API; auto-emits a flag-view event on access; returnsundefinedfor anonymous sessions, resolves afteridentify()Tracking
eventStreamin real time; view/hover rows update in place; events blocked by consent are absent; history persists across routesdata-ctfl-*DOM attributes observed by the SDK; view, click, hover events fire after consent; stop on withdrawal. Click scenarios: direct, descendant, and ancestor all emitcomponent_clickenableElement; emits view events only; no click or hover eventsLive updates
Content
[Merge Tag]fallback when no profile is activeNotable decisions
zone.jsremoved; usesprovideZonelessChangeDetectionwith signals andtoSignal()throughoutinject()for DI,input()/output()for component I/O,@if/@forcontrol flow syntaxhome.tsnothome.component.ts;OptimizationnotOptimizationServiceinstanceandattachmentStartedprevent double-init;ngOnDestroyresets both for clean teardownprovideClientHydration, no SSR🤖 Generated with Claude Code