
Security News
Re-Enabled GitHub Actions Expose Thousands of Repositories to Mini Shai-Hulud
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.
@schematichq/schematic-angular
Advanced tools
`schematic-angular` is a client-side Angular library for [Schematic](https://schematichq.com) which provides an injectable service to track events, check flags, and more. `schematic-angular` provides the same capabilities as [schematic-js](https://github.
schematic-angular is a client-side Angular library for Schematic which provides an injectable service to track events, check flags, and more. schematic-angular provides the same capabilities as schematic-js, for Angular apps.
npm install @schematichq/schematic-angular
# or
yarn add @schematichq/schematic-angular
# or
pnpm add @schematichq/schematic-angular
provideSchematicAdd provideSchematic to your application's providers. This works with both standalone and NgModule-based apps.
Standalone app (app.config.ts):
import { ApplicationConfig } from "@angular/core";
import { provideSchematic } from "@schematichq/schematic-angular";
export const appConfig: ApplicationConfig = {
providers: [
provideSchematic({ publishableKey: "your-publishable-key" }),
],
};
NgModule-based app:
import { NgModule } from "@angular/core";
import { provideSchematic } from "@schematichq/schematic-angular";
@NgModule({
providers: [
provideSchematic({ publishableKey: "your-publishable-key" }),
],
})
export class AppModule {}
You can also pass a pre-configured client:
import { Schematic } from "@schematichq/schematic-angular";
const client = new Schematic("your-publishable-key", { useWebSocket: true });
provideSchematic({ client });
To set the user context for events and flag checks, use the identify method on SchematicService:
import { Component, OnInit, inject } from "@angular/core";
import { SchematicService } from "@schematichq/schematic-angular";
@Component({ selector: "app-root", template: `<router-outlet />` })
export class AppComponent implements OnInit {
private schematic = inject(SchematicService);
ngOnInit() {
this.schematic.identify({
keys: { id: "my-user-id" },
company: {
keys: { id: "my-company-id" },
traits: { location: "Atlanta, GA" },
},
});
}
}
To learn more about identifying companies with the keys map, see key management in Schematic public docs.
Once you've set the context with identify, you can track events:
import { Component, inject } from "@angular/core";
import { SchematicService } from "@schematichq/schematic-angular";
@Component({
selector: "app-usage",
template: `<button (click)="onQuery()">Run Query</button>`,
})
export class UsageComponent {
private schematic = inject(SchematicService);
onQuery() {
this.schematic.track({ event: "query" });
}
}
If you want to record large numbers of the same event at once, or measure usage in terms of a unit like tokens or memory, you can optionally specify a quantity:
this.schematic.track({ event: "query", quantity: 10 });
Use flagValue$ to get an Observable of a flag's boolean value:
With async pipe:
import { Component, inject } from "@angular/core";
import { AsyncPipe } from "@angular/common";
import { SchematicService } from "@schematichq/schematic-angular";
@Component({
selector: "app-feature",
standalone: true,
imports: [AsyncPipe],
template: `
@if (isFeatureEnabled$ | async) {
<app-feature />
} @else {
<app-fallback />
}
`,
})
export class FeatureComponent {
private schematic = inject(SchematicService);
isFeatureEnabled$ = this.schematic.flagValue$("my-flag-key");
}
With Signals (Angular 16+):
import { Component, inject } from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop";
import { SchematicService } from "@schematichq/schematic-angular";
@Component({
selector: "app-feature",
standalone: true,
template: `
@if (isFeatureEnabled()) {
<app-feature />
} @else {
<app-fallback />
}
`,
})
export class FeatureComponent {
private schematic = inject(SchematicService);
isFeatureEnabled = toSignal(this.schematic.flagValue$("my-flag-key"), {
initialValue: false,
});
}
Use entitlement$ to get an Observable with detailed entitlement data including usage information:
import { Component, inject } from "@angular/core";
import { AsyncPipe } from "@angular/common";
import { SchematicService } from "@schematichq/schematic-angular";
@Component({
selector: "app-entitlement",
standalone: true,
imports: [AsyncPipe],
template: `
@if (isPending$ | async) {
<app-loader />
} @else if (entitlement$ | async; as entitlement) {
@if (entitlement.featureUsageExceeded) {
<div>
You have used all of your usage
({{ entitlement.featureUsage }} / {{ entitlement.featureAllocation }})
</div>
} @else if (entitlement.value) {
<app-feature />
} @else {
<app-no-access />
}
}
`,
})
export class EntitlementComponent {
private schematic = inject(SchematicService);
isPending$ = this.schematic.isPending$();
entitlement$ = this.schematic.entitlement$("my-flag-key");
}
Note: isPending$ checks if entitlement data has been loaded, typically via identify. It should be used to wrap flag and entitlement checks, but never the initial call to identify.
For features metered by credit burndown, the emitted entitlement also carries the company's credit position:
| Property | Type | Description |
|---|---|---|
creditId | string | undefined | The ID of the credit funding this feature |
creditSettled | number | undefined | The spendable balance, including any amount held by an open lease. This is the number to show end users |
creditRemaining | number | undefined | The balance available to fund new consumption, excluding any open lease hold |
creditReserved | number | undefined | The unspent amount held by an open credit lease, 0 when none is open |
All four are undefined when the feature is not credit-based.
@Component({
selector: "app-credit-feature",
standalone: true,
imports: [AsyncPipe],
template: `
@if (entitlement$ | async; as entitlement) {
@if (entitlement.value) {
<app-feature [creditsRemaining]="entitlement.creditSettled" />
} @else {
<app-out-of-credits />
}
}
`,
})
export class CreditFeatureComponent {
private schematic = inject(SchematicService);
entitlement$ = this.schematic.entitlement$("my-flag-key");
}
These values refresh with each flag check. For a balance that also updates on the credit partials arriving between checks, pipe creditId into creditBalance$ instead.
If a usage warning is configured on the entitlement, the emitted entitlement carries it as warningTiers, so you can warn a customer before they hit the limit rather than after:
| Property | Type | Description |
|---|---|---|
warningTiers | WarningTier[] | undefined | The usage warning thresholds configured on the entitlement, each a { key, value } pair in the entitlement's usage units. undefined when none are configured |
softLimit | number | undefined | For usage-based pricing, the soft limit for overage charges or the next tier boundary |
The dashboard writes a single tier under the key default.
import { Component, inject } from "@angular/core";
import { AsyncPipe } from "@angular/common";
import { map } from "rxjs";
import { SchematicService } from "@schematichq/schematic-angular";
@Component({
selector: "app-usage-warning",
standalone: true,
imports: [AsyncPipe],
template: `
@if (approachingLimit$ | async) {
<app-approaching-limit />
}
<app-feature />
`,
})
export class UsageWarningComponent {
private schematic = inject(SchematicService);
entitlement$ = this.schematic.entitlement$("my-flag-key");
approachingLimit$ = this.entitlement$.pipe(
map((entitlement) => {
const warning = entitlement.warningTiers?.find(
(tier) => tier.key === "default",
);
return (
typeof entitlement.featureUsage === "number" &&
typeof warning?.value === "number" &&
entitlement.featureUsage >= warning.value
);
}),
);
}
Use plan$ to get an Observable of the current plan information:
import { Component, inject } from "@angular/core";
import { AsyncPipe } from "@angular/common";
import { SchematicService } from "@schematichq/schematic-angular";
@Component({
selector: "app-plan",
standalone: true,
imports: [AsyncPipe],
template: `
@if (plan$ | async; as plan) {
<div>Current plan: {{ plan.name }}</div>
} @else {
<div>No active subscription</div>
}
`,
})
export class PlanComponent {
private schematic = inject(SchematicService);
plan$ = this.schematic.plan$();
}
Use creditBalance$ to observe a company's credit balance. It is keyed by credit ID and emits as the balance changes over the DataStream:
import { Component, inject } from "@angular/core";
import { AsyncPipe } from "@angular/common";
import { SchematicService } from "@schematichq/schematic-angular";
@Component({
selector: "app-credit-meter",
standalone: true,
imports: [AsyncPipe],
template: `
@if (creditBalance$ | async; as credit) {
@if (credit.isLoading) {
<div>Loading…</div>
} @else {
<div>{{ credit.balance }} credits remaining</div>
}
}
`,
})
export class CreditMeterComponent {
private schematic = inject(SchematicService);
creditBalance$ = this.schematic.creditBalance$("credit-id");
}
creditBalance$ emits an object with the following properties:
| Property | Type | Description |
|---|---|---|
balance | number | The spendable balance, or 0 while loading or when the company holds no balance in this credit |
isLoading | boolean | true while the balance is still loading and no value has arrived yet |
It surfaces the settled (spendable) balance. The credit ID is available on a feature's entitlement, and creditBalance$ accepts an Observable of credit IDs as well as a plain one, so you can pipe the entitlement straight in:
import { map } from "rxjs";
@Component({
selector: "app-credit-meter",
standalone: true,
imports: [AsyncPipe],
template: `
@if (creditBalance$ | async; as credit) {
@if (credit.isLoading) {
<div>Loading…</div>
} @else {
<div>{{ credit.balance }} credits remaining</div>
}
}
`,
})
export class CreditMeterComponent {
private schematic = inject(SchematicService);
creditBalance$ = this.schematic.creditBalance$(
this.schematic
.entitlement$("my-flag-key")
.pipe(map((entitlement) => entitlement.creditId)),
);
}
It switches to the new credit as the source emits. While the ID is undefined, it emits the client's loading state and a balance of 0.
provideSchematic(config)Configures the Schematic client for dependency injection. Accepts either a publishableKey string or a pre-configured client instance, plus any SchematicOptions.
SchematicServiceInjectable service providing all Schematic functionality:
| Method | Return Type | Description |
|---|---|---|
getClient() | Schematic | Access the underlying Schematic client |
setContext(ctx) | void | Set the evaluation context (company/user) |
identify(body) | void | Identify a user and/or company |
track(body) | void | Track a usage event |
flagValue$(key, fallback?) | Observable<boolean> | Observe a feature flag's boolean value |
entitlement$(key, fallback?) | Observable<CheckFlagReturn> | Observe detailed entitlement data |
plan$() | Observable<CheckPlanReturn | undefined> | Observe plan information |
creditBalance$(creditId) | Observable<SchematicCreditBalance> | Observe a company's lease-aware credit balance. Takes a credit ID or an Observable of credit IDs |
isPending$() | Observable<boolean> | Observe loading state |
SCHEMATIC_CLIENTInjectionToken<Schematic> for direct access to the raw client instance via Angular DI.
The SDK includes built-in fallback behavior to ensure your application continues to function even when unable to reach Schematic.
When flag checks cannot reach Schematic, they use fallback values in the following priority order:
flagValue$ or entitlement$flagCheckDefaults or flagValueDefaults options in provideSchematicfalse if no fallback is configured// Provide a fallback value at the callsite
isFeatureEnabled$ = this.schematic.flagValue$("feature-flag", true);
// Or configure defaults at initialization
provideSchematic({
publishableKey: "your-publishable-key",
flagValueDefaults: {
"feature-flag": true,
},
});
When events (track, identify) cannot be sent due to network issues, they are automatically queued and retried:
maxEventQueueSize)maxEventRetries)In WebSocket mode, if the WebSocket connection fails, the SDK will provide the last known value or the configured fallback values as outlined above. The WebSocket will also automatically attempt to re-establish its connection using an exponential backoff.
For debugging and development, Schematic supports two special modes:
Enables console logging of all Schematic operations:
provideSchematic({
publishableKey: "your-publishable-key",
debug: true,
});
// Or via URL parameter
// https://yoursite.com/?schematic_debug=true
Prevents network requests and returns fallback values for all flag checks:
provideSchematic({
publishableKey: "your-publishable-key",
offline: true,
});
// Or via URL parameter
// https://yoursite.com/?schematic_offline=true
Offline mode automatically enables debug mode to help with troubleshooting.
MIT
Need help? Please open a GitHub issue or reach out to support@schematichq.com and we'll be happy to assist.
FAQs
`schematic-angular` is a client-side Angular library for [Schematic](https://schematichq.com) which provides an injectable service to track events, check flags, and more. `schematic-angular` provides the same capabilities as [schematic-js](https://github.
The npm package @schematichq/schematic-angular receives a total of 7 weekly downloads. As such, @schematichq/schematic-angular popularity was classified as not popular.
We found that @schematichq/schematic-angular demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 4 open source maintainers collaborating on the project.

Security News
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.

Research
/Security News
A malicious Firefox extension fetches its payload after installation to evade detection, steal Google session cookies, and automate account takeover.

Research
/Security News
The compromise affects MemTensor's MemOS, an open source memory framework for large language models (LLMs) and AI agents. Both npm package @memtensor/memos-cloud-openclaw-plugin and the PyPI package MemoryOS are compromised. They drop cross-platform Go binaries that exfiltrate developer secrets.