Angular installation
Angular App Testimonial Widget Embed
Angular will not run a script tag you write in a template, by design. So the StoryProof embed becomes a small standalone component: the template binds the widget id onto an empty div, and afterNextRender adds the script once, in the browser, after that div exists.
- A standalone component with your widget id
- Script loading that skips the server render
- How to handle router navigation
Code generator
The component, with your widget id
Enter the widget id and copy both files. The component works in apps with or without server side rendering, because the script is added only in the browser.
Part 1 · The standalone component
src/app/storyproof-widget.component.ts
// src/app/storyproof-widget.component.ts (needs signal inputs and afterNextRender)
import { Component, afterNextRender, inject, input } from '@angular/core';
import { DOCUMENT } from '@angular/common';
const SRC = 'https://app.storyproof.io/embed.js';
@Component({
selector: 'app-storyproof-widget',
standalone: true,
// attr. binding writes a real data attribute; leave the div empty.
template: `<div [attr.data-storyproof-widget]="widgetId()"></div>`,
})
export class StoryProofWidgetComponent {
widgetId = input.required<string>();
private readonly doc = inject(DOCUMENT);
constructor() {
// Runs in the browser only, after the div is in the DOM.
afterNextRender(() => {
if (this.doc.querySelector(`script[src="${SRC}"]`)) return;
const script = this.doc.createElement('script');
script.src = SRC;
script.async = true;
this.doc.body.appendChild(script);
});
}
}Part 2 · Using it in a template
any component template
<!-- any template; add StoryProofWidgetComponent to that component's imports -->
<section class="testimonials">
<h2>What customers say</h2>
<app-storyproof-widget widgetId="YOUR_WIDGET_ID" />
</section>
<!-- Linking to a page with a widget from elsewhere in the app?
A plain href (no routerLink) loads the page fresh. -->
<a href="/customers">Customer stories</a>Run ng serve, open the route directly in the browser, and check the console for errors before shipping a production build.
The code is generated in your browser; nothing is sent.
The problem, solved
Angular and third-party embeds: the usual failures and the fix
- Problem 1
My pasted script tag does nothing
Angular’s template guide states it does not support the script element in templates, so the vendor snippet loses its most important line.
How StoryProof solves it
The StoryProof embed is an empty div plus one async script. The div stays in the template; the component adds the script through the injected document.
- Problem 2
Binding the id throws an error
Angular template bindings target DOM properties by default, and a data attribute has no property behind it.
How StoryProof solves it
Use [attr.data-storyproof-widget], the attr. prefix Angular documents for attributes without a DOM property. The div then carries the widget id the embed expects.
- Problem 3
Server rendering crashes on document
Touching the browser document during a server render fails or produces markup the client then fights.
How StoryProof solves it
afterNextRender callbacks run on browser platforms only, so the script is never added on the server. The div still renders there, empty, ready for the script.
- Problem 4
Three widgets load three scripts
A component reused across a page would otherwise append its script once per instance.
How StoryProof solves it
The component checks for an existing tag first. One embed.js fills every StoryProof div on the page.
- Problem 5
Customer content in a production app
Rendering unreviewed text from the public inside your app is a risk somebody has to own.
How StoryProof solves it
Only testimonials you approve in StoryProof can appear, and each widget shows just the ones you chose.
In StoryProof
Add it to your Angular app
Step 1 of 5
Get the widget id
Build the widget in StoryProof with its layout and chosen testimonials. The id is the value of data-storyproof-widget in the embed code.
Step 2 of 5
Create the component
Add the component file from the generator. It is standalone, so import it into the components that use it rather than a module.
Step 3 of 5
Render it where proof belongs
Place the selector in a template with the widget id. Different routes can pass different ids.
Step 4 of 5
Allow the script in your CSP
If your server sends a Content-Security-Policy header, add https://app.storyproof.io to script-src and connect-src, and allow any further hosts the console reports for images or media.
Step 5 of 5
Test arrival both ways
Load the widget route directly, then reach it from another route by clicking a link. If the second way leaves it empty, use the plain href pattern in the usage file.
Before you start
Angular caveats
Router navigation
The script fills the StoryProof divs present when it runs. A widget rendered later by a router navigation may stay empty. Link to widget pages with a plain href so they load fresh, and report it to StoryProof support.
Not in index.html
A script tag in index.html can run before Angular has rendered the div, which is why the component adds it after render instead.
No npm package
There is no StoryProof Angular package; this component is the integration. Widgets show a "Powered by StoryProof" badge until Pro.
Older Angular versions
Signal inputs and afterNextRender arrived in recent releases. On older versions, use an @Input and ngAfterViewInit with a browser platform check instead.
Angular docs as of 2026-11-13
APIs follow angular.dev as read on 2026-11-13. The component has not been run against a live StoryProof widget by a person yet.
FAQ
Questions people ask
Short answers to what usually comes up next.
Can I add embed.js to the scripts array in angular.json?
Angular’s workspace docs describe that option for JavaScript files in your workspace, with paths relative to the project. A hosted script is better added at run time as the component does.
Do I need CUSTOM_ELEMENTS_SCHEMA?
No. The widget is a plain div with a data attribute, which Angular already knows. That schema is for unknown element names.
Can I bypass sanitisation and bind the snippet with innerHTML?
Angular sanitises bound HTML and removes the script element, and its security guide warns against bypassing that. The component avoids the question entirely.
Will the testimonials be in my server rendered HTML?
No. The server renders the empty div and the visitor’s browser fills it after the script loads.
Still have a question? Ask the StoryProof team or book a demo.
Get a widget id for your Angular app
Create a workspace, approve a few testimonials, and drop the id into the component.
Sources
- Template syntax, Angular documentation. Checked 2026-11-13.
- Binding dynamic text, properties and attributes, Angular documentation. Checked 2026-11-13.
- afterNextRender, Angular API reference. Checked 2026-11-13.
- Security, Angular documentation. Checked 2026-11-13.
- Workspace configuration, Angular documentation. Checked 2026-11-13.
Last updated 2026-11-13.