> ## Documentation Index
> Fetch the complete documentation index at: https://omniloy.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Angular

> SofIA SDK integration with Angular

This guide details how to integrate SofIA SDK in Angular applications, including the necessary configuration for using Web Components and implementing callbacks.

## Initial setup

### 1. Package installation

```bash theme={null}
npm install @omniloy/sofia-sdk
```

### 2. Module configuration

Angular requires specific configuration to support Web Components:

```typescript theme={null}
// app.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

// Import SofIA SDK
import '@omniloy/sofia-sdk';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  providers: [],
  bootstrap: [AppComponent],
  schemas: [CUSTOM_ELEMENTS_SCHEMA] // Required for Web Components
})
export class AppModule { }
```

### 3. Standalone components configuration (Angular 15+)

For applications using Standalone Components:

```typescript theme={null}
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import '@omniloy/sofia-sdk';

bootstrapApplication(AppComponent, {
  providers: []
});
```

```typescript theme={null}
// app.component.ts
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

@Component({
  selector: 'app-root',
  standalone: true,
  templateUrl: './app.component.html',
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppComponent {
  // Component implementation
}
```

## Component implementation

### HTML template

```html theme={null}
<!-- app.component.html -->
<sofia-sdk
  #sofia
  [attr.apikey]="apiKey"
  [attr.userid]="userId"
  [attr.patientid]="patientId"
  [attr.templateid]="templateId"
  [attr.template]="templateJson"
  [attr.isopen]="isOpen ? 'true' : 'false'"
  language="en">
</sofia-sdk>
```

<Info>
  SofIA SDK delivers data to your app through **property callbacks** — assign `handleReport`, `setIsOpen`, and `setGetLastReport` as element properties (via `@ViewChild` in `ngAfterViewInit`, shown below). The SDK does **not** emit `handle-report`/`set-is-open` DOM events, so Angular event bindings like `(handle-report)` will not fire. (The only DOM event the SDK dispatches is `sofia:transcriber-url-changed`.)
</Info>

### TypeScript component

```typescript theme={null}
// app.component.ts
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
  @ViewChild('sofia') sofia!: ElementRef;

  apiKey = 'your-api-key'; // endpoint auto-resolved for newer keys (baseurl only if your key needs it)
  userId = 'user_12345';
  patientId = 'patient_67890';
  templateId = 'clinical-notes-v1';
  isOpen = false;

  // JSON Schema for clinical configuration
  templateJson = JSON.stringify({
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "clinical_notes",
    "type": "object",
    "properties": {
      "diagnosis": {
        "type": "string",
        "description": "Primary diagnosis"
      }
    },
    "required": ["diagnosis"]
  });

  // Assign callbacks as element PROPERTIES once the web component is in the DOM
  ngAfterViewInit() {
    const el = this.sofia.nativeElement as any;

    // Called when a report is generated
    el.handleReport = (report: any) => {
      console.log('Report received:', report);
      // Process the generated report — e.g. send it to your backend or EHR
    };

    // Called when the open state changes
    el.setIsOpen = (isOpen: boolean) => {
      this.isOpen = isOpen;
      console.log('Open state:', isOpen);
    };
  }

  // Method to open/close programmatically
  toggleSofIA() {
    this.isOpen = !this.isOpen;
  }
}
```

## Retrieving the last report

Use the `set-get-last-report` callback to access the last generated report for the current patient/user session:

```typescript theme={null}
ngAfterViewInit() {
  const sofiaElement = document.querySelector('sofia-sdk') as any;
  if (sofiaElement) {
    sofiaElement.setGetLastReport = (getLastReportFn: () => Promise<object | undefined>) => {
      this.getLastReport = getLastReportFn;
    };
  }
}

// Later, retrieve the last report
async fetchLastReport() {
  const report = await this.getLastReport?.();
  if (report) {
    console.log('Last report:', report);
  }
}
```

<Tip>
  Add `debug="true"` to the `<sofia-sdk>` element to enable verbose console logging. This is helpful during development to trace SDK lifecycle events, connection status, and configuration validation.

  ```html theme={null}
  <sofia-sdk debug="true" ...></sofia-sdk>
  ```
</Tip>

## Patient data updates

Update patient context dynamically by changing the bound properties:

```typescript theme={null}
// Update patient for new consultation
updatePatient(newId: string, data: object) {
  this.patientId = newId;
  this.patientData = JSON.stringify(data);
}
```

See [Patient Data](/sofia/en/sdk/patient-data) for the complete data structure reference.

## Advanced configuration

### TypeScript interfaces

For better typing, you can define interfaces for the data:

```typescript theme={null}
// interfaces/sofia.interface.ts
export interface SofiaReport {
  diagnosis: string;
  timestamp: string;
  userId: string;
  patientId: string;
}

export interface PatientData {
  fullName?: string;
  birthDate?: string;
  phone?: string;
  address?: string;
  extraData?: Record<string, unknown>;
}
```

### Angular service

You can encapsulate SofIA logic in a service:

```typescript theme={null}
// services/sofia.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { SofiaReport } from '../interfaces/sofia.interface';

@Injectable({
  providedIn: 'root'
})
export class SofiaService {
  private reportSubject = new BehaviorSubject<SofiaReport | null>(null);
  private isOpenSubject = new BehaviorSubject<boolean>(false);

  // Public observables
  report$: Observable<SofiaReport | null> = this.reportSubject.asObservable();
  isOpen$: Observable<boolean> = this.isOpenSubject.asObservable();

  // Methods to handle reports
  handleReport(report: SofiaReport) {
    this.reportSubject.next(report);
    // Here you can add additional logic like saving to localStorage
    // or sending to server
  }

  // Methods to handle state
  setIsOpen(isOpen: boolean) {
    this.isOpenSubject.next(isOpen);
  }

  // Method to generate template dynamically
  generateTemplate(specialty: string): string {
    const baseSchema = {
      "$schema": "http://json-schema.org/draft-07/schema#",
      "title": `${specialty} Consultation`,
      "type": "object"
    };

    // Customize by specialty
    switch(specialty) {
      case 'cardiology':
        return JSON.stringify({
          ...baseSchema,
          "properties": {
            "diagnosis": { "type": "string" },
            "heart_rate": { "type": "number" },
            "blood_pressure": { "type": "string" }
          }
        });
      case 'dermatology':
        return JSON.stringify({
          ...baseSchema,
          "properties": {
            "diagnosis": { "type": "string" },
            "lesion_type": { "type": "string" },
            "location": { "type": "string" }
          }
        });
      default:
        return JSON.stringify({
          ...baseSchema,
          "properties": {
            "diagnosis": { "type": "string" },
            "treatment": { "type": "string" }
          }
        });
    }
  }
}
```

### Using the service in component

```typescript theme={null}
// app.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { SofiaService } from './services/sofia.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit, OnDestroy {
  private destroy$ = new Subject<void>();

  userId = 'user_12345';
  patientId = 'patient_67890';
  templateId = 'clinical-notes-v1';
  isOpen = false;
  templateJson = '';

  constructor(private sofiaService: SofiaService) {}

  ngOnInit() {
    // Subscribe to state changes
    this.sofiaService.isOpen$
      .pipe(takeUntil(this.destroy$))
      .subscribe(isOpen => {
        this.isOpen = isOpen;
      });

    // Subscribe to reports
    this.sofiaService.report$
      .pipe(takeUntil(this.destroy$))
      .subscribe(report => {
        if (report) {
          console.log('New report:', report);
          // Process report
        }
      });

    // Configure initial template
    this.templateJson = this.sofiaService.generateTemplate('general');
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }

  onReport(report: any) {
    this.sofiaService.handleReport(report);
  }

  onSetIsOpen(isOpen: boolean) {
    this.sofiaService.setIsOpen(isOpen);
  }
}
```

## Important considerations

### Change detection

Angular may require manual change detection in some cases:

```typescript theme={null}
import { ChangeDetectorRef } from '@angular/core';

constructor(private cdr: ChangeDetectorRef) {}

onReport(report: any) {
  // Process report
  this.cdr.detectChanges(); // Force detection if necessary
}
```

### Component lifecycle

```typescript theme={null}
ngAfterViewInit() {
  // Web Component is available after view initialization
  const sofiaElement = document.querySelector('sofia-sdk');
  if (sofiaElement) {
    // Additional configuration if necessary
  }
}
```

### Error handling

```typescript theme={null}
onReport(report: any) {
  try {
    // Process report
    this.processReport(report);
  } catch (error) {
    console.error('Error processing report:', error);
    // Handle error appropriately
  }
}
```

For a complete list of error messages and their resolution steps, see the [Error Reference](/sofia/en/sdk/error-reference).

<Warning>
  Never expose your `apikey` in client code in production. Use a backend proxy to inject the API key server-side. See the [Installation guide](/sofia/en/sdk/installation#production-security) for details.
</Warning>

## Next steps

1. [JavaScript integration](/sofia/en/sdk/vanilla)
2. [React integration](/sofia/en/sdk/react)
3. [Required properties reference](/sofia/en/sdk/required-properties)
4. [Optional properties reference](/sofia/en/sdk/optional-properties)
