Blog>
Snippets

Integrating Angular Elements with Server-Side Rendering

Explain the process of integrating server-side rendered HTML with Angular Elements, focusing on hydration and the coordination of client-side interactivity.
const { enableProdMode } = require('@angular/core');
const { platformServer, renderModuleFactory } = require('@angular/platform-server');
const { AppServerModuleNgFactory } = require('./path/to/app.server.module.ngfactory');
const { ngExpressEngine } = require('@nguniversal/express-engine');
const express = require('express');

enableProdMode();

const app = express();

app.engine('html', ngExpressEngine({
    bootstrap: AppServerModuleNgFactory
}));

app.set('view engine', 'html');
app.set('views', 'src');

app.get('*', (req, res) => {
    res.render('index', { req });
});

app.listen(8080, () => {
    console.log('Listening on http://localhost:8080');
});
This server-side Node.js script with Express sets up server-side rendering for an Angular app. It uses the Angular Universal package to render the Angular components on the server. The resulting HTML is then sent to the client for faster first paint.
// Polyfills are required for Angular Elements
import 'zone.js/dist/zone';

import { enableProdMode, NgModuleRef } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { BrowserDynamicTestingModule,
         platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
import { AppModule } from './app/app.module';

// Enable production mode if necessary
if (process.env.NODE_ENV === 'production') {
    enableProdMode();
}

// Ensure tests have clean state
// Only required if Angular Elements are used within tests
if (window['__karma__']) {
    platformBrowserDynamicTesting();
} else {
    if (document.readyState === 'complete') {
        bootstrapClient();
    } else {
        document.addEventListener('DOMContentLoaded', bootstrapClient);
    }
}

function bootstrapClient() {
    platformBrowserDynamic().bootstrapModule(AppModule).then((moduleRef: NgModuleRef<AppModule>) => {
        // Hydration of server-rendered Angular Elements
        if (moduleRef.instance instanceof AppModule) {
            const elementsAreReady = (moduleRef.instance as AppModule).initializeElements();
            elementsAreReady.then(() => {
                console.log('Angular Elements are initialized');
            });
        }
    });
}
This client-side code bootstraps the Angular application and initializes Angular Elements, ensuring that they are 'hydrated' with events and behavior. Hydration means binding the server-side rendered views with Angular elements on the client side to make them fully interactive.
// Example Angular Elements module
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { createCustomElement } from '@angular/elements';
import { AppComponent } from './app.component';

@NgModule({
  imports: [BrowserModule],
  declarations: [AppComponent],
  entryComponents: [AppComponent],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule {
  constructor() { }

  initializeElements() {
    // Convert 'AppComponent' to a custom element
    const appElement = createCustomElement(AppComponent, {injector: this.injector});
    customElements.define('app-component', appElement);

    return Promise.resolve();
  }
}
This code defines an Angular module that declares the `AppComponent` and sets it up to be used as a custom element. `initializeElements` method converts the `AppComponent` to a custom element and registers it. This enables an Angular component to be used as a web component outside Angular applications, and it can be part of the server-side rendering process.