Skip to content

Template

Contract

@modularityjs/template defines the abstract TemplateEngine:

typescript
abstract class TemplateEngine {
  abstract render(
    name: string,
    data?: Record<string, unknown>,
  ): Promise<string>;
}

The name parameter identifies the template (e.g. 'welcome-email'). The data parameter provides variables available inside the template.

typescript
@Module({
  name: 'template',
  contracts: [TemplateEngine, TemplateHelpersPool],
})
class TemplateModule {}

TemplateHelpersPool is a cross-engine helper registry — see Helpers below.

Drivers

Handlebars (@modularityjs/template-handlebars)

File-based Handlebars templates with compiled template caching. Templates are .hbs files loaded from a configured directory.

typescript
import { TemplateModule } from '@modularityjs/template';
import { TemplateHandlebarsModule } from '@modularityjs/template-handlebars';

const modules = [
  TemplateModule,
  TemplateHandlebarsModule.forRoot({
    directory: './templates',
  }),
];

Given directory: './templates', calling render('welcome-email', data) reads ./templates/welcome-email.hbs, compiles it with Handlebars, caches the compiled template, and returns the rendered string. Subsequent calls to the same template name skip the filesystem read and compilation.

Configuration

OptionDefaultDescription
directory(required)Path to the directory of .hbs files
cacheCapacity200Maximum number of compiled templates to keep in the LRU cache

EJS (@modularityjs/template-ejs)

File-based EJS templates with LRU compiled template caching. Templates are .ejs files loaded from a configured directory.

typescript
import { TemplateModule } from '@modularityjs/template';
import { TemplateEjsModule } from '@modularityjs/template-ejs';

const modules = [
  TemplateModule,
  TemplateEjsModule.forRoot({
    directory: './templates',
  }),
];

Given directory: './templates', calling render('welcome-email', data) reads ./templates/welcome-email.ejs, compiles it with EJS, caches the compiled function, and returns the rendered string.

Configuration

OptionDefaultDescription
directory(required)Path to the directory of .ejs files
cacheCapacity200Maximum number of compiled templates to keep in the LRU cache

Usage

Rendering an email

typescript
import { Inject, Injectable } from '@modularityjs/di';
import { TemplateEngine } from '@modularityjs/template';

@Injectable()
class EmailService {
  constructor(
    @Inject(TemplateEngine) private readonly templates: TemplateEngine,
  ) {}

  async sendWelcome(user: { name: string; email: string }): Promise<void> {
    const html = await this.templates.render('welcome-email', {
      name: user.name,
    });
    await this.sendMail(user.email, 'Welcome!', html);
  }

  private async sendMail(
    to: string,
    subject: string,
    html: string,
  ): Promise<void> {
    // ...
  }
}

With templates/welcome-email.hbs:

handlebars
<h1>Welcome, {{name}}!</h1>
<p>Thanks for signing up.</p>

Rendering an HTML page in a controller

typescript
import { Inject } from '@modularityjs/di';
import { Controller, Get, SetHeader } from '@modularityjs/http';
import { TemplateEngine } from '@modularityjs/template';

@Controller('/pages')
class PagesController {
  constructor(
    @Inject(TemplateEngine) private readonly templates: TemplateEngine,
  ) {}

  @Get('/about')
  @SetHeader('Content-Type', 'text/html')
  async about() {
    return this.templates.render('about', {
      title: 'About Us',
      year: new Date().getFullYear(),
    });
  }
}

Helpers

Packages contribute reusable template helpers via TemplateHelpersPool. Each entry is an @Injectable() subclass of TemplateHelper with a name and an apply(...args) method:

typescript
import { Injectable } from '@modularityjs/di';
import { Module } from '@modularityjs/modularity';
import { TemplateHelper, TemplateHelpersPool } from '@modularityjs/template';

@Injectable()
class FormatDateHelper extends TemplateHelper {
  readonly name = 'formatDate';

  apply(input: unknown): string {
    return new Date(String(input)).toLocaleDateString();
  }
}

@Module({
  name: 'my-helpers',
  providers: [FormatDateHelper],
  pools: [
    {
      pool: TemplateHelpersPool,
      key: 'format-date',
      useClass: FormatDateHelper,
    },
  ],
})
class MyHelpersModule {}

Templates then call the helper by name:

handlebars
<time>{{formatDate user.createdAt}}</time>

The Handlebars driver reads the pool in afterLoad and registers each entry via Handlebars.registerHelper. Drivers without first-class helper registration may ignore the pool — register helpers as render-data context values instead.

The asset URL helper () is provided by @modularityjs/template-assets — wire TemplateAssetsModule to enable it.