Angular integration for Cerebral state management.
npm install @cerebral/angular @angular/core @angular/platform-browser
AppService
is the core service that creates and exposes the Cerebral controller to your Angular application.
// app.config.ts
import { ApplicationConfig } from '@angular/core'
import { provideAnimations } from '@angular/platform-browser/animations'
import { provideRouter } from '@angular/router'
import { AppService } from '@cerebral/angular'
import { SomeService } from './some.service'
import { routes } from './app.routes'
// Create a Cerebral app factory
export function createCerebralApp(someService: SomeService) {
return new AppService({
state: {
count: 0,
items: []
},
sequences: {
increment: [
({ store, get }) => store.set('count', get(state`count`) + 1)
],
decrement: [({ store, get }) => store.set('count', get(state`count`) - 1)]
},
providers: {
// Provide Angular services to Cerebral
someService
}
})
}
// Provide AppService in your app config
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideAnimations(),
{
provide: AppService,
useFactory: createCerebralApp,
deps: [SomeService]
}
]
}
connect
decorator∞ The connect
decorator connects Cerebral state and sequences to your components:
import {
Component,
ChangeDetectionStrategy,
ChangeDetectorRef,
Inject
} from '@angular/core'
import { state, sequences } from 'cerebral'
import { connect, AppService, CerebralComponent } from '@cerebral/angular'
@Component({
selector: 'app-counter',
standalone: true, // In Angular 19+ is this the default
template: `
<div>
<h2>Count: {{ count }}</h2>
<button (click)="increment()">+</button>
<button (click)="decrement()">-</button>
</div>
`,
// OnPush change detection is required for optimal performance
changeDetection: ChangeDetectionStrategy.OnPush
})
@connect({
count: state`count`,
increment: sequences`increment`,
decrement: sequences`decrement`
})
export class CounterComponent extends CerebralComponent {
// Properly declare connected properties with TypeScript
count!: number
increment!: () => void
decrement!: () => void
constructor(
@Inject(ChangeDetectorRef) cdr: ChangeDetectorRef,
@Inject(AppService) app: AppService
) {
super(cdr, app)
}
}
For parent-child component relationships, ensure child components are properly imported:
import {
Component,
ChangeDetectionStrategy,
ChangeDetectorRef,
Inject
} from '@angular/core'
import { state, sequences } from 'cerebral'
import { connect, AppService, CerebralComponent } from '@cerebral/angular'
import { ChildComponent } from './child.component'
@Component({
selector: 'app-parent',
standalone: true,
imports: [ChildComponent],
template: `
<div>
<h1>Parent</h1>
<app-child [data]="items"></app-child>
<button (click)="addItem()">Add Item</button>
</div>
`,
// Change detection needs to be set to OnPush
changeDetection: ChangeDetectionStrategy.OnPush
})
@connect({
items: state`items`,
addItem: sequences`addItem`
})
export class ParentComponent extends CerebralComponent {
items!: any[]
addItem!: () => void
constructor(
@Inject(ChangeDetectorRef) cdr: ChangeDetectorRef,
@Inject(AppService) app: AppService
) {
super(cdr, app)
}
}
You can inject Angular services into your Cerebral sequences using providers:
// app.config.ts
import { HttpClient } from '@angular/common/http'
import { ApplicationConfig } from '@angular/core'
import { AppService } from '@cerebral/angular'
export function createCerebralApp(httpClient: HttpClient) {
return new AppService({
state: {
data: null,
loading: false,
error: null
},
sequences: {
fetchData: [
({ store }) => store.set('loading', true),
async ({ http }) => {
try {
const response = await http.get('/api/data').toPromise()
return { response }
} catch (error) {
return { error }
}
},
({ store, props }) => {
if (props.error) {
store.set('error', props.error)
} else {
store.set('data', props.response)
}
store.set('loading', false)
}
]
},
providers: {
http: httpClient
}
})
}
export const appConfig: ApplicationConfig = {
providers: [
{
provide: AppService,
useFactory: createCerebralApp,
deps: [HttpClient]
}
]
}
Cerebral uses functions to compute derived state values:
import { state } from 'cerebral'
import { AppService } from '@cerebral/angular'
// 1. Define types for better type safety
type Item = { id: number; name: string }
type AppState = {
items: Item[]
filter: string
filteredItems?: Item[]
}
// 2. Define the computed function
const filteredItems = (get: any) => {
const items = get(state`items`)
const filter = get(state`filter`)
return items.filter((item: Item) =>
filter ? item.name.includes(filter) : true
)
}
// 3. Create your app with computed properties
const app = new AppService({
state: {
items: [],
filter: '',
filteredItems // Attach computed to state
} as AppState,
sequences: {
// Define your sequences here
}
})
// 4. Use in component
@Component({
standalone: true
// ...component config
})
@connect({
filteredItems: state`filteredItems` // Access via state tag or proxy
})
export class ListComponent extends CerebralComponent {
// Declare with proper type
filteredItems!: Item[]
}
Computed values automatically track their dependencies and only recalculate when necessary, improving performance for derived state.
State updates not reflected in components
ChangeDetectionStrategy.OnPush
CerebralComponent
@Inject()
for dependency injectionTypeScript errors with connected properties
!
assertioncount!: number;
Angular directives not working
imports: [NgIf, NgFor, CommonModule]
Component not updating after state change
app.flush()
after sequence execution in tests