# Angular

> Integrate the Document Editor and Document Viewer into an Angular app: environments, a dev proxy, a document service, TypeScript calls, routes and hosting.

- **Product:** TX Text Control .NET Server
- **Section:** Documentation
- **Kind:** Topic
- **Path:** TX Text Control .NET Server > Integration Guide > Angular
- **5708 words**
- **Web URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/integration/angular/
- **LLMs URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/integration/angular/llms.txt
- **LLMs-Full URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/integration/angular/llms-full.txt

---

In an Angular application, the Document Editor and the Document Viewer behave like other components: they sit in templates, appear on routes and exchange documents through services. What sets them apart is the ASP.NET Core backend they need and the global JavaScript objects that their API is called through. Developers who add both components to an existing Angular application find the pieces here: backend addresses per environment, a development proxy, a document service, calls from TypeScript, routes and hosting.

A first editor or viewer in a new Angular project is built in [Document Editor: Angular](https://docs.textcontrol.com/tx-text-control-dotnet-server/getting-started/document-editor/angular/llms-full.txt) and [Document Viewer: Angular](https://docs.textcontrol.com/tx-text-control-dotnet-server/getting-started/document-viewer/angular/llms-full.txt). The supported Angular versions and the setup of standalone applications are described in [npm Packages: Angular](https://docs.textcontrol.com/tx-text-control-dotnet-server/installation/npm/angular/llms-full.txt). The code below uses an Angular 19 application with a root module, as the tutorials do.

### How the Application Connects to the Backend

```text
Angular application (ng serve, http://localhost:4200)
    |
    |-- tx-document-editor ---- WebSocket ----> /api/TXWebSocket
    |-- tx-document-viewer ---- HTTP ---------> /TextControl (controller of the viewer)
    |-- DocumentService ------- HTTP ---------> /Document (your controller)
    v
ASP.NET Core backend with TX Text Control .NET Server, for example https://localhost:7118
```

One ASP.NET Core backend serves all three connections. Set it up as described in [Configure Program.cs for Separate Frontends](https://docs.textcontrol.com/tx-text-control-dotnet-server/integration/aspnet-core/#configure-program-cs-for-separate-frontends) and add the controller of [Add a Document Controller](https://docs.textcontrol.com/tx-text-control-dotnet-server/integration/aspnet-core/#add-a-document-controller). Place a *template.docx* in the *Documents* folder of the backend.

During development, the Angular development server can forward all three connections to the backend, so the application calls its own origin and the backend needs no CORS policy for it. In production, the application uses the public address of the backend.

### Add Both Components to the Application

Install both packages as [npm Packages: Angular](https://docs.textcontrol.com/tx-text-control-dotnet-server/installation/npm/angular/#install-the-packages) describes; an application with both needs an Angular version from 18.2.14 to 21. In an application with a root module, *src/app/app.module.ts* imports both modules, declares the components of your application that show them and provides `HttpClient` for the document service:

```typescript
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { provideHttpClient } from '@angular/common/http';
import { DocumentEditorModule } from '@txtextcontrol/tx-ng-document-editor';
import { DocumentViewerModule } from '@txtextcontrol/tx-ng-document-viewer';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { EditorComponent } from './editor/editor.component';
import { ViewerComponent } from './viewer/viewer.component';

@NgModule({
  declarations: [
    AppComponent,
    EditorComponent,
    ViewerComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    DocumentEditorModule,
    DocumentViewerModule
  ],
  providers: [provideHttpClient()],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

`DocumentEditorModule` provides the `tx-document-editor` component and `DocumentViewerModule` the `tx-document-viewer` component. `EditorComponent` and `ViewerComponent` are the components of your application that show them; their code follows below. The routes in *src/app/app-routing.module.ts* map them, for example to `editor` and `viewer`.

### Set the Backend Address per Environment

The editor, the viewer and the document service need the address of the backend, and the address differs between development and production. Angular environment files keep it in one place per build configuration. Create them with the Angular CLI:

```bash
ng generate environments
```

The command creates *src/environments/environment.ts* and *src/environments/environment.development.ts* and configures *angular.json* to replace the first file with the second in the `development` configuration, which `ng serve` uses. `ng build` uses the `production` configuration and *environment.ts*.

Set the production addresses in *src/environments/environment.ts*:

```typescript
export const environment = {
  backendUrl: 'https://backend.example.com',
  webSocketUrl: 'wss://backend.example.com/api/TXWebSocket'
};
```

During development, the application uses the address of the development server, which forwards the requests to the backend. Set them in *src/environments/environment.development.ts*:

```typescript
export const environment = {
  backendUrl: 'http://localhost:4200',
  webSocketUrl: 'ws://localhost:4200/api/TXWebSocket'
};
```

The files are part of the application that the browser loads, so they must not contain secrets. The editor connects with `wss`, or `ws` when the address uses HTTP. The components read their inputs once, when they're initialized; changing a bound value such as `webSocketURL` later has no effect on a component that already runs.

### Proxy the Backend During Development

Create *src/proxy.conf.json*, which forwards the paths of the editor, the viewer and the document controller to the backend:

```json
{
  "/api/TXWebSocket": {
    "target": "https://localhost:7118",
    "secure": false,
    "ws": true
  },
  "/TextControl": {
    "target": "https://localhost:7118",
    "secure": false
  },
  "/Document": {
    "target": "https://localhost:7118",
    "secure": false
  }
}
```

Then add the file to the options of the `serve` target in *angular.json*:

```json
"serve": {
  "options": {
    "proxyConfig": "src/proxy.conf.json"
  }
}
```

The code works as follows:

- `/api/TXWebSocket` covers the script of the editor and its WebSocket connection. `"ws": true` forwards the WebSocket connection as well.
- `/TextControl` covers the requests of the viewer, and `/Document` the document controller.
- `"secure": false` accepts the development certificate of the backend. Replace the port with the HTTPS port of your backend.

Restart `ng serve` after you change the proxy configuration. If the proxy reports `ECONNREFUSED` for a `localhost` target, use `127.0.0.1` in the target instead. Without the proxy, set the backend address in *environment.development.ts* directly and allow the origin `http://localhost:4200` in the CORS policy of the backend.

### Load and Save Documents Through a Service

A service encapsulates the calls to the document controller of the backend. Create *src/app/document.service.ts*:

```typescript
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from '../environments/environment';

export interface DocumentData {
  name: string;
  data: string;
}

@Injectable({ providedIn: 'root' })
export class DocumentService {
  constructor(private http: HttpClient) {}

  load(name: string) {
    return this.http.get<DocumentData>(environment.backendUrl + '/Document/Load', { params: { name: name } });
  }

  save(document: DocumentData) {
    return this.http.post<boolean>(environment.backendUrl + '/Document/Save', document);
  }
}
```

`DocumentData` matches the class of the controller: the name of the document and its base64-encoded content, the form in which both components save and load binary formats.

### Program the Editor From TypeScript

The editor's API is the global `TXTextControl` object, which the component loads with its script. When the object is available, the component fires the `txDocumentEditorLoaded` event on `document`; the editor is ready for API calls after its own `textControlLoaded` event. *src/app/editor/editor.component.ts* loads the template when the editor is ready, saves it back on request and removes the editor when the route changes:

```typescript
import { Component, HostListener, OnDestroy } from '@angular/core';
import { DocumentService } from '../document.service';
import { environment } from '../../environments/environment';

declare const TXTextControl: any;

@Component({
  selector: 'app-editor',
  templateUrl: './editor.component.html',
  standalone: false
})
export class EditorComponent implements OnDestroy {
  webSocketUrl = environment.webSocketUrl;

  constructor(private documents: DocumentService) {}

  @HostListener('document:txDocumentEditorLoaded')
  onTxDocumentEditorLoaded() {
    TXTextControl.addEventListener('textControlLoaded', () => {
      this.documents.load('template.docx').subscribe(result => {
        TXTextControl.loadDocument(TXTextControl.StreamType.WordprocessingML, result.data);
      });
    });
  }

  save() {
    TXTextControl.saveDocument(TXTextControl.StreamType.WordprocessingML, (result: any) => {
      this.documents.save({ name: 'template.docx', data: result.data }).subscribe();
    });
  }

  ngOnDestroy() {
    // Close the connection and remove the editor before Angular destroys the component
    if (typeof TXTextControl !== 'undefined') {
      TXTextControl.removeFromDom();
    }
  }
}
```

The template *src/app/editor/editor.component.html* binds the address and adds a button:

```html
<button (click)="save()">Save</button>

<tx-document-editor
  width="1000px"
  height="600px"
  [webSocketURL]="webSocketUrl">
</tx-document-editor>
```

The code works as follows:

- `declare const TXTextControl: any;` tells TypeScript about the global [TXTextControl](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript/txtextcontrol/llms-full.txt) object. The packages include no type definitions for it.
- `@HostListener('document:txDocumentEditorLoaded')` runs when the script is loaded. [addEventListener](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript/txtextcontrol/addeventlistener-method/llms-full.txt) then waits for `textControlLoaded` before the document is requested.
- [loadDocument](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript/txtextcontrol/loaddocument-method/llms-full.txt) replaces the content of the editor with the base64-encoded DOCX from the service.
- [saveDocument](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript/txtextcontrol/savedocument-method/llms-full.txt) passes a [SaveDocumentResult](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript/savedocumentresult/llms-full.txt) to its callback, whose [data](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript/savedocumentresult/data-property/llms-full.txt) the service posts to the backend.
- `ngOnDestroy` runs when Angular destroys the component, for example when the user navigates to another route. [TXTextControl.removeFromDom](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript/txtextcontrol/removefromdom-method/llms-full.txt) closes the WebSocket connection gracefully and removes the editor, its event handlers and its CSS references.

To change settings such as the edit mode while the editor runs, call the API instead of changing the bound input, for example [TXTextControl.setEditMode](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript/txtextcontrol/seteditmode-method/llms-full.txt). [Loading and Saving Documents](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/load-save/llms-full.txt) and [JavaScript API](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/javascript-api/llms-full.txt) describe the API in detail.

### Program the Viewer From TypeScript

The viewer's API is the global `TXDocumentViewer` object, and the viewer fires `documentViewerLoaded` on `window` when it's ready. Request the document inside the handler, not earlier, so that it never arrives before the viewer can take it. *src/app/viewer/viewer.component.ts*:

```typescript
import { Component, HostListener } from '@angular/core';
import { DocumentService } from '../document.service';
import { environment } from '../../environments/environment';

declare const TXDocumentViewer: any;

@Component({
  selector: 'app-viewer',
  templateUrl: './viewer.component.html',
  standalone: false
})
export class ViewerComponent {
  backendUrl = environment.backendUrl;

  constructor(private documents: DocumentService) {}

  @HostListener('window:documentViewerLoaded')
  onDocumentViewerLoaded() {
    this.documents.load('template.docx').subscribe(result => {
      TXDocumentViewer.loadDocument(result.data, result.name);
    });
  }

  async saveAsPdf() {
    const result = await TXDocumentViewer.saveDocument(TXDocumentViewer.StreamType.AdobePDF,
      { mergeFormFields: true, embedAnnotations: true });
    const data = await result.base64();

    this.documents.save({ name: 'template.pdf', data: data }).subscribe();
  }
}
```

The template *src/app/viewer/viewer.component.html*:

```html
<button (click)="saveAsPdf()">Save as PDF</button>

<tx-document-viewer
  width="1000px"
  height="600px"
  [basePath]="backendUrl">
</tx-document-viewer>
```

The code works as follows:

- [TXDocumentViewer.loadDocument](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript-document-viewer/documentviewercontroller/loaddocument-method/llms-full.txt) takes the base64-encoded document and its name.
- [TXDocumentViewer.saveDocument](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript-document-viewer/documentviewercontroller/savedocument-method/llms-full.txt) returns a promise. The [DocumentSaveSettings](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript-document-viewer/documentsavesettings/llms-full.txt) merge the form field values and embed the annotations into the PDF, and [base64](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript-document-viewer/documentsaveresult/base64-method/llms-full.txt) returns the saved document as base64.
- `basePath` is the address of the backend, during development the address of the development server. When the application calls the backend on another origin, the backend's CORS policy must allow it.

[Saving Documents](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-viewer/saving/llms-full.txt) in the Document Viewer section describes the formats and settings.

### Use the Editor on Routes

Changing the route doesn't reload an Angular application. When a route shows the editor a second time without removing the first instance, the new component fails with the error "The editor is already initialized." Choose one of two approaches:

- **Remove the editor with the component.** Call `TXTextControl.removeFromDom` in `ngOnDestroy`, as the `EditorComponent` above does. Each visit of the route then creates a new editor with a new connection and loads the document again. Save the document before the user leaves the route; unsaved content is lost.
- **Keep one editor alive.** Place the editor in a component that is always rendered, and move its element into a placeholder of each routed component that shows it. A `CanDeactivate` guard moves the editor back and resets its content before the route changes. Moving the editor avoids a new initialization on the server and in the browser. The blog article [Reuse Persistent Document Editor Components in Angular SPA Applications](https://www.textcontrol.com/blog/2025/04/03/reuse-persistent-document-editor-components-in-angular-spa-applications/) and the repository [TXTextControl.Angular.SPA](https://github.com/TextControl/TXTextControl.Angular.SPA) contain the complete implementation.

### Pass Access Tokens

When the backend protects the editor and the viewer with access tokens, the Angular application requests a token from its backend first. The Angular component of the editor has no setting for additional WebSocket sub-protocols, so the Text Control Angular security sample adds the token to the query string of `webSocketURL` and creates the component only after the token is available:

```html
<div *ngIf="accessToken">
  <tx-document-editor
    width="1000px"
    height="600px"
    webSocketURL="wss://backend.example.com/api/TXWebSocket?access_token={{ accessToken }}">
  </tx-document-editor>
</div>
```

The viewer component has no setting for a token: it splits a query string off `basePath` and sends it only with the request for the viewer script, so the other requests of the viewer carry no token. Serve the application from the origin of the backend and protect the viewer with the sign-in cookie of the user, as [Frontends on Another Origin](https://docs.textcontrol.com/tx-text-control-dotnet-server/security/authentication/#frontends-on-another-origin) describes.

Tokens in URLs can appear in the logs of web servers and proxies. [Access Tokens](https://docs.textcontrol.com/tx-text-control-dotnet-server/security/access-tokens/llms-full.txt) and [WebSocket Security](https://docs.textcontrol.com/tx-text-control-dotnet-server/security/websockets/llms-full.txt) describe the server side and the alternatives. When users sign in to the backend with a cookie instead, [Require Signed-In Users](https://docs.textcontrol.com/tx-text-control-dotnet-server/security/authentication/#require-signed-in-users) protects the endpoints without tokens in the URL. The blog article [Implementing a Security Middleware for Angular Document Editor Applications in C#](https://www.textcontrol.com/blog/2024/10/14/implementing-a-security-middleware-for-angular-document-editor-applications/) shows the complete token sample.

### Use a Node.js WebSocket Server

The editor component doesn't depend on ASP.NET Core: any server that provides the TX Text Control WebSocket endpoint works. A Node.js application with the `WebSocketServer` class of the `@txtextcontrol/tx-websocket-server` package can take the place of the middleware and forward the connection to the Document Editor backend. [Document Editor: Node.js](https://docs.textcontrol.com/tx-text-control-dotnet-server/getting-started/document-editor/nodejs/llms-full.txt) sets up the backend and the `WebSocketServer`, and [Connect to a Backend on Another Machine](https://docs.textcontrol.com/tx-text-control-dotnet-server/getting-started/document-editor/nodejs/#connect-to-a-backend-on-another-machine) lists its settings. Point the component to the endpoint of the Node.js application:

```html
<tx-document-editor
  width="1000px"
  height="500px"
  webSocketURL="ws://localhost:8080/TXWebSocket">
</tx-document-editor>
```

`WebSocketServer` also serves the editor script that the component requests from `/TXWebSocket/GetResource`. The Node.js application covers only the editor: the Document Viewer and your own document endpoints, such as the `DocumentService` above, still need an ASP.NET Core backend. The blog article [Creating an Angular Document Editor Application with a Node.js WebSocket Server](https://www.textcontrol.com/blog/2023/08/24/creating-an-angular-document-editor-application-with-a-nodejs-websocket-server/) builds the same setup. It dates from before version 33.0, when the backend was a Windows service, so start the backend as the Getting Started tutorial describes.

### Build and Host the Application

`ng build` compiles the application with the production configuration and *environment.ts*. With the application builder of current Angular versions, the files to deploy are in *dist/project-name/browser*, where *project-name* is the name of your project. If the application doesn't run at the root of its server, set its base URL with `ng build --base-href`. Two setups are common:

- **Separate hosting.** The Angular application and the backend run on different servers or origins. *environment.ts* contains the public address of the backend, and the CORS policy of the backend allows the origin of the application.
- **One ASP.NET Core project serves both.** Visual Studio's project templates for Angular with ASP.NET Core create a client project and a server project. In development, the server starts the Angular development server; in production, it serves the built application with `app.UseDefaultFiles()`, `app.UseStaticFiles()` and `app.MapFallbackToFile("/index.html")`. The application and the backend then share one origin, and *environment.ts* contains that origin.

Behind a load balancer, keep every WebSocket connection of an editor on the same server, as [Scaling and Load Balancing](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/architecture/#scaling-and-load-balancing) explains. [Host the Connection](https://docs.textcontrol.com/tx-text-control-dotnet-server/integration/websockets/#host-the-connection) describes proxies and hosting platforms.

### Next Steps

- [WebSocket Architecture](https://docs.textcontrol.com/tx-text-control-dotnet-server/integration/websockets/llms-full.txt): the connection between the editor and the backend in detail.
- [ASP.NET Core](https://docs.textcontrol.com/tx-text-control-dotnet-server/integration/aspnet-core/llms-full.txt): the backend for the Angular application.
- [JavaScript API](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/javascript-api/llms-full.txt): program the editor from TypeScript.
- [REST Services](https://docs.textcontrol.com/tx-text-control-dotnet-server/integration/rest-services/llms-full.txt): call document services from Angular.

---

## Nearby

- **Previous:** [ASP.NET Core](https://docs.textcontrol.com/tx-text-control-dotnet-server/integration/aspnet-core/llms-full.txt)
- **Next:** [React](https://docs.textcontrol.com/tx-text-control-dotnet-server/integration/react/llms-full.txt)
