Angular
npm install @walangstudio/badgetrip-core @walangstudio/badgetrip-angular @walangstudio/badgetrip-assetsNeeds Angular 17.1 or newer (it uses signal inputs). This uses the engine from Getting started.
Provide the engine
// app.config.ts
import type { ApplicationConfig } from '@angular/core';
import { provideBadgetrip } from '@walangstudio/badgetrip-angular';
import { engine } from './badges';
export const appConfig: ApplicationConfig = {
providers: [provideBadgetrip(engine)],
};Show the badges and record something
// trophies.component.ts
import { Component, inject, input } from '@angular/core';
import { AchievementBadgeComponent, BadgetripService } from '@walangstudio/badgetrip-angular';
@Component({
selector: 'app-trophies',
standalone: true,
imports: [AchievementBadgeComponent],
template: `
<button (click)="log()">Done for today</button>
@if (badges.error()) {
<p>Couldn't load achievements.</p>
}
<div class="grid">
@for (b of badges(); track b.code) {
<badgetrip-achievement-badge [achievement]="b" />
}
</div>
`,
styles: `.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 1rem; }`,
})
export class TrophiesComponent {
private readonly badgetrip = inject(BadgetripService);
readonly userId = input.required<string>();
readonly badges = this.badgetrip.catalog(this.userId);
async log() {
const { unlocked } = await this.badgetrip.engine.emit({
id: crypto.randomUUID(),
actor: this.userId(),
type: 'habit.done',
ts: Date.now(),
payload: {},
});
if (unlocked.length) console.log('Unlocked', unlocked);
}
}Each query on BadgetripService returns a signal. Pass a plain string or a signal like this.userId, and the query re-runs when the signal changes and after every emit through badgetrip.engine. Failures show up in .error() and in Angular's ErrorHandler.
Create queries in a field initializer or constructor, where Angular's injection context exists. Anywhere else, pass one in: this.badgetrip.catalog(id, { injector }).
Other queries
score, streak, leaderboard, tier, escalator, progress and achievements all work like catalog.
Custom icons
import { createIconResolver } from '@walangstudio/badgetrip-assets';
provideBadgetrip(engine, { icons: createIconResolver({ icons: { medal: { src: '/art/medal.png' } } }) });For a badge of your own design, achievementIcon(signal) returns a signal of the right image.
Celebrate unlocks
Add notifier to the provider: provideBadgetrip(engine, { notifier: () => ({ actor: inject(Auth).userId }) }). A function runs in the injection context, so it can read services; a plain options object works too. Every unlock then pops up on top of the page, in the browser only. inject(BADGETRIP_NOTIFIER)?.update({ sound: true }) changes sound settings later. See Unlock celebrations. For a custom UI, inject(BadgetripService).unlocks() returns { queue, dismiss, clear }, where queue is a signal.
Themes
Add theme to the provider, and switch it from the service:
import { themes } from '@walangstudio/badgetrip-assets';
provideBadgetrip(engine, { theme: themes.dark, notifier: true });
// in a component
inject(BadgetripService).setTheme(themes.arcade);Badges, the notifier and unlocks() follow BadgetripService.theme. Colors only go on the page in the browser. The themes guide covers making your own.