Enterprise recipes
Working Angular 22 recipes: HTTP forms, remote Table, lazy TreeTable, controlled Dialog and a themed workspace.
Transport and test setup
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { recipeBackend } from './recipe-backend';
// Demo only. Production: provideHttpClient() with your authentication policy
// and a real backend implementing the response contracts in these recipes.
export const appConfig: ApplicationConfig = {
providers: [provideHttpClient(withInterceptors([recipeBackend]))],
};Profile form with HTTP validation
Remote Table: filter, select and export
Lazy TreeTable: errors and retry
The demo backend fails the first child request. Use Retry in the row.
Accept or reject Dialog close proposals
Navigation, theme and direction
Neural
Navigation keeps the application-owned settings.
Test HTTP, proposals and cleanup
import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { By } from '@angular/platform-browser';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { EnterpriseFormExample } from '../foundation-examples/enterprise-form.example';
import { EnterpriseTableExample } from '../foundation-examples/enterprise-table.example';
import { EnterpriseTreeExample } from '../foundation-examples/enterprise-tree.example';
import { EnterpriseDialogExample } from '../foundation-examples/enterprise-dialog.example';
import { EnterpriseShellExample } from '../foundation-examples/enterprise-shell.example';
import { NeuDialogComponent } from '@neural-ui/core/modal';
import { type NeuTableDataRequest } from '@neural-ui/core/table';
import { type OrderField, emptyOrderQuery, ORDERS } from '../examples/table-data';
describe('Enterprise HTTP recipes against the installed public package', () => {
let http: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [provideHttpClient(), provideHttpClientTesting()],
});
http = TestBed.inject(HttpTestingController);
});
afterEach(() => http.verify({ ignoreCancelled: true }));
it('validates availability, rejects a taken address, posts valid data and resets interaction state', async () => {
const f = TestBed.createComponent(EnterpriseFormExample);
f.detectChanges();
f.componentInstance.data.set({ name: 'Pedro', email: 'taken@example.com', updates: true });
f.detectChanges();
await Promise.resolve();
TestBed.tick();
http.expectOne('/api/recipes/email?value=taken%40example.com').flush({ available: false });
await f.whenStable();
expect(
f.componentInstance.fields
.email()
.errors()
.some((error) => error.kind === 'taken'),
).toBe(true);
f.componentInstance.save();
http.expectNone('/api/recipes/profile');
f.componentInstance.data.update((data) => ({ ...data, email: 'free@example.com' }));
f.detectChanges();
await Promise.resolve();
TestBed.tick();
http.expectOne('/api/recipes/email?value=free%40example.com').flush({ available: true });
await f.whenStable();
f.componentInstance.save();
expect(f.componentInstance.saving()).toBe(true);
const post = http.expectOne('/api/recipes/profile');
expect(post.request.method).toBe('POST');
expect(post.request.body.email).toBe('free@example.com');
post.flush({ saved: true });
await f.whenStable();
expect(f.componentInstance.saving()).toBe(false);
f.componentInstance.reset();
await f.whenStable();
expect(f.componentInstance.data()).toEqual({ name: '', email: '', updates: false });
expect(f.componentInstance.fields.email().touched()).toBe(false);
expect(f.componentInstance.fields.email().dirty()).toBe(false);
});
it('cancels replaced Table queries and applies only the current response metadata', async () => {
const f = TestBed.createComponent(EnterpriseTableExample);
// No render: explicit requests isolate HTTP from the initial dataRequest.
const request = {
requestId: 40,
queryRevision: 3,
query: emptyOrderQuery(),
reason: 'query',
} as NeuTableDataRequest<OrderField>;
f.componentInstance.load(request);
const first = http.expectOne((req) => req.url === '/api/recipes/orders');
f.componentInstance.load({
...request,
requestId: 41,
queryRevision: 4,
query: { ...request.query, search: 'Atlas' },
});
expect(first.cancelled).toBe(true);
const second = http.expectOne((req) => req.url === '/api/recipes/orders');
expect(JSON.parse(second.request.params.get('query')!).search).toBe('Atlas');
second.flush({ rows: [ORDERS[0]], total: 1 });
expect(f.componentInstance.rows()).toEqual([ORDERS[0]]);
expect(f.componentInstance.metadata()).toMatchObject({
loading: false,
queryRevision: 4,
appliedRequestId: 41,
totalItems: 1,
});
f.componentInstance.load({ ...request, requestId: 42 });
const pending = http.expectOne((req) => req.url === '/api/recipes/orders');
f.destroy();
expect(pending.cancelled).toBe(true);
});
it('applies TreeTable child errors, retries the relation and cancels requests on destroy', () => {
const f = TestBed.createComponent(EnterpriseTreeExample);
const roots = {
kind: 'roots',
requestId: 1,
queryRevision: 0,
relationRevision: 0,
} as Parameters<EnterpriseTreeExample['load']>[0];
f.componentInstance.load(roots);
http.expectOne((req) => req.params.get('parent') === '__roots__').flush({ keys: ['design'] });
const children = {
...roots,
kind: 'children',
parentKey: 'design',
requestId: 2,
} as Parameters<EnterpriseTreeExample['load']>[0];
f.componentInstance.load(children);
http
.expectOne((req) => req.params.get('parent') === 'design')
.flush('temporary error', { status: 503, statusText: 'Unavailable' });
expect(
f.componentInstance.hierarchy().children.find((child) => child.parentKey === 'design')
?.relation.status,
).toBe('error');
f.componentInstance.load({ ...children, requestId: 3, relationRevision: 1 });
http.expectOne((req) => req.params.get('parent') === 'design').flush({ keys: ['research'] });
expect(
f.componentInstance.hierarchy().children.find((child) => child.parentKey === 'design')
?.relation,
).toMatchObject({ status: 'loaded', keys: ['research'], appliedRequestId: 3 });
f.componentInstance.load({
...children,
kind: 'children',
parentKey: 'research',
requestId: 4,
} as Parameters<EnterpriseTreeExample['load']>[0]);
const pending = http.expectOne((req) => req.params.get('parent') === 'research');
f.destroy();
expect(pending.cancelled).toBe(true);
});
it('requests children from the rendered expansion gesture and retries its row error', async () => {
const f = TestBed.createComponent(EnterpriseTreeExample);
await f.whenStable();
http
.expectOne((req) => req.params.get('parent') === '__roots__')
.flush({ keys: ['design', 'delivery'] });
await f.whenStable();
const expand = f.nativeElement.querySelector(
'button[aria-expanded="false"]',
) as HTMLButtonElement;
expect(expand).not.toBeNull();
expand.click();
await f.whenStable();
http
.expectOne((req) => req.params.get('parent') === 'design')
.flush('error', { status: 503, statusText: 'Unavailable' });
await f.whenStable();
const retry = [...f.nativeElement.querySelectorAll('button')].find((button: any) =>
button.textContent.includes('Retry'),
) as HTMLButtonElement;
expect(retry).toBeDefined();
retry.click();
await f.whenStable();
http.expectOne((req) => req.params.get('parent') === 'design').flush({ keys: ['research'] });
await f.whenStable();
expect(f.nativeElement.textContent).toContain('Research');
});
it('rejects dirty Dialog close proposals and accepts after saving', async () => {
const f = TestBed.createComponent(EnterpriseDialogExample);
await f.whenStable();
f.componentInstance.open.set(true);
await f.whenStable();
const dialog = f.debugElement.query(By.directive(NeuDialogComponent))
.componentInstance as NeuDialogComponent;
dialog.requestClose('escape', new KeyboardEvent('keydown', { key: 'Escape' }));
await f.whenStable();
expect(f.componentInstance.open()).toBe(true);
expect(f.componentInstance.rejected()).toBe(true);
f.componentInstance.dirty.set(false);
dialog.requestClose('escape', new KeyboardEvent('keydown', { key: 'Escape' }));
await f.whenStable();
expect(f.componentInstance.open()).toBe(false);
});
it('preserves settings across workspace navigation independently of language and direction', async () => {
const f = TestBed.createComponent(EnterpriseShellExample);
await f.whenStable();
f.componentRef.setInput('language', 'es');
f.componentInstance.page.set('settings');
f.componentInstance.name.set('Equipo');
f.componentInstance.dark.set(true);
f.componentInstance.rtl.set(true);
await f.whenStable();
expect(f.nativeElement.querySelector('.workspace').getAttribute('dir')).toBe('rtl');
expect(f.nativeElement.querySelector('.workspace').getAttribute('data-theme')).toBe('dark');
f.componentInstance.page.set('overview');
await f.whenStable();
expect(f.nativeElement.querySelector('h3').textContent).toBe('Equipo');
expect(f.componentInstance.language()).toBe('es');
});
});