How to SSR Lit Components in Angular template using Angular Universal

269 Views Asked by At

When enabling Angular Universal, Lit components included in the template are not server side rendered. I've followed the Lit docs on SSR. Using the render function from @lit-labs/ssr correctly SSRs the component on the server.

import { render, html } from '@lit-labs/ssr';
import { collectResultSync } from '@lit-labs/ssr/lib/render-result';


const ssrComponent = collectResultSync(render(html`<my-component></my-component>`))

I can use the generated html in the Express server to modify the index.html served by Angular and add the generated component, but that is not ideal as the component should be included in the Angular template.

The problem is that I cannot find a way to bind that server side rendered component to the Angular component's template.

Using the html function from @lit-labs/ssr in the Angular Component class (be it in the constructor, onInit or anywhere in the file) raises the following error:

instrument.js:109 Error: server-only templates can only be rendered on the server,
they cannot be rendered in the browser. Use the html function for templates that
need to be rendered from the browser. This check is based on the "node" export condition

so it can only be used on the server. However, using the if(isPlatformServer(PLATFORM_ID)) strategy doesn't work because the browser bundle knows nothing about the code run in there, so even if I SSR the component inside that block I don't know how to bind it to the actual template.

My Lit component is imported from a private npm package in the project.

The definition file is the following:

//my-component.d.ts
import { LitElement } from 'lit';
export declare class MyComponent extends LitElement {
    render(): import("lit-html").TemplateResult<1>;
    static styles: import("lit").CSSResult;
}
//# sourceMappingURL=my-component.d.ts.map
// my-component.js
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { LitElement, html, css } from 'lit';
import asset from './utils/asset';
import { webComponent } from './utils/decorators';
let MyComponent = class MyComponent extends LitElement {
    render() {
        return html `/* some html */`;
    }
};
MyComponent.styles = css `/* some css */`;
MyComponent = __decorate([
    webComponent('my-component')
], MyComponent);
export { MyComponent };
//# sourceMappingURL=my-component.js.map
// app.component.ts
import { Component } from '@angular/core';
import '@my-lit-components-package/component';

@Component({
  selector: 'app-root',
  template: `
    <h1>Angular App</h1>
    <lit-component></lit-component>
  `,
  styles: []
})
export class AppComponent {}

1

There are 1 best solutions below

0
On

Okay, so I found a workaround:

//app.component.ts
import { Renderer2 } from '@angular/core';
import { html } from 'lit';
import { render } from '@lit-labs/ssr';

@Component({
  selector: 'app',
  template: `<div #child></div>`,

})
class AppComponent
   @ViewChild('child', {static: true}) child: ElementRef;
   private renderer: Renderer2,
 

    ngAfterViewInit() {
        const litComponent = collectResultSync(render(html`<my-component></my-component>`));
        this.renderer.setProperty(this.child.nativeElement, 'innerHTML', litComponent);
      }

The html function used here is imported from lit instead of @lit/ssr. Now, because ngAfterInit will run both on the server and in the browser, when it runs on the server it will set the innerHtml property of the child <div>.The difference is that when it runs on the browser it won't raise any error because the html function from lit can run in the browser.

The caveat is that this will run on the browser again for no reason, as the server already rendered and put the component in the template.

This is the only way the suggested solution works because otherwise, if you wrapp this same code with if(isPlatformServer) it won't work.