<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Jason Moody</title>
    <description>The latest articles on Forem by Jason Moody (@moodyjw).</description>
    <link>https://forem.com/moodyjw</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2531410%2Fdcb89291-f94e-47d0-95b5-7a2c9dbff33c.jpg</url>
      <title>Forem: Jason Moody</title>
      <link>https://forem.com/moodyjw</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/moodyjw"/>
    <language>en</language>
    <item>
      <title>Building a Portfolio That Actually Demonstrates Enterprise Skills - Part 3</title>
      <dc:creator>Jason Moody</dc:creator>
      <pubDate>Thu, 08 Jan 2026 14:05:00 +0000</pubDate>
      <link>https://forem.com/moodyjw/building-a-portfolio-that-actually-demonstrates-enterprise-skills-part-3-3ili</link>
      <guid>https://forem.com/moodyjw/building-a-portfolio-that-actually-demonstrates-enterprise-skills-part-3-3ili</guid>
      <description>&lt;h1&gt;
  
  
  Phase 2: The Invisible Architecture
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Core Services, Error Handling, and Authentication That Power Everything
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/moodyjw/building-a-portfolio-that-actually-demonstrates-enterprise-skills-part-2-47e1"&gt;previous article&lt;/a&gt;, I covered Phase 1, where I established the tooling and governance that set the rules of engagement for the entire project. With &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/eslint.config.mjs" rel="noopener noreferrer"&gt;ESLint&lt;/a&gt;, Prettier, &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/vitest.config.ts" rel="noopener noreferrer"&gt;Vitest&lt;/a&gt;, &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/playwright.config.ts" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt;, and &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/.github/workflows/ci.yml" rel="noopener noreferrer"&gt;CI/CD pipelines&lt;/a&gt; in place, the foundation was solid. Phase 2 builds on that foundation by implementing what I call the "invisible architecture," the singleton services, error handling, and authentication system that run underneath every feature without the user ever seeing them.&lt;/p&gt;

&lt;p&gt;This is the code that separates production applications from demos. Anyone can build a login form. The difference is what happens when the network fails, when sessions expire, when errors cascade, and when the application needs to recover gracefully. Phase 2 addresses all of these concerns.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/docs/specs/PHASE_2_CORE.md" rel="noopener noreferrer"&gt;Phase 2 specification&lt;/a&gt; breaks this work into four major areas: &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/src/app/core/config" rel="noopener noreferrer"&gt;typed environment configuration&lt;/a&gt;, &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/src/app/core/services" rel="noopener noreferrer"&gt;infrastructure services&lt;/a&gt; (logging, analytics, SEO, theming), &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/src/app/core/services" rel="noopener noreferrer"&gt;global error handling&lt;/a&gt;, and &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/src/app/core/auth" rel="noopener noreferrer"&gt;authentication&lt;/a&gt;. Each area follows the same principle: abstract what might change behind clean interfaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Philosophy: Abstractions That Enable Change
&lt;/h2&gt;

&lt;p&gt;Before diving into implementation details, it's worth explaining the guiding principle behind Phase 2's architecture: &lt;strong&gt;abstract everything that might change&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In enterprise software, requirements change constantly. The analytics vendor you use today might be replaced next quarter. The authentication provider might switch from a mock implementation to OAuth to SAML. The logging service might need to pipe errors to Sentry instead of the console. If these concerns are hardcoded throughout the application, changing them requires touching dozens of files and risking regressions everywhere.&lt;/p&gt;

&lt;p&gt;The solution is to define interfaces (contracts) and use &lt;a href="https://angular.dev/guide/di" rel="noopener noreferrer"&gt;Angular's dependency injection&lt;/a&gt; to swap implementations without changing consuming code. This is the &lt;a href="https://vugar-005.medium.com/angular-design-patterns-strategy-pattern-ace359ae77b3" rel="noopener noreferrer"&gt;Strategy Pattern&lt;/a&gt;, and it appears throughout Phase 2:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/src/app/core/auth/auth-strategy.interface.ts" rel="noopener noreferrer"&gt;&lt;code&gt;AuthStrategy&lt;/code&gt;&lt;/a&gt; defines what any authentication provider must do, whether it's a mock for demos or a real OAuth flow&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/src/app/core/services/analytics/analytics.provider.ts" rel="noopener noreferrer"&gt;&lt;code&gt;AnalyticsProvider&lt;/code&gt;&lt;/a&gt; defines what any analytics service must implement, whether it's console logging or Google Analytics&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/src/app/core/config/environment.token.ts" rel="noopener noreferrer"&gt;Environment configuration is injected via tokens&lt;/a&gt;, not imported directly
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────┐
│                    Application Code                     │
│         (Components, Services, Features)                │
└────────────────────┬────────────────────────────────────┘
                     │ depends on
                     ▼
         ┌───────────────────────┐
         │  Strategy Interface   │  (Contract: what must be done)
         │  - AnalyticsProvider  │
         │  - AuthStrategy       │
         └───────────┬───────────┘
                     │ implemented by (swappable)
        ┌────────────┴────────────┐
        ▼                         ▼
┌──────────────────┐    ┌──────────────────┐
│ Implementation A │    │ Implementation B │
│ (Mock/Console)   │    │ (Real/Google)    │
└──────────────────┘    └──────────────────┘
   Selected via             Selected via
   app.config.ts           app.config.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach adds a small amount of upfront complexity, but it pays dividends when requirements inevitably change. Swapping an analytics provider becomes a one-line change in &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/src/app/app.config.ts" rel="noopener noreferrer"&gt;&lt;code&gt;app.config.ts&lt;/code&gt;&lt;/a&gt; rather than a refactoring project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typed Environment Configuration
&lt;/h2&gt;

&lt;p&gt;The first piece of Phase 2 is typed environment configuration. Angular provides an environment file mechanism, but out of the box, there's no type safety. You can access &lt;code&gt;environment.anything&lt;/code&gt; without the compiler complaining, even if that property doesn't exist.&lt;/p&gt;

&lt;p&gt;The solution is a &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/src/environments/environment.type.ts" rel="noopener noreferrer"&gt;TypeScript interface&lt;/a&gt; that defines exactly what the environment must contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AppEnvironment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;appName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;production&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FeatureFlags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AnalyticsConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every environment file must satisfy this interface. The compiler catches typos, missing properties, and type mismatches at build time rather than runtime. The &lt;code&gt;readonly&lt;/code&gt; modifiers prevent accidental mutation of environment values, which should be immutable throughout the application lifecycle.&lt;/p&gt;

&lt;p&gt;Rather than importing the environment file directly (which creates tight coupling), the configuration is provided via an injection token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ENVIRONMENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;InjectionToken&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppEnvironment&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ENVIRONMENT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="nx"&gt;Figure&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Errors&lt;/span&gt; &lt;span class="nx"&gt;are&lt;/span&gt; &lt;span class="nx"&gt;caught&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;network&lt;/span&gt; &lt;span class="nx"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transformed&lt;/span&gt; &lt;span class="nx"&gt;into&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;friendly&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;logged&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;debugging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;provideEnvironment&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Provider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ENVIRONMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;useValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Services inject &lt;code&gt;ENVIRONMENT&lt;/code&gt; rather than importing the file directly. This makes testing trivial since you can provide a mock environment in tests without any module gymnastics. It also means the environment source could change in the future without touching any consuming code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure Services
&lt;/h2&gt;

&lt;p&gt;Phase 2 implements four infrastructure services that other parts of the application depend on. Each follows the same pattern: a clear interface, comprehensive logging, and thorough test coverage.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/src/app/core/services/logger" rel="noopener noreferrer"&gt;LoggerService: The Foundation of Observability&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The simplest service is also one of the most important. &lt;code&gt;LoggerService&lt;/code&gt; wraps &lt;code&gt;console&lt;/code&gt; methods with environment-aware behavior. In development, all log levels output normally. In production, &lt;code&gt;log()&lt;/code&gt; and &lt;code&gt;info()&lt;/code&gt; are suppressed to avoid console noise, while &lt;code&gt;warn()&lt;/code&gt; and &lt;code&gt;error()&lt;/code&gt; always output.&lt;/p&gt;

&lt;p&gt;This seems trivial, but it serves two purposes. First, it provides a single point where logging behavior can change. If the application later needs to send errors to Sentry or Datadog, only &lt;code&gt;LoggerService&lt;/code&gt; needs modification. Second, it prevents the common mistake of leaving debug logs in production code. With the logger checking the environment, debug information never reaches production users' consoles.&lt;/p&gt;

