Angular Integration Guide

Install IssueCapture in Angular

Add bug reporting to an Angular app. Works with Angular 15+, TypeScript, standalone components, and Angular Router.

Quick Summary

Time: ~10 minutes (incl. Jira OAuth)
Prerequisites: Angular 15+, Jira Cloud account
Method: AppComponent inject or index.html tag
Works with: Angular 15+, Router, NgRx, Universal

Prerequisites

  • Angular 15 or higher
  • TypeScript 4.9+
  • Jira Cloud account (Software or JSM)
  • IssueCapture account (free — includes 10 issues/month)

Step-by-Step Installation

Follow these steps to get up and running in minutes.

1

Get Your API Key

Sign up for IssueCapture and grab your widget API key

  • Go to issuecapture.com/signup and create a free account
  • Connect your Jira instance via OAuth (about 30 seconds)
  • Open the Widgets page and create a new widget
  • Click the "Keys" tab on your widget and copy the API key (starts with "ic_")
2

Add the API key to your environment files

Angular uses environment.ts and environment.prod.ts for build-time config

  • Angular CLI swaps environment.ts → environment.prod.ts via fileReplacements during production builds
  • You can use separate API keys for dev and production widgets if you want
  • Don't commit production keys to a public repo — load via CI secrets if needed
  • No restart required after editing environment files — `ng serve` picks them up on next rebuild
// src/environments/environment.ts
export const environment = {
  production: false,
  issueCaptureApiKey: 'ic_your_api_key_here',
};

// src/environments/environment.prod.ts
export const environment = {
  production: true,
  issueCaptureApiKey: 'ic_your_production_api_key',
};
3

Inject the widget from AppComponent

Add a `<script type="module">` after Angular bootstraps

  • The widget is an ESM module — type="module" is the gotcha here
  • AppComponent runs once for the app, so the widget is initialised exactly once
  • The SSR guard (`typeof document === "undefined"`) is harmless if you don't use Angular Universal
  • You can move this into a service if you want to lazy-load it instead
// src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { environment } from '../environments/environment';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
  ngOnInit(): void {
    // Skip on the server during SSR
    if (typeof document === 'undefined') return;

    const apiKey = environment.issueCaptureApiKey;
    const s = document.createElement('script');
    s.type = 'module';
    // Inline ESM module — runs on append. type="module" is required
    // because widget.js exposes ES exports.
    s.textContent = `
      import IssueCapture from 'https://issuecapture.com/widget.js';
      IssueCapture.init({ apiKey: '${apiKey}' });
    `;
    document.body.appendChild(s);
  }
}
4

Or paste a static script tag in index.html

Simpler — fine if you can hardcode the key

  • index.html is not processed for environment.ts substitution — you have to paste the key directly
  • Use this only when you don't need to keep the key out of source control
  • For per-env keys, use Step 3 (AppComponent) instead
<!-- src/index.html -->
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Your Angular App</title>
</head>
<body>
  <app-root></app-root>

  <!-- IssueCapture Widget (ESM module) -->
  <script type="module">
    import IssueCapture from 'https://issuecapture.com/widget.js';
    IssueCapture.init({ apiKey: 'ic_your_api_key_here' });
  </script>
</body>
</html>
5

Trigger the widget from your own button

Skip the floating button and open the modal from a component

  • Optional-chain `window.IssueCapture?.open()` so it never throws if the script hasn't loaded yet
  • You can use this button alongside the floating one, or set `trigger: '#your-id'` on init to disable the floating one
  • aria-label keeps the button accessible even if it goes icon-only
// src/app/bug-report-button.component.ts
import { Component } from '@angular/core';

declare global {
  interface Window {
    IssueCapture?: {
      open: () => void;
      close: () => void;
      updateConfig: (cfg: Record<string, unknown>) => void;
    };
  }
}

@Component({
  selector: 'app-bug-report-button',
  template: `
    <button (click)="open()" aria-label="Report an issue">
      Report an issue
    </button>
  `,
})
export class BugReportButtonComponent {
  open(): void {
    window.IssueCapture?.open();
  }
}
6

Pre-fill the logged-in user (optional)

When auth state changes, push name and email through

  • Use `updateConfig` for runtime changes — `init` should only fire once per page
  • If the user signs out, you can call `updateConfig({ user: { name: '', email: '' } })` to clear it
  • Pre-fills are visible to the user before submit — they can still edit them
// src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { AuthService } from './services/auth.service'; // your own auth service

@Component({ /* ... */ })
export class AppComponent implements OnInit {
  constructor(private auth: AuthService) {}

  ngOnInit(): void {
    // Push user info whenever auth state changes.
    // updateConfig is cheap — call it freely. Do NOT re-call init().
    this.auth.user$.subscribe((user) => {
      if (!user) return;
      window.IssueCapture?.updateConfig({
        user: { name: user.name, email: user.email },
      });
    });
  }
}
7

Verify it works

Quick sanity check end-to-end

  • Start the dev server: `ng serve`
  • Open `http://localhost:4200` — you should see the floating Report Issue button
  • Open DevTools console and type `IssueCapture` — you should see the widget object
  • Click the button, submit a test issue, and confirm it lands in Jira

Troubleshooting

Common integration issues and how to solve them.

TypeScript: Property 'IssueCapture' does not exist on type 'Window'

Add a global declaration to your project (src/types/issuecapture.d.ts)

  • Create `src/types/issuecapture.d.ts` declaring `IssueCapture` on the global Window interface
  • Make sure the file is picked up by `tsconfig.app.json` `include`
  • See the IssueCaptureAPI surface in the docs for the full method list

environment.prod.ts isn't being used in production builds

  • Confirm `angular.json` has the fileReplacements entry for `production`
  • Build with `ng build --configuration=production` (the default for `ng build` in recent CLI versions)
  • Import via `from '../environments/environment'`, never via `from '../environments/environment.prod'` directly

Widget loads twice when navigating with Angular Router

  • Inject the script in AppComponent (which mounts once), not in route-level components
  • If you must run it from a routed component, guard with `if (window.IssueCapture) return` before injecting
  • For per-route config, use `IssueCapture.updateConfig()` — never re-call `init()`

"Domain not allowed" error (often shows up as CORS)

  • In the IssueCapture dashboard, open your widget and click the Domains tab
  • Add `localhost:4200` (Angular default) for local dev
  • Add your production domain — both www and non-www if you serve both
  • Subdomains need to be listed individually

Ready to get started?

Free plan includes 10 issues/month. No card needed — connect Jira and you're done.