> Lettrove docs 1.x · https://docs.lettrove.com/docs/get-started/angular

# Angular

The editor in an Angular app: one component opens it with `createEditor` from `@lettrove/embed`,
and a small Express server mints its tokens.

You need a project's two **test** keys from [lettrove.com](https://lettrove.com) → **Settings →
Embed** (step 1 of the [Quickstart](/docs/get-started/quickstart#1-create-a-project-and-copy-its-test-keys)),
and the [Node.js](https://nodejs.org) version the current Angular CLI asks for (it tells you when
it starts).

## 1. The token server

The editor opens with a short-lived **token** that your server mints with the secret key, so the
key never reaches a browser. In your product this is one route in the server you already have.

```bash
mkdir -p lettrove-angular/server
cd lettrove-angular/server
npm init -y
npm install express @lettrove/node
```

```bash title="server/.env"
LETTROVE_SECRET_KEY=lt_sk_test_paste_yours_here
```

```js title="server/server.mjs"
import express from 'express';
import { Lettrove } from '@lettrove/node';

const lettrove = new Lettrove({ secretKey: process.env.LETTROVE_SECRET_KEY });
const app = express();

// The page asks this route for a token whenever the editor needs one.
app.post('/lettrove-token', async (req, res) => {
  // In your product: your own id for the signed-in person, from your session.
  const userId = 'demo-user';
  try {
    const { token } = await lettrove.tokens.create({
      user: { id: userId },
      origin: req.get('origin'), // the page the editor opens on
    });
    res.set('Cache-Control', 'no-store').json({ token });
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: error.message, code: error.code });
  }
});

app.listen(3000, () => console.log('Token server on http://localhost:3000'));
```

Start it, and leave it running:

```bash
node --env-file=.env server.mjs
```

## 2. The Angular app

In a second terminal, from the `lettrove-angular` folder:

```bash
npx @angular/cli@latest new app --defaults --skip-git --routing=false --ssr=false --style=css
cd app
npm install @lettrove/embed
```

Send the app's `/lettrove-token` requests to the token server:

```json title="app/proxy.conf.json"
{
  "/lettrove-token": { "target": "http://localhost:3000" }
}
```

Replace `src/app/app.ts`. Put your publishable key where it says so.

```ts title="app/src/app/app.ts"
import { AfterViewInit, Component, ElementRef, OnDestroy, viewChild } from '@angular/core';
import { createEditor, type EditorHandle } from '@lettrove/embed';

async function getToken() {
  const response = await fetch('/lettrove-token', { method: 'POST' });
  return (await response.json()).token;
}

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
})
export class App implements AfterViewInit, OnDestroy {
  private readonly container = viewChild.required<ElementRef<HTMLDivElement>>('editor');
  private editor: EditorHandle | null = null;

  async ngAfterViewInit() {
    this.editor = await createEditor({
      container: this.container().nativeElement,
      publishableKey: 'lt_pk_test_paste_yours_here',
      getToken,
      on: { 'design:saved': ({ designId }) => console.log('saved', designId) },
    });
  }

  ngOnDestroy() {
    this.editor?.destroy();
  }

  async exportHtml() {
    const email = await this.editor?.exportHtml();
    console.log(email?.html);
  }
}
```

Replace `src/app/app.html` and `src/app/app.css`:

```html title="app/src/app/app.html"
<div class="page">
  <button (click)="exportHtml()">Export HTML</button>
  <div #editor class="editor"></div>
</div>
```

```css title="app/src/app/app.css"
.page { height: 100vh; display: flex; flex-direction: column; }
.editor { flex: 1; }
```

And in `src/styles.css`, remove the page's own margin:

```css title="app/src/styles.css"
body { margin: 0; }
```

## 3. Run it

```bash
npx ng serve --proxy-config proxy.conf.json
```

Open [http://localhost:4200](http://localhost:4200). Add a **Heading** from the panel on the left;
the browser's console prints `saved` and the design's id. Keep that id to open the same design
again: pass it as `designId`. Click **Export HTML** and the console prints the whole email,
compiled for every inbox.

## Next

- If the box shows a code instead of the editor: [the Quickstart's checklist](/docs/get-started/quickstart#if-something-is-not-right).
- [`@lettrove/embed`](/docs/api/embed): every option and method of `createEditor`, and the events.
- [Getting designs out](/docs/editor/exports) · [Theming](/docs/editor/theming) · [Test and live keys](/docs/going-live/test-and-live).