&lt;p&gt;The service is intentionally simple. Just 15 tests cover all the behavior. Sometimes the best code is the code that does exactly one thing well.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/src/app/core/services/analytics" rel="noopener noreferrer"&gt;AnalyticsService: The Strategy Pattern in Action&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Analytics demonstrates the Strategy Pattern clearly. The application needs to track events and page views, but the destination might be console logging during development, Google Analytics in production, or something else entirely.&lt;/p&gt;

&lt;p&gt;The solution starts with an interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AnalyticsProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;trackEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;EventProperties&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;trackPageView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;identify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;traits&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;EventProperties&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two implementations exist: &lt;code&gt;ConsoleAnalyticsProvider&lt;/code&gt; for development (logs everything via &lt;code&gt;LoggerService&lt;/code&gt;) and &lt;code&gt;GoogleAnalyticsProvider&lt;/code&gt; for production (integrates with GA4/gtag.js). The &lt;code&gt;AnalyticsService&lt;/code&gt; acts as a facade, delegating to whichever provider is configured.&lt;/p&gt;

&lt;p&gt;Switching providers is a one-line change in environment configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// or 'console'&lt;/span&gt;
  &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;measurementId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;G-XXXXXXXXXX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;provideAnalytics()&lt;/code&gt; function reads this configuration and provides the appropriate implementation. An app initializer ensures the provider is ready before the application bootstraps. A separate &lt;code&gt;withAnalyticsRouterTracking()&lt;/code&gt; provider automatically tracks page views on navigation events, so developers don't need to remember to add tracking to every route.&lt;/p&gt;

&lt;p&gt;The total test count across all analytics code: 55 tests covering the service, both providers, and the router integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/src/app/core/services/seo" rel="noopener noreferrer"&gt;SeoService: Beyond Basic Meta Tags&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;SEO in modern applications goes far beyond setting a page title. Search engines and social platforms expect specific meta tags, Open Graph properties, Twitter Card metadata, and JSON-LD structured data. &lt;code&gt;SeoService&lt;/code&gt; handles all of these concerns through a unified API.&lt;/p&gt;

&lt;p&gt;The primary method accepts a comprehensive configuration object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;seo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updatePageSeo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;How to Build Enterprise Angular Apps&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A comprehensive guide to building...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;angular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;enterprise&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;architecture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;canonicalUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/blog/enterprise-angular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;openGraph&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;article&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/assets/images/article-cover.jpg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;twitterCard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;summary_large_image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@architect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;jsonLd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Article&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;How to Build Enterprise Angular Apps&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;datePublished&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2025-01-15&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One method call sets the page title, description, keywords, canonical URL, Open Graph tags for Facebook and LinkedIn sharing, Twitter Card metadata, and JSON-LD structured data for rich search results. The service handles the DOM manipulation, creates and removes script tags for JSON-LD, and ensures proper cleanup on navigation.&lt;/p&gt;

&lt;p&gt;This service required 49 tests to cover all the combinations of metadata and edge cases around tag cleanup.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/src/app/core/services/theme" rel="noopener noreferrer"&gt;ThemeService: System Preferences and Persistence&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk3osdpj2z817g8lbitue.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk3osdpj2z817g8lbitue.webp" alt="A split-screen screenshot of your app. Left side: Light Mode. Right side: Dark Mode" width="748" height="816"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2: The theming engine supports multiple color schemes and high-contrast modes, persisted via LocalStorage.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Modern applications need to support light mode, dark mode, and often a "system" setting that follows the operating system preference. &lt;code&gt;ThemeService&lt;/code&gt; implements this with signals for reactive state management.&lt;/p&gt;

&lt;p&gt;The service tracks three things: the available themes, the currently active theme, and whether the theme was chosen explicitly or derived from system preferences. It watches the &lt;code&gt;prefers-color-scheme&lt;/code&gt; media query and updates automatically when system preferences change. User preferences persist to localStorage and restore on subsequent visits.&lt;/p&gt;

&lt;p&gt;Theme changes update a &lt;code&gt;data-theme&lt;/code&gt; attribute on the document element, which CSS custom properties can target. This approach is simpler and more performant than maintaining theme state in JavaScript and passing it through the component tree.&lt;/p&gt;

&lt;p&gt;I also included a script in the &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/src/index.html" rel="noopener noreferrer"&gt;&lt;code&gt;index.html&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;storageKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;defaultLight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light-default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;defaultDark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark-default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Check URL param first (for testing)&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;urlParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;themeParam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;themeParam&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;themeParam&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Check localStorage&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;stored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storageKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stored&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stored&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Fall back to system preference&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;prefersDark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(prefers-color-scheme: dark)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;prefersDark&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;defaultDark&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;defaultLight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;})();&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that this script lives in &lt;code&gt;index.html&lt;/code&gt;, &lt;strong&gt;outside&lt;/strong&gt; of the Angular bootstrap process. This ensures it runs instantly when the DOM parses, preventing the dreaded 'flash of white light' that occurs if you wait for Angular to load before applying a dark theme preference.&lt;/p&gt;

&lt;p&gt;The service includes 41 tests covering theme switching, system preference detection, persistence, and edge cases around initialization order.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/src/app/core/error-handling" rel="noopener noreferrer"&gt;Global Error Handling&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff0i2onhk4ox8s1fug0qa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff0i2onhk4ox8s1fug0qa.png" alt="Screenshot of Chrome console showing error logging" width="722" height="283"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 3: Errors are caught at the network layer, transformed into user-friendly messages, and logged with context for debugging.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Error handling is where production applications diverge most sharply from demos. Demos assume everything works. Production applications assume everything can fail and plan accordingly.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Error Handling Architecture
&lt;/h3&gt;

&lt;p&gt;The error handling system has three layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/src/app/core/error-handling/global-error-handler.ts" rel="noopener noreferrer"&gt;GlobalErrorHandler&lt;/a&gt;&lt;/strong&gt; catches any uncaught error in the Angular application&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/src/app/core/interceptors/http-error.interceptor.ts" rel="noopener noreferrer"&gt;HttpErrorInterceptor&lt;/a&gt;&lt;/strong&gt; catches and transforms HTTP errors into user-friendly messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/src/app/core/error-handling/error-notification.service.spec.ts" rel="noopener noreferrer"&gt;ErrorNotificationService&lt;/a&gt;&lt;/strong&gt; provides a way to display errors to users (and will integrate with the toast system in Phase 3)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;GlobalErrorHandler&lt;/code&gt; extends Angular's &lt;code&gt;ErrorHandler&lt;/code&gt; class. When an error occurs, it extracts useful information like the error message, stack trace, and component context, then logs it via &lt;code&gt;LoggerService&lt;/code&gt;. The handler could also report errors to an external service like Sentry. It distinguishes between different error types and handles each appropriately, whether they're HTTP errors, chunk loading failures, or standard JavaScript errors.&lt;/p&gt;

&lt;p&gt;One subtle but important detail: the handler checks for &lt;code&gt;Error.cause&lt;/code&gt;, a relatively new JavaScript feature that allows errors to wrap underlying causes. Many frameworks now use this pattern, so properly extracting the root cause is essential for useful error messages.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/src/app/core/interceptors/http-error.interceptor.ts" rel="noopener noreferrer"&gt;HTTP Error Interception&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;HttpErrorInterceptor&lt;/code&gt; transforms HTTP errors into consistent, user-friendly messages. A raw &lt;code&gt;HttpErrorResponse&lt;/code&gt; might contain technical details that are useless to end users. The interceptor maps status codes to human-readable messages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;400: "The request was invalid. Please check your input."&lt;/li&gt;
&lt;li&gt;401: "Your session has expired. Please log in again."&lt;/li&gt;
&lt;li&gt;403: "You don't have permission to access this resource."&lt;/li&gt;
&lt;li&gt;404: "The requested resource was not found."&lt;/li&gt;
&lt;li&gt;429: "Too many requests. Please try again in X seconds."&lt;/li&gt;
&lt;li&gt;500+: "A server error occurred. Please try again later."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For 429 (rate limiting) responses, the interceptor extracts the &lt;code&gt;Retry-After&lt;/code&gt; header and includes the wait time in the error message. For 401 responses, it triggers the authentication store's session expiration flow, redirecting users to login.&lt;/p&gt;

&lt;p&gt;The interceptor also logs errors appropriately. Network failures log differently than server errors, and each log entry includes relevant context like the HTTP method, URL, and status code.&lt;/p&gt;

&lt;p&gt;Between the &lt;code&gt;GlobalErrorHandler&lt;/code&gt;, &lt;code&gt;HttpErrorInterceptor&lt;/code&gt;, and &lt;code&gt;ErrorNotificationService&lt;/code&gt;, the error handling code has 77 tests covering every error type, edge case, and recovery scenario.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/src/app/core/auth" rel="noopener noreferrer"&gt;Authentication: The Most Critical System&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Authentication is Phase 2's most complex system and demonstrates several patterns working together. The design uses the Strategy Pattern to support different authentication mechanisms, NgRx SignalStore for state management, and functional route guards for access control.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/src/app/core/auth/auth-strategy.interface.ts" rel="noopener noreferrer"&gt;The Strategy Pattern for Auth&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Authentication requirements vary wildly between applications. Some use simple username/password flows. Others use OAuth with external providers. Some need SAML for enterprise SSO. A demo application might use mock authentication with localStorage.&lt;/p&gt;

&lt;p&gt;The solution is an interface that defines what any authentication strategy must do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AuthStrategy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoginCredentials&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;checkSession&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interface is intentionally minimal. It doesn't prescribe how authentication works, only that any strategy must be able to login with credentials, log out, and check whether a valid session exists. This abstraction allows the mock implementation used during development to be swapped for a real OAuth implementation later without changing any consuming code.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/src/app/core/auth/strategies/mock-auth.strategy.ts" rel="noopener noreferrer"&gt;MockAuthStrategy: Simulating Real-World Conditions&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The mock implementation isn't just a stub that returns success. It simulates real-world conditions that the application must handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Network latency&lt;/strong&gt;: Every operation includes an 800ms delay to simulate network round-trips&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Random failures&lt;/strong&gt;: In non-production environments, 10% of requests randomly fail to test error handling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session persistence&lt;/strong&gt;: Sessions store in localStorage with a JWT-like token structure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple user types&lt;/strong&gt;: Accepts "demo" (standard user) and "admin" (admin privileges) as usernames&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These behaviors ensure the application handles loading states, error recovery, and different user roles correctly. Without them, it's easy to build an app that works perfectly in development but falls apart when network conditions are imperfect.&lt;/p&gt;

&lt;p&gt;The mock strategy has 25 tests covering all login scenarios, logout behavior, session restoration, error simulation, and the differences between production and development modes.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/src/app/core/auth/auth.store.ts" rel="noopener noreferrer"&gt;AuthStore: Reactive State with SignalStore&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Authentication state lives in an NgRx SignalStore. The store manages the current user, authentication status, loading states, and error messages. It exposes computed signals for derived state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;withComputed&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;()?.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Guest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;isAdmin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;()?.&lt;/span&gt;&lt;span class="nx"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;hasRole&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserRole&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;()?.&lt;/span&gt;&lt;span class="nx"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;avatarUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;()?.&lt;/span&gt;&lt;span class="nx"&gt;avatarUrl&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/assets/images/default-avatar.svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These computed signals derive from the user state, so components can use &lt;code&gt;authStore.displayName()&lt;/code&gt; or &lt;code&gt;authStore.isAdmin()&lt;/code&gt; without manually checking for null users or extracting role information.&lt;/p&gt;

&lt;p&gt;The store uses &lt;code&gt;rxMethod&lt;/code&gt; from &lt;code&gt;@ngrx/signals&lt;/code&gt; for operations that involve observables. The &lt;code&gt;login&lt;/code&gt;, &lt;code&gt;logout&lt;/code&gt;, and &lt;code&gt;checkSession&lt;/code&gt; methods are reactive. They accept input (credentials or nothing), pipe through the authentication strategy, update state on success or failure, and handle side effects like navigation and logging.&lt;/p&gt;

&lt;p&gt;A critical detail: the store integrates with the HTTP error interceptor. When a 401 response occurs, the interceptor calls &lt;code&gt;authStore.handleSessionExpired()&lt;/code&gt;, which clears the user state and redirects to login. This ensures session expiration is handled consistently regardless of which HTTP request triggered it.&lt;/p&gt;

&lt;p&gt;The store has 40 tests covering state management, login/logout flows, session restoration, error handling, and the session expiration integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/src/app/core/auth/guards" rel="noopener noreferrer"&gt;Route Guards: Protecting Resources&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Three functional guards control route access:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;authGuard&lt;/strong&gt;: Requires authentication. Redirects to &lt;code&gt;/auth/login&lt;/code&gt; if not authenticated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;adminGuard&lt;/strong&gt;: Requires admin role. Redirects to &lt;code&gt;/forbidden&lt;/code&gt; if not admin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;guestGuard&lt;/strong&gt;: Requires no authentication. Redirects to &lt;code&gt;/&lt;/code&gt; if already authenticated (prevents logged-in users from seeing the login page).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The guards are simple functions that inject the &lt;code&gt;AuthStore&lt;/code&gt; and check the appropriate signal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authGuard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CanActivateFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AuthStore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createUrlTree&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/auth/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Functional guards are a newer Angular pattern that replaces class-based guards. They're simpler, more testable, and align with Angular's move toward functional APIs.&lt;/p&gt;

&lt;p&gt;The guards have 9 tests covering all access control scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/src/app/core/auth/auth.provider.ts" rel="noopener noreferrer"&gt;Provider Registration&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;All authentication components register through a single &lt;code&gt;provideAuth()&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;provideAuth&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;EnvironmentProviders&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;makeEnvironmentProviders&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AUTH_STRATEGY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;useClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MockAuthStrategy&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;provideAppInitializer&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AuthStore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;authStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function provides the mock strategy and registers an app initializer that checks for an existing session on startup. The app initializer runs before Angular bootstraps the application, ensuring authentication state is ready before any components render. When a user refreshes the page, the initializer restores their session from localStorage, creating a seamless experience. Users stay logged in across page refreshes without seeing loading states or being kicked to the login page. This detail matters for perceived performance and user experience.&lt;/p&gt;

&lt;p&gt;Swapping to a real authentication strategy later requires only changing which class is provided for &lt;code&gt;AUTH_STRATEGY&lt;/code&gt;. Everything else continues working unchanged: the store, guards, and error handling integration all remain the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Test Coverage Story
&lt;/h2&gt;

&lt;p&gt;Phase 2 concludes with 318 unit tests across all services and components. Here's the breakdown:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Tests&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Environment Config&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LoggerService&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AnalyticsService&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analytics Providers&lt;/td&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SeoService&lt;/td&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ThemeService&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GlobalErrorHandler&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HttpErrorInterceptor&lt;/td&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ErrorNotificationService&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MockAuthStrategy&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AuthStore&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth Guards&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App Component&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;318&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every service has tests covering its public API, edge cases, error conditions, and integration points. The CI pipeline enforces 85% coverage thresholds, and the actual coverage exceeds that in most areas.&lt;/p&gt;

&lt;p&gt;This test coverage isn't just a vanity metric. It provides confidence that the invisible architecture works correctly, enabling faster development in later phases. When a feature relies on &lt;code&gt;AuthStore&lt;/code&gt; or &lt;code&gt;SeoService&lt;/code&gt;, the feature developer doesn't need to worry about whether those services work. The tests already prove they do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Building Phase 2 reinforced several lessons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Abstractions require discipline.&lt;/strong&gt; It's tempting to skip the interface and code directly against an implementation when you "only have one implementation." But the interface forces you to think about the contract, makes testing easier, and enables future flexibility. The small upfront cost is worth it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error handling is a feature.&lt;/strong&gt; Users don't see error handling when it works. They just see an application that recovers gracefully. But they definitely notice when it fails. Investing in comprehensive error handling early prevents embarrassing production incidents later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mock implementations should be realistic.&lt;/strong&gt; A mock that always succeeds instantly doesn't prepare the application for real-world conditions. Adding delays, random failures, and edge cases to mocks surfaces issues during development when they're cheap to fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SignalStore simplifies reactive state.&lt;/strong&gt; Previous authentication implementations I've built used services with BehaviorSubjects, or full NgRx with actions and reducers. SignalStore hits a sweet spot. It's simpler than traditional NgRx but more structured than ad-hoc service state. The &lt;code&gt;rxMethod&lt;/code&gt; pattern for async operations is particularly elegant. It effectively replaces the 'Service with a Subject' pattern that has dominated Angular for a decade. Instead of manually managing &lt;code&gt;BehaviorSubject.next()&lt;/code&gt;, we just update the signal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Comprehensive testing enables fearless refactoring.&lt;/strong&gt; Several times during Phase 2, I refactored implementation details. I changed how errors were logged, restructured the auth flow, and adjusted how providers initialized. The tests caught regressions immediately, making refactoring safe rather than scary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The foundation enables everything else.&lt;/strong&gt; None of the work in Phase 2 is visible to end users, but all of it is essential for building features quickly and confidently. This invisible architecture is what makes the rest of the project possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;With Phase 2 complete, the application has a solid invisible architecture. Environment configuration is typed and injectable. Logging, analytics, SEO, and theming are abstracted behind clean interfaces. Errors are caught, transformed, and handled consistently. Authentication works with session persistence and route protection.&lt;/p&gt;

&lt;p&gt;The next article will cover &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/docs/specs/PHASE_3_DS.md" rel="noopener noreferrer"&gt;Phase 3, the Design System&lt;/a&gt;. That phase builds the visual component library with all the UI primitives that features will compose. With the core architecture in place, the design system can focus purely on presentation, knowing that services, state, and error handling are already solved.&lt;/p&gt;

&lt;p&gt;You can explore the complete Phase 2 implementation on GitHub: &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint" rel="noopener noreferrer"&gt;MoodyJW/angular-enterprise-blueprint&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Next in series: Phase 3 Deep Dive – The Design System and Component Library&lt;/em&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>architecture</category>
      <category>cicd</category>
      <category>portfolio</category>
    </item>
    <item>
      <title>Building a Portfolio That Actually Demonstrates Enterprise Skills - Part 2</title>
      <dc:creator>Jason Moody</dc:creator>
      <pubDate>Thu, 01 Jan 2026 18:13:27 +0000</pubDate>
      <link>https://forem.com/moodyjw/building-a-portfolio-that-actually-demonstrates-enterprise-skills-part-2-47e1</link>
      <guid>https://forem.com/moodyjw/building-a-portfolio-that-actually-demonstrates-enterprise-skills-part-2-47e1</guid>
      <description>&lt;h1&gt;
  
  
  Phase 1: Building the Enterprise Rig
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Tooling, Governance, and the Foundation That Makes Everything Else Possible
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/moodyjw/building-a-portfolio-that-actually-demonstrates-enterprise-skills-intro-5hlm"&gt;previous article&lt;/a&gt;, I outlined my approach to building a &lt;a href="https://moodyjw.github.io/angular-enterprise-blueprint/" rel="noopener noreferrer"&gt;portfolio that demonstrates enterprise-level skills&lt;/a&gt;. The core idea is simple: treat a personal project with the same rigor you would apply to production software. That starts with &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/docs/specs/PHASE_1_SETUP.md" rel="noopener noreferrer"&gt;Phase 1&lt;/a&gt;, which I call the "Enterprise Rig." This is the foundation of tooling, governance, and automation that everything else builds upon.&lt;/p&gt;

&lt;p&gt;Before writing a single line of feature code, I spent time establishing the rules of engagement. Linting, formatting, testing, documentation, and &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/.github/workflows" rel="noopener noreferrer"&gt;CI/CD pipelines&lt;/a&gt; all needed to be in place. The principle guiding this phase is &lt;a href="https://en.wikipedia.org/wiki/Shift-left_testing" rel="noopener noreferrer"&gt;"shift left,"&lt;/a&gt; meaning we catch (most) errors, style violations, and bad commits on the developer's machine before they ever reach the CI server or, worse, production.&lt;/p&gt;

&lt;p&gt;This article explains the decisions I made during Phase 1, why I made them, and what I learned along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workspace Configuration: Starting Strict
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://angular.dev/events/v21" rel="noopener noreferrer"&gt;Angular 21&lt;/a&gt; ships with sensible defaults, but enterprise projects need stricter constraints. The first decision was enabling every strictness option available.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/angular.json" rel="noopener noreferrer"&gt;&lt;code&gt;angular.json&lt;/code&gt;&lt;/a&gt;, strict mode is enabled globally. This turns on strict template type checking, strict property initialization, and other TypeScript strictness flags. These settings catch entire categories of bugs at compile time that would otherwise surface at runtime. The short-term cost is that you write slightly more explicit code. The long-term benefit is that refactoring becomes safer and the codebase stays maintainable as it grows.&lt;/p&gt;

&lt;p&gt;I also changed the component selector prefix from the default &lt;code&gt;app&lt;/code&gt; to &lt;code&gt;eb&lt;/code&gt; (Enterprise Blueprint). This prevents naming collisions if someone integrates these components into another project, and it makes it immediately clear which components belong to this codebase when reading templates.&lt;/p&gt;

&lt;p&gt;For styling, I chose SCSS over plain CSS. The decision was practical: SCSS offers variables, nesting, and mixins that make maintaining a design system significantly easier. When you are building a &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/src/app/shared/components" rel="noopener noreferrer"&gt;component library with multiple themes&lt;/a&gt;, plain CSS becomes unwieldy quickly. Not to mention, SCSS is the de facto standard for Angular projects.&lt;/p&gt;

&lt;p&gt;Package management uses npm with engine constraints in &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/package.json" rel="noopener noreferrer"&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/a&gt;. The project requires Node 20+ and npm 10+. This prevents developers from accidentally running the build with outdated tooling that might produce subtly different results. Version mismatches across a team are a common source of "works on my machine" problems, and engine constraints eliminate that class of issues entirely. I highly recommend using &lt;a href="https://www.nvmnode.com/" rel="noopener noreferrer"&gt;Node Version Manager (nvm)&lt;/a&gt; to manage Node versions if you find yourself working on multiple projects with different Node versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  ESLint: The Flat Config and Architectural Boundaries
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/eslint.config.mjs" rel="noopener noreferrer"&gt;ESLint configuration&lt;/a&gt; was one of the more interesting parts of Phase 1. The JavaScript ecosystem recently shifted from the legacy &lt;code&gt;.eslintrc&lt;/code&gt; format to the new "flat config" format. I decided to use flat config from the start rather than dealing with a migration later. Although I did end up (accidentally) using the &lt;a href="https://typescript-eslint.io/packages/typescript-eslint/#config-deprecated" rel="noopener noreferrer"&gt;deprecated tseslint &lt;code&gt;config()&lt;/code&gt; as opposed to &lt;code&gt;defineConfig()&lt;/code&gt;&lt;/a&gt;, but this will change in the future.&lt;/p&gt;

&lt;p&gt;The flat config format uses &lt;code&gt;eslint.config.mjs&lt;/code&gt; and exports an array of configuration objects. Each object can target specific file patterns, extend other configs, and define rules. The mental model is cleaner than the legacy format's cascading inheritance, and it plays better with TypeScript tooling.&lt;/p&gt;

&lt;p&gt;The configuration extends &lt;code&gt;typescript-eslint&lt;/code&gt;'s strict type-checked preset along with &lt;a href="https://github.com/angular-eslint/angular-eslint" rel="noopener noreferrer"&gt;Angular ESLint's recommended rules&lt;/a&gt;. This combination enables rules that require type information, like &lt;code&gt;no-floating-promises&lt;/code&gt; (catching unhandled promise rejections) and &lt;code&gt;no-misused-promises&lt;/code&gt; (preventing promises in places that expect synchronous code). These rules have caught real bugs in my professional work.&lt;/p&gt;

&lt;p&gt;Beyond the standard rules, I added several strict TypeScript rules that I consider non-negotiable for enterprise code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;no-explicit-any&lt;/code&gt; prevents the &lt;code&gt;any&lt;/code&gt; type entirely. If you need to represent an unknown type, use &lt;code&gt;unknown&lt;/code&gt; and narrow it properly. I've had nightmares dealing with &lt;code&gt;any&lt;/code&gt; types in the past.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;explicit-function-return-type&lt;/code&gt; requires return type annotations on functions. This catches cases where TypeScript infers a broader type than you intended.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strict-boolean-expressions&lt;/code&gt; prevents truthy/falsy coercion in conditions. You must explicitly check for null, undefined, empty strings, or zero.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;no-unnecessary-condition&lt;/code&gt; catches conditions that are always true or always false based on the types.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These rules add friction when writing code, but they eliminate entire categories of subtle bugs. The investment pays off quickly on any project that will be maintained over time.&lt;/p&gt;

&lt;p&gt;The most important plugin is &lt;a href="https://www.npmjs.com/package/eslint-plugin-boundaries" rel="noopener noreferrer"&gt;&lt;code&gt;eslint-plugin-boundaries&lt;/code&gt;&lt;/a&gt;. This plugin enforces architectural layering at the import level. The configuration defines five element types: entry points, app root files, core modules, features, and shared modules. Then it defines rules about which types can import from which:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Entry points (like &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/src/main.ts" rel="noopener noreferrer"&gt;&lt;code&gt;main.ts&lt;/code&gt;&lt;/a&gt;) can only import from app root files&lt;/li&gt;
&lt;li&gt;App root files can import from anywhere&lt;/li&gt;
&lt;li&gt;Core modules can import from other core modules and shared&lt;/li&gt;
&lt;li&gt;Features can import from core, shared, and the same feature, but not from other features&lt;/li&gt;
&lt;li&gt;Shared can only import from other shared modules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh0k50dh9kao2zwwoqbl5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh0k50dh9kao2zwwoqbl5.png" alt="Figure 1: The unidirectional data flow enforced by ESLint. Features (Green) can consume Core and Shared, but never other Features" width="640" height="981"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That last rule is critical. Features cannot import from other features. This prevents the spaghetti dependencies that make large codebases unmaintainable. If two features need to share logic, that logic must move to shared or core. The boundaries are enforced automatically on every lint run, which means they cannot be accidentally violated during a rushed PR. Teams often use tools like Nx to enforce these boundaries, but I prefer to keep the configuration simple and focused on code quality.&lt;/p&gt;

&lt;p&gt;For HTML templates, the configuration extends Angular ESLint's template accessibility rules. These catch common accessibility issues like missing alt text, improper ARIA usage, and form elements without labels. Accessibility enforcement at the linting level means these issues are caught before code review, not during manual QA. One common mistake I should have included earlier was checking the hierarchy of headings to ensure proper nesting, but I did not consider it until I was optimizing near the end of the project. I ended up adding it to the configuration in the final phase using a custom rule and also including markdown linting with &lt;code&gt;markdownlint-cli2&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prettier and Import Organization
&lt;/h2&gt;

&lt;p&gt;Formatting is handled by &lt;a href="https://prettier.io/" rel="noopener noreferrer"&gt;Prettier&lt;/a&gt; with a simple configuration: single quotes, trailing commas, 2-space indentation, and a 100-character line width. The specific choices matter less than the consistency. Every file in the repository follows the same formatting rules, which eliminates bike-shedding in code reviews and makes diffs cleaner.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.npmjs.com/package/prettier-plugin-organize-imports" rel="noopener noreferrer"&gt;&lt;code&gt;prettier-plugin-organize-imports&lt;/code&gt;&lt;/a&gt; plugin automatically sorts and groups imports on every format. This eliminates manual import organization and ensures imports follow a consistent pattern across the codebase. It also removes unused imports automatically, which keeps files clean.&lt;/p&gt;

&lt;p&gt;The integration with ESLint is intentionally minimal. Prettier handles formatting, ESLint handles code quality. Mixing the two leads to conflicts and slower lint runs. The configuration runs them separately: ESLint with &lt;code&gt;--fix&lt;/code&gt; for auto-fixable issues, then Prettier for formatting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Git Hooks: Enforcing Quality Before Commit
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/husky" rel="noopener noreferrer"&gt;Husky&lt;/a&gt; manages Git hooks with two key hooks configured: &lt;code&gt;pre-commit&lt;/code&gt; and &lt;code&gt;commit-msg&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;pre-commit&lt;/code&gt; hook runs &lt;code&gt;lint-staged&lt;/code&gt;, which executes linting and formatting only on staged files. This keeps the hook fast even in large codebases. For TypeScript and HTML files, it runs ESLint with auto-fix followed by Prettier. For SCSS, JSON, Markdown, and JavaScript config files, it runs only Prettier. The staged files are automatically re-staged after fixes, so the commit includes the corrected versions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjpooryn30mvj5tqq5hw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjpooryn30mvj5tqq5hw.png" alt="Figure 2: Husky intercepts a bad commit before it leaves my machine. Quality is enforced at the source." width="712" height="201"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;commit-msg&lt;/code&gt; hook runs &lt;a href="https://commitlint.js.org/" rel="noopener noreferrer"&gt;Commitlint&lt;/a&gt; to enforce conventional commit messages. Every commit must start with a type (&lt;code&gt;feat&lt;/code&gt;, &lt;code&gt;fix&lt;/code&gt;, &lt;code&gt;docs&lt;/code&gt;, &lt;code&gt;refactor&lt;/code&gt;, etc.) followed by a scope and description. This convention enables automated changelog generation in Phase 6 and makes the Git history readable. When you can scan commit messages and understand what changed without reading diffs, code archaeology becomes much easier. I can't say I followed this standard very well in the early stages, but I did follow it in the later parts of the project.&lt;/p&gt;

&lt;p&gt;The combination of these hooks means that by the time code reaches the remote repository, it has already passed linting, formatting, and commit message validation. The CI pipeline rarely fails for style issues because those issues never make it past the developer's machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing: Vitest for Units, Playwright for E2E
&lt;/h2&gt;

&lt;p&gt;Angular historically shipped with Karma and Jasmine for unit testing. &lt;a href="https://angular.dev/guide/testing/migrating-to-vitest" rel="noopener noreferrer"&gt;Starting with Angular 21, the framework includes Vitest as the default test runner.&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;I stuck with Vitest for unit testing. Vitest is fast, has excellent TypeScript support, and provides a better developer experience than Karma. The watch mode is nearly instant, the error messages are clearer, and the configuration is simpler. It uses the same configuration format as Vite, which means less tooling to learn.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/vitest.config.ts" rel="noopener noreferrer"&gt;Vitest configuration&lt;/a&gt; is straightforward. It uses &lt;code&gt;jsdom&lt;/code&gt; for DOM simulation, includes verbose and JUnit reporters (the latter for CI integration), and targets &lt;code&gt;*.spec.ts&lt;/code&gt; files in the src directory. Coverage uses the &lt;code&gt;v8&lt;/code&gt; provider with thresholds set at 85% for statements, branches, functions, and lines. The CI pipeline fails if coverage drops below these thresholds, which prevents coverage from slowly eroding over time.&lt;/p&gt;

&lt;p&gt;For end-to-end testing, &lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; was the obvious choice. It supports Chromium, Firefox, and WebKit from a single API, runs tests in parallel, and has excellent debugging tools. The Playwright test runner is also significantly faster than alternatives like Cypress because it does not run tests inside a browser's JavaScript context. Being free and open source is another big plus.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/playwright.config.ts" rel="noopener noreferrer"&gt;E2E configuration&lt;/a&gt; enables sharding for CI. The dedicated E2E workflow splits tests across four parallel runners using Playwright's built-in sharding support. Each shard runs a quarter of the tests, and the results are uploaded as separate artifacts. This approach scales well as the test suite grows, and it keeps CI times reasonable even with comprehensive E2E coverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI/CD: GitHub Actions Workflows
&lt;/h2&gt;

&lt;p&gt;The CI/CD setup uses multiple GitHub Actions workflows, each with a specific responsibility.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/.github/workflows/ci.yml" rel="noopener noreferrer"&gt;main CI workflow&lt;/a&gt; runs on every push to main and every pull request. It has four jobs that run in parallel where possible: lint, test, build, and E2E. The lint job runs ESLint and checks Prettier formatting. The test job runs Vitest with coverage and uploads results to Codecov. The build job creates a production build and uploads it as an artifact. The E2E job depends on the build job, downloads the artifact, and runs Playwright tests against it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk892qvdy4owjf4pzha24.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk892qvdy4owjf4pzha24.png" alt="Figure 3: The CI Pipeline running parallel jobs. Sharding E2E tests reduces execution time significantly." width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All workflows use npm caching through the &lt;code&gt;setup-node&lt;/code&gt; action, which significantly speeds up dependency installation. The &lt;code&gt;concurrency&lt;/code&gt; setting cancels in-progress runs when a new commit is pushed to the same branch, preventing resource waste on outdated commits.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/.github/workflows/e2e.yml" rel="noopener noreferrer"&gt;dedicated E2E workflow&lt;/a&gt; runs Playwright with sharding across four parallel machines. Each machine installs dependencies, installs Playwright browsers, runs its shard of tests, and uploads the report. The &lt;code&gt;fail-fast: false&lt;/code&gt; setting ensures all shards complete even if one fails, which gives complete test results rather than stopping at the first failure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/.github/workflows/codeql.yml" rel="noopener noreferrer"&gt;Security scanning&lt;/a&gt; uses GitHub's CodeQL on a weekly schedule and on every PR. CodeQL analyzes TypeScript code for common vulnerabilities like XSS and injection attacks. The dependency review workflow scans &lt;code&gt;package-lock.json&lt;/code&gt; changes and blocks PRs that introduce dependencies with high-severity common vulnerabilities and exposures (CVEs) or non-compliant licenses.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/.github/workflows/deploy.yml" rel="noopener noreferrer"&gt;deployment workflow&lt;/a&gt; triggers on pushes to main, which isn't ideal for a large codebase, but served my purposes well. It builds the Angular application, Storybook, and Compodoc documentation, then merges them into a single artifact and deploys to GitHub Pages. The Storybook build ends up at &lt;code&gt;/storybook&lt;/code&gt; and the API documentation at &lt;code&gt;/docs&lt;/code&gt;, making all project documentation accessible from a single deployment. Eventually I will include links directly to the documentation from the main page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/.github/workflows/lighthouse.yml" rel="noopener noreferrer"&gt;Lighthouse CI&lt;/a&gt; runs on pull requests to catch performance regressions before they merge. This ensures bundle size stays reasonable and performance metrics do not degrade over time. This is the longest running CI pipeline, but it checks all six themes and all pages of the application. At first, Lighthouse scores below 90 were considered a failure, but later I bumped the thresholds for accessibility, SEO, and best practices to 100 and left performance at 90.&lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation: Storybook and Compodoc
&lt;/h2&gt;

&lt;p&gt;Documentation is treated as code in this project. That means it lives in the repository, builds automatically, and deploys alongside the application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flmm31vjbhx6dqkvrq2c9.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flmm31vjbhx6dqkvrq2c9.webp" alt="Figure 4: Documentation as Code. Storybook (Left) handles UI states, while Compodoc (Right) generates API references automatically." width="600" height="816"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://storybook.js.org/" rel="noopener noreferrer"&gt;Storybook&lt;/a&gt; serves as the visual catalog for the design system. It targets the &lt;code&gt;src/app/shared&lt;/code&gt; directory where all reusable UI components live. Each component gets a &lt;code&gt;.stories.ts&lt;/code&gt; file that demonstrates its variants, states, and props. The Storybook configuration uses Angular's application builder and enables automatic documentation generation.&lt;/p&gt;

&lt;p&gt;Storybook's accessibility addon runs axe-core checks on every story, which catches accessibility issues during component development rather than after integration. When you can see accessibility violations in the Storybook UI while building a component, fixing them becomes part of the natural workflow rather than a separate QA step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://compodoc.app/" rel="noopener noreferrer"&gt;Compodoc&lt;/a&gt; generates API documentation from TSDoc comments in the code. It targets core services and feature modules, producing documentation that includes dependency graphs, method signatures, and cross-references. The configuration excludes test and story files to keep the documentation focused on production code.&lt;/p&gt;

&lt;p&gt;Both tools build as part of the deployment workflow and are accessible from the deployed site (in-app &lt;a href="https://moodyjw.github.io/angular-enterprise-blueprint/storybook/" rel="noopener noreferrer"&gt;Storybook&lt;/a&gt; / &lt;a href="https://moodyjw.github.io/angular-enterprise-blueprint/docs/" rel="noopener noreferrer"&gt;Compodoc&lt;/a&gt;). This means stakeholders can browse component documentation and API references without running the project locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Internationalization: Why Transloco
&lt;/h2&gt;

&lt;p&gt;For internationalization, I chose &lt;a href="https://github.com/jsverse/transloco" rel="noopener noreferrer"&gt;Transloco&lt;/a&gt; over Angular's built-in &lt;code&gt;@angular/localize&lt;/code&gt;. The decision came down to workflow and flexibility.&lt;/p&gt;

&lt;p&gt;Angular's localize requires building separate bundles for each language. This approach offers better runtime performance because translations are compiled into the bundle, but it complicates the build and deployment pipeline. You need separate deployments for each language or a server that routes to the correct bundle.&lt;/p&gt;

&lt;p&gt;Transloco uses runtime translation loading. The application builds once, and &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/src/assets/i18n" rel="noopener noreferrer"&gt;translation files&lt;/a&gt; load via HTTP based on the selected language. This means a single deployment supports all languages, and adding a new language is just adding a JSON file. Language switching is instant without a page reload, which provides a better user experience for applications where users might switch languages frequently.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/src/app/core/i18n/transloco.config.ts" rel="noopener noreferrer"&gt;Transloco configuration&lt;/a&gt; sets English as the default language with Spanish and eventually French as additional options. Translation files live in &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/src/assets/i18n" rel="noopener noreferrer"&gt;&lt;code&gt;assets/i18n/&lt;/code&gt;&lt;/a&gt; as JSON files with dot-notation keys that mirror the component structure. This naming convention makes it easy to find and update translations for specific features.&lt;/p&gt;

&lt;p&gt;For this portfolio project, the simpler deployment workflow and instant language switching outweigh the marginal performance benefits of build-time localization. If the application needed to support dozens of languages or had strict performance requirements, the calculus might be different.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Phase 1 took longer than I initially expected, but the investment will likely pay off long-term (it absolutely did).&lt;/p&gt;

&lt;p&gt;The boundaries plugin will catch architectural violations during later phases that I probably would have overlooked. Having the tooling enforce architecture rather than relying on discipline and documentation makes a real difference. This matters even more when developing with AI-assisted tools like GitHub Copilot or Claude. LLMs can generate valid-looking code that violates architectural boundaries or uses implicit type coercion. Having automated linting catch these mistakes immediately means you can move fast with AI assistance while maintaining code quality; the tooling acts as a safety net that catches errors whether they come from human developers or AI suggestions.&lt;/p&gt;

&lt;p&gt;The strict TypeScript rules caused friction initially, but they caught real bugs. The &lt;code&gt;strict-boolean-expressions&lt;/code&gt; rule in particular surfaced several places where I was relying on implicit coercion that could have caused issues with falsy values like zero or empty strings.&lt;/p&gt;

&lt;p&gt;Setting up Vitest with Angular was smoother than I expected. In previous projects, I struggled with configuration issues and slow test runs. This time, the setup worked on the first try and tests run fast enough that I actually run them frequently during development.&lt;/p&gt;

&lt;p&gt;The CI pipeline catches issues before they become problems. I have pushed commits that broke the build or failed linting, and the feedback loop is fast enough that I catch and fix these issues before context-switching to something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;With Phase 1 complete, the project has a solid foundation of tooling and automation. The next article will cover &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/docs/specs/PHASE_2_CORE.md" rel="noopener noreferrer"&gt;Phase 2: Core Architecture&lt;/a&gt;. That phase builds the invisible infrastructure that powers the application, including environment configuration, core services, error handling, and the mock authentication system.&lt;/p&gt;

&lt;p&gt;The patterns established in Phase 1 will guide development throughout the remaining phases. Every new file gets linted and formatted automatically. Every commit follows conventional commit standards. Every PR must pass the quality gates. This consistency compounds over time, making the codebase easier to maintain as it grows.&lt;/p&gt;

&lt;p&gt;You can explore the complete Phase 1 implementation on GitHub: &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint" rel="noopener noreferrer"&gt;MoodyJW/angular-enterprise-blueprint&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Next in series: Phase 2 Deep Dive - Core Architecture, Services, and Authentication&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>architecture</category>
      <category>tooling</category>
      <category>angular</category>
    </item>
    <item>
      <title>Building a Portfolio That Actually Demonstrates Enterprise Skills - Intro</title>
      <dc:creator>Jason Moody</dc:creator>
      <pubDate>Mon, 29 Dec 2025 14:54:30 +0000</pubDate>
      <link>https://forem.com/moodyjw/building-a-portfolio-that-actually-demonstrates-enterprise-skills-intro-5hlm</link>
      <guid>https://forem.com/moodyjw/building-a-portfolio-that-actually-demonstrates-enterprise-skills-intro-5hlm</guid>
      <description>&lt;h2&gt;
  
  
  Creating an Enterprise Blueprint as a Portfolio Using Angular
&lt;/h2&gt;

&lt;p&gt;As a &lt;a href="https://www.linkedin.com/in/jasonwmoody/" rel="noopener noreferrer"&gt;Lead Front-end Developer&lt;/a&gt;, I don't have much time for personal projects, and none of my professional work is public. I realized I needed something tangible to demonstrate my skills. Instead of building a collection of small projects, I'm creating a &lt;a href="https://moodyjw.github.io/angular-enterprise-blueprint/" rel="noopener noreferrer"&gt;"mini enterprise" application&lt;/a&gt; as my portfolio. This means applying modern architecture, documentation standards, CI/CD pipelines, comprehensive testing, and other common enterprise practices. I'm treating it like a real production project, following enterprise best practices from the ground up. This approach lets me focus on a single project while demonstrating an entire professional skill set.&lt;/p&gt;

&lt;p&gt;In this introductory article, I'll explain the motivation behind this approach, outline the technology decisions, and provide a roadmap of what's to come. Future articles will dive deep into each phase of implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Traditional Developer Portfolios
&lt;/h2&gt;

&lt;p&gt;Most developer portfolios fall into one of two traps: either they're visually polished static sites that lack technical depth, or they're collections of small demos that show variety but not complexity. Neither format reflects what it's like to build and maintain enterprise-scale software.&lt;/p&gt;

&lt;p&gt;Consider what a typical portfolio might include: a to-do app, a weather widget, maybe a simple e-commerce page. These projects demonstrate basic competency, but they don't show how you handle the challenges that define senior-level work. How do you structure a codebase that multiple developers can maintain? How do you enforce code quality across a team? How do you ensure accessibility compliance? How do you set up deployment pipelines that catch bugs before they reach production?&lt;/p&gt;

&lt;p&gt;As someone working on proprietary enterprise applications, I face a specific challenge. My daily work involves complex state management, routing architectures, comprehensive test suites, and CI/CD automation; however, none of that code can be shared publicly. I needed a way to showcase these skills without violating NDAs or exposing proprietary code.&lt;/p&gt;

&lt;p&gt;The solution was to build something new that demonstrates the same principles and practices I use professionally, but in a context I can share openly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Mini Enterprise" Approach
&lt;/h2&gt;

&lt;p&gt;Rather than build many small apps, I decided to build one portfolio application that mirrors how enterprise teams operate. That means treating it as a serious product with production-grade standards, not a weekend side project.&lt;/p&gt;

&lt;p&gt;The goal is to create something that functions as both a personal portfolio and a "clone-and-go" starter kit for enterprise teams. Anyone should be able to clone the repository and have a fully functional development environment with all the tooling, testing, and CI/CD infrastructure already configured.&lt;/p&gt;

&lt;h3&gt;
  
  
  Here's what that looks like in practice:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft0jyn08dlg029f3zabwi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft0jyn08dlg029f3zabwi.png" alt="Screenshot of multiple passing github workflows on a pull request" width="800" height="501"&gt;&lt;/a&gt;&lt;em&gt;Figure 1: Automated quality gates enforcing standards on every Pull Request.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure First:&lt;/strong&gt; Before writing any feature code, I built out the complete development foundation. That includes &lt;a href="https://eslint.org/docs/latest/" rel="noopener noreferrer"&gt;ESLint&lt;/a&gt; with strict type checking and accessibility rules, automated testing with &lt;a href="https://vitest.dev/guide/" rel="noopener noreferrer"&gt;Vitest&lt;/a&gt; and &lt;a href="https://playwright.dev/docs/intro" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt;, documentation through &lt;a href="https://storybook.js.org/docs" rel="noopener noreferrer"&gt;Storybook&lt;/a&gt; and &lt;a href="https://compodoc.app/guides/getting-started.html" rel="noopener noreferrer"&gt;Compodoc&lt;/a&gt;, and Git hooks to enforce code quality before commits even reach the repository. I also leveraged AI tools like &lt;a href="https://github.com/features/copilot" rel="noopener noreferrer"&gt;GitHub Copilot&lt;/a&gt; and &lt;a href="https://claude.ai/new" rel="noopener noreferrer"&gt;Claude&lt;/a&gt; to help scaffold boilerplate, generate initial test stubs, and accelerate repetitive setup tasks, freeing me to focus on architecture and quality decisions.&lt;/p&gt;

&lt;p&gt;In enterprise development, you don't start building features until the base infrastructure is solid. This "shift left" philosophy catches errors, style violations, and problematic commits on the developer's machine before they ever reach the CI server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture Over Aesthetics:&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fztr46o72pbua0yn2a3jc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fztr46o72pbua0yn2a3jc.png" alt="Figure 2: The Unidirectional Data Flow. The architecture enforces strict boundaries: Features (Green) are lazy-loaded by the Shell (Grey) and consume Core logic (Blue) and Shared UI (Purple), but never depend on each other." width="640" height="981"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2: The strict unidirectional data flow: Features consume Core and Shared, but never each other.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;While the portfolio will look professional, the emphasis is on maintainable architecture. It's built with &lt;a href="https://v21.angular.dev/overview" rel="noopener noreferrer"&gt;Angular 21&lt;/a&gt; using standalone components, signals for reactive state management, &lt;a href="https://ngrx.io/guide/signals/signal-store" rel="noopener noreferrer"&gt;NgRx SignalStore&lt;/a&gt; for centralized state, and lazy-loaded routes for performance. The codebase follows a strict layered architecture: &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/src/app/core/services" rel="noopener noreferrer"&gt;Core services&lt;/a&gt; (singletons loaded once), &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/src/app/shared" rel="noopener noreferrer"&gt;Shared components&lt;/a&gt; (reusable UI elements), and &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/src/app/features" rel="noopener noreferrer"&gt;feature modules&lt;/a&gt; (routed pages). &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/eslint.config.mjs" rel="noopener noreferrer"&gt;ESLint rules&lt;/a&gt; enforce these boundaries, features cannot import from other features, and shared components cannot depend on core services.&lt;/p&gt;

&lt;p&gt;Each architectural decision is documented with clear rationale. When someone reviews the codebase, they should understand not just what was built, but why it was built that way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quality Gates:&lt;/strong&gt; Nothing merges without passing quality checks. The &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/.github/workflows" rel="noopener noreferrer"&gt;CI/CD pipeline&lt;/a&gt; runs linting, unit tests with 85% coverage thresholds, &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/e2e" rel="noopener noreferrer"&gt;E2E tests&lt;/a&gt; across multiple browsers, security scanning with CodeQL, dependency vulnerability checks, and &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/.github/workflows/lighthouse.yml" rel="noopener noreferrer"&gt;Lighthouse performance audits&lt;/a&gt;. If any of these checks fail, the pull request cannot be merged. This mirrors how enterprise teams operate. Quality is enforced automatically, not left to manual review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accessibility as a Requirement:&lt;/strong&gt; The project targets &lt;a href="https://www.w3.org/WAI/WCAG2AA-Conformance" rel="noopener noreferrer"&gt;WCAG 2.1 AA compliance&lt;/a&gt; as a minimum, with efforts toward AAA where practical. This includes proper color contrast ratios, complete keyboard navigation, accurate ARIA labeling, and automated accessibility testing at multiple layers. Accessibility is built into the development process from the start, not retrofitted after the fact. The ESLint configuration includes Angular's template accessibility rules, and &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/.storybook" rel="noopener noreferrer"&gt;Storybook integrates&lt;/a&gt; accessibility auditing for every component.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-World Complexity:&lt;/strong&gt; The app includes engineering elements that rarely appear in portfolio projects but define real enterprise work. There's proper error handling with a global error boundary and HTTP interceptors. Loading states are handled consistently across the application. Internationalization is set up with Transloco, supporting multiple languages with lazy-loaded translation files. A mock API layer simulates network latency and occasional failures, demonstrating how the application handles real-world conditions. These details separate production-ready code from demo code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Planning Before Building: The Implementation Plan
&lt;/h2&gt;

&lt;p&gt;Before writing code, I created an in-depth &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/docs/PLAN.md" rel="noopener noreferrer"&gt;implementation plan&lt;/a&gt; that divides the work into six phases, each with clear deliverables, technical specifications, and success criteria. This isn't a static document, it's a set of living specifications that evolve as the project progresses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/docs/specs/PHASE_1_SETUP.md" rel="noopener noreferrer"&gt;Phase 1: Tooling and Governance&lt;/a&gt;&lt;/strong&gt; – This phase establishes the "rules of engagement" for the entire project. It covers workspace configuration with Angular CLI in strict mode, ESLint with the boundaries plugin for architectural enforcement, Prettier with automatic import organization, Husky for Git hooks, Commitlint for conventional commit messages, Vitest for unit testing, Playwright for E2E testing, Storybook for component documentation, Compodoc for API documentation, and GitHub Actions workflows for CI/CD, security scanning, and deployment. The principle here is "shift left" and catch problems as early as possible in the development process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/docs/specs/PHASE_2_CORE.md" rel="noopener noreferrer"&gt;Phase 2: Core Architecture&lt;/a&gt;&lt;/strong&gt; – This phase builds the invisible singletons that power the application. It includes typed environment configuration, infrastructure services (logging, analytics, SEO, theming), global error handling with custom error boundaries and HTTP interceptors, and a mock authentication system. The authentication layer uses a strategy pattern, making it easy to swap the mock implementation for a real authentication provider later. NgRx SignalStore manages authentication state, with functional guards protecting routes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/docs/specs/PHASE_3_DS.md" rel="noopener noreferrer"&gt;Phase 3: The Design System&lt;/a&gt;&lt;/strong&gt; – This phase develops the reusable UI component library that lives in the shared folder. It covers global styling with CSS custom properties supporting multiple themes (light, dark, high contrast), atomic components (buttons, icons, badges, spinners, cards), molecular components (alerts, breadcrumbs), layout components (containers, grids, stacks), form components with proper ControlValueAccessor implementation, and feedback components (toasts, modals, skeletons). Every component gets a Storybook story with documentation and accessibility checks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/docs/specs/PHASE_4_SHELL.md" rel="noopener noreferrer"&gt;Phase 4: The Application Shell&lt;/a&gt;&lt;/strong&gt; – This phase builds the frame that holds all the pages. It includes the main layout component with header, footer, and router outlet, responsive navigation with theme picker integration, and lazy-loaded routing configuration. The header connects to the authentication store to show appropriate UI based on login state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/docs/specs/PHASE_5_FEATURES.md" rel="noopener noreferrer"&gt;Phase 5: Feature Implementation&lt;/a&gt;&lt;/strong&gt; – This phase creates the actual content of the portfolio. Features map one-to-one with UI navigation: a dashboard showing system status and build information, a "Reference Modules" catalog (the projects showcase), an "Architecture Decisions" viewer for ADRs, a profile page ("The Architect"), and a contact form ("Hire Me") with rate-limiting simulation. Each feature uses SignalStore for state management and includes its own documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/docs/specs/PHASE_6_DEVOPS.md" rel="noopener noreferrer"&gt;Phase 6: Ops and Optimization&lt;/a&gt;&lt;/strong&gt; – This final phase ensures the application ships like enterprise software. It covers deployment to GitHub Pages, performance tuning with bundle budgets and Lighthouse CI, release management with automated changelog generation, and final documentation including a comprehensive README, contributing guide, and architecture overview.&lt;/p&gt;

&lt;p&gt;Each phase builds on the previous one, just like a real enterprise project where you can't skip ahead without a solid foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;When a hiring manager or technical lead reviews this portfolio, they won't just see a polished website. They'll see evidence of engineering discipline:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/commits" rel="noopener noreferrer"&gt;A Clean Git History:&lt;/a&gt;&lt;/strong&gt; Commits follow conventional commit standards (feat, fix, docs, refactor, etc.), enabling automated changelog generation. The history tells a story of incremental, well-documented progress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/docs/adr" rel="noopener noreferrer"&gt;Architectural Documentation:&lt;/a&gt;&lt;/strong&gt; Architecture Decision Records (ADRs) capture the reasoning behind significant technical choices. This demonstrates not just the ability to make decisions, but the ability to communicate and document them for future team members.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/.github/workflows/ci.yml" rel="noopener noreferrer"&gt;Measurable Test Coverage:&lt;/a&gt;&lt;/strong&gt; Test coverage isn't assumed, it's enforced. The CI pipeline fails if coverage drops below 85%. Unit tests use Vitest for speed, and E2E tests use Playwright for cross-browser validation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/tree/main/.github/workflows" rel="noopener noreferrer"&gt;DevOps Proficiency:&lt;/a&gt;&lt;/strong&gt; The GitHub Actions workflows demonstrate understanding of CI/CD pipelines, including build optimization, test parallelization, security scanning, and automated deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/lighthouserc.json" rel="noopener noreferrer"&gt;Accessibility Compliance:&lt;/a&gt;&lt;/strong&gt; Automated accessibility testing at the linting, component, and E2E levels proves attention to inclusive design. This is increasingly important for enterprise applications that must meet legal accessibility requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint/blob/main/tsconfig.json" rel="noopener noreferrer"&gt;Strict TypeScript Discipline:&lt;/a&gt;&lt;/strong&gt; The entire codebase uses strict TypeScript with zero &lt;code&gt;any&lt;/code&gt; types. ESLint rules enforce explicit return types, proper null handling, and other type safety best practices.&lt;/p&gt;

&lt;p&gt;This is what separates experienced engineers from those still building foundational skills. It's not about knowing the latest framework, but about demonstrating engineering discipline, maintainability, and attention to the details that matter in production systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technology Stack
&lt;/h2&gt;

&lt;p&gt;I chose Angular 21 because it offers enterprise-grade features out of the box: standalone components that simplify module management, fine-grained reactivity via signals, built-in dependency injection, and a mature ecosystem with excellent tooling. It's designed for building large-scale, maintainable applications—exactly what this project aims to demonstrate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core Framework:&lt;/strong&gt; Angular 21 with standalone components, TypeScript 5.9 in strict mode, &lt;a href="https://rxjs.dev/guide/overview" rel="noopener noreferrer"&gt;RxJS&lt;/a&gt; for async operations, Signals for reactive state, and NgRx SignalStore for centralized state management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing:&lt;/strong&gt; Vitest for unit testing (fast, modern, excellent developer experience), Playwright for E2E testing across Chromium, Firefox, and WebKit, with support for visual regression testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code Quality:&lt;/strong&gt; ESLint with Angular ESLint rules, the boundaries plugin for architectural enforcement, strict TypeScript rules, and 85% minimum coverage thresholds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documentation:&lt;/strong&gt; Storybook for visual component documentation with accessibility auditing, Compodoc for API documentation with dependency graphs, and TSDoc comments throughout the codebase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accessibility:&lt;/strong&gt; WCAG 2.1 AA compliance as a baseline, with Angular's template accessibility linting and Storybook's a11y addon for component-level testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build and Tooling:&lt;/strong&gt; Angular CLI with the modern build system, npm for package management with engine version enforcement, Husky for Git hooks, and Prettier for consistent formatting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI/CD and Deployment:&lt;/strong&gt; GitHub Actions for automated pipelines, CodeQL for security scanning, Lighthouse CI for performance auditing, and GitHub Pages for hosting.&lt;/p&gt;

&lt;p&gt;Each tool was chosen for a specific reason: it solves a real problem or aligns with patterns I've seen succeed in enterprise environments. The stack is opinionated but practical; these are tools that work well together and scale to larger teams and codebases.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;This is the first post in a series documenting the entire process of building this portfolio. I'm not just writing tutorials, I'm sharing the reasoning, trade-offs, and lessons that arise from building a production-grade Angular application from scratch.&lt;/p&gt;

&lt;p&gt;The next article will cover Phase 1 in depth: Tooling and Governance. I'll explain how I configured ESLint with the &lt;a href="https://eslint.org/docs/latest/use/configure/configuration-files" rel="noopener noreferrer"&gt;flat config format&lt;/a&gt; and boundaries plugin, set up Vitest to replace Karma and Jasmine, integrated Playwright for E2E testing, established Git hooks with Husky and Commitlint, configured Storybook and Compodoc for documentation, and built out the GitHub Actions workflows that enforce quality on every pull request.&lt;/p&gt;

&lt;p&gt;If you're a developer aiming to demonstrate enterprise-level skills, preparing for senior or lead roles, or just interested in seeing how production-grade Angular projects are structured, I hope this series offers both practical insight and inspiration. If you have questions, suggestions, or your own experiences to share, I'd love to hear from you in the comments.&lt;/p&gt;

&lt;p&gt;You can follow along with the project on GitHub: &lt;a href="https://github.com/MoodyJW/angular-enterprise-blueprint" rel="noopener noreferrer"&gt;MoodyJW/angular-enterprise-blueprint&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Next in series: Phase 1 Deep Dive – Infrastructure, Linting, Testing, and CI/CD Automation&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>architecture</category>
      <category>angular</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
