Angular 20 SSR with AWS CDK v2

In this article we’re going to set up an Angular 20 web application with server-side rendering, using AWS Lambda and Cloudfront to serve it, and AWS CDK v2 to manage the infrastructure.

If you’re reading this, I’m assuming that you know how CDK and Angular work. I’m not going to explain in detail how to set up your projects.

The CDK Construct

Create a CDK application wherever you want. I usually create a folder within my Angular project, called aws or infrastructure.

Now create a new file in the lib folder called angular-ssr.ts (or whatever you want) and put this code in it.

TypeScript
import {CfnOutput} from 'aws-cdk-lib';
import {Code, Function, FunctionUrl, FunctionUrlAuthType, HttpMethod, Runtime} from 'aws-cdk-lib/aws-lambda';
import {Construct} from 'constructs';
import {Certificate} from 'aws-cdk-lib/aws-certificatemanager';
import {
  CachePolicy,
  Distribution,
  OriginRequestPolicy,
  PriceClass,
  ViewerProtocolPolicy
} from 'aws-cdk-lib/aws-cloudfront';
import {FunctionUrlOrigin} from 'aws-cdk-lib/aws-cloudfront-origins';

export interface AngularSSRProps {
  /**
   * Used to comment the Cloudfront distribution
   */
  appName?: string;
  /**
   * If set, the Cloudfront distribution will have custom domains and an SSL certificate.
   * The certificate domains must cover the domains specified in `domainNames`.
   */
  certificate?: Certificate;
  /**
   * If set, the Cloudfront distribution will have custom domains.
   * For this to work, you must specify a `certificate` as well.
   */
  domainNames?: string[];
}

export class AngularSSR extends Construct {

  public distribution: Distribution;
  public lambdaUrl: FunctionUrl;
  public ssrLambda: Function;

  constructor(scope: Construct, id: string, props: AngularSSRProps) {
    super(scope, id);

    this.ssrLambda = new Function(this, 'WebsiteSSRLambda', {
      // Adjust this if it's outdated
      runtime: Runtime.NODEJS_22_X,
      handler: 'index.handler',
      // We'll create this file later, change path if needed
      code: Code.fromAsset(`./assets/ssr-lambda`),
      // Adjust to your project needs
      memorySize: 1024
    });

    this.lambdaUrl = this.ssrLambda.addFunctionUrl({
      authType: FunctionUrlAuthType.NONE,
      cors: {
        allowedOrigins: [
          'https://localhost:4200',
          ...(props.domainNames?.map(d => `https://${d}`) ?? []),
        ],
        allowedMethods: [HttpMethod.GET],
        allowCredentials: true,
        allowedHeaders: ["*"]
      }
    });

    this.distribution = new Distribution(this, 'WebsiteSSRDistribution', {
      certificate: props.certificate,
      domainNames: props.domainNames,
      defaultBehavior: {
        origin: new FunctionUrlOrigin(this.lambdaUrl),
        viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        originRequestPolicy: OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
        cachePolicy: CachePolicy.CACHING_OPTIMIZED
      },
      priceClass: PriceClass.PRICE_CLASS_ALL,
      comment: props.appName,
      errorResponses: [
        {httpStatus: 403, responseHttpStatus: 200, responsePagePath: '/index.html'},
        {httpStatus: 404, responseHttpStatus: 200, responsePagePath: '/index.html'}
      ]
    });

    new CfnOutput(this, 'ClientLambdaURL', {value: this.lambdaUrl.url});
    new CfnOutput(this, 'DistributionID', {value: this.distribution.distributionId});
  }

}

Some remarks on this code

  • Line 51: I’ve included https://localhost:4200 in the allowedOrigins property. This is only useful for debug purposes if you also plan to include additional API endpoints in your lambda, but it won’t be covered here.
  • Line 52: This line maps the domains array we’ll be using later, and adds “https://” to each of them, so that they can be used by CORS headers. If you also need old fashioned “http://“, you don’t.

Read more

RxJS 6 Patterns with Angular Material: Refresh Button

RxJS + Angular + Material

In this article, we’ll be looking at a very simple RxJS 6 pattern that allows us to easily refresh data coming from REST APIs (or any other async data source).

In Angular, we’ll be using a service to handle most of the work, including the data fetching and the refresh logic.

Let’s look at a mock service that we’ll be using to fetch some data from a REST API.

TypeScript
export class MockService {

  refresh$ = new ReplaySubject(1);
  data$: Observable<SomeDataType>;

  constructor(private http: HttpClient) {

    this.refresh();

    this.data$ = this.refresh$.pipe(
      switchMap(() => this.getData()),
      share()
    );
  }

  refresh() {
    this.refresh$.next();
  }

  // This one could also be private
  getData(): Observable<SomeDataType> {
    return this.http.get<SomeDataType>(`url`);
  }
}

Read more

RxJS 6 Patterns with Angular Material: Confirmation Dialog

RxJS + Angular + Material

Intro

This basic pattern applies to RxJS 6 in combination with Angular Material. However, it can be applied to any other environment that supports RxJS 6, as long as you have an observable-based dialog implementation.

Desired behaviour

  1. A user clicks on a button
  2. A confirmation dialog is shown
  3. If the user confirms, an action is performed

Read more