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.