@smartbit4all/ng-client
v7.3.5
Published
Angular client library for the smartbit4all platform — a server-driven UI framework that renders views, pages, forms, grids, trees and dialogs from backend (ViewApi/PageApi) definitions. Provides reactive view-context state management, dynamic component l
Keywords
Readme
@smartbit4all/ng-client
The Angular client of the smartbit4all platform: a backend-driven UI layer where the server
owns the view lifecycle and the client renders it. Screen components extend
SmartComponent, talk to the platform's BFF (ViewApi / PageApi), and the widgets inside
them — grid, tree, form, filter, map, diagram, toolbars — reload themselves when the backend
says their part of the model changed.
Requires Angular 22 and Material 22. Everything is standalone; there are no NgModules.
- Upgrading from 6.x?
MIGRATION-7.0.md, shipped next to this file, is the whole change list, and there is a codemod for the mechanical part. - Writing your own widget?
WIDGETS.md, also next to this file.
Installation
npm i @smartbit4all/ng-client
npm i @angular/material-date-fns-adapter@^22.0.0 date-fns@^4Wiring it up
One call provides the whole library. It is deliberately not optional per module: the session
and header interceptors used to be easy to leave out, and leaving them out failed silently
— with no Authorization header.
bootstrapApplication(AppComponent, {
providers: [
provideAnimations(),
provideRouter(ROUTES),
provideSmartNgClient(
{
gridMenuIcon: 'more_horiz',
aclEditingViewName: Pages.ACL_MATRIX_PAGE,
namedValidators: [MY_VALIDATOR_FACTORY],
},
withSmartMap({ engine: MapEngine.LEAFLET }), // optional: pulls in a map engine
withSmartDiagram({ customOptions: [MY_CHART] }) // optional: pulls in chart.js
),
{ provide: HTTP_INTERCEPTORS, useClass: MyLoadingInterceptor, multi: true },
],
});SmartNgClientConfig is the entire configuration surface — see its doc comments for the
fields. Only the map and the diagram are opt-in, because they are the only features that pull
heavy third-party code.
Components are standalone: import the ones your templates use in the component that uses them.
@Component({
selector: 'app-my-page',
templateUrl: './my-page.component.html',
imports: [SmartGridComponent, UiActionToolbarComponent, SmartEmbeddedSlotDirective],
})
export class MyPageComponent extends SmartComponent<MyModel> { … }Session and view context
SmartBackendBootstrapService owns the startup sequence. Configure it once, then choose one
entry point — start() for a normal web app, bootstrapWith(creds) when a native shell
hands the session over. They are mutually exclusive; reset() switches.
@Injectable({ providedIn: 'root' })
export class AppAuthBootstrap {
constructor(
private bootstrap: SmartBackendBootstrapService,
defaultErrorUi: SmartDefaultErrorUiService
) {
this.bootstrap.configure({
url: 'https://my-host/api',
cookieName: 'my-app',
viewHandlers: HANDLERS,
actionDescriptors: ACTION_DESCRIPTORS,
...defaultErrorUi.hooks({ language: 'hu' }),
});
this.bootstrap.start();
}
}providers: [provideAppInitializer(() => { inject(AppAuthBootstrap); })]Every screen component awaits bootstrap.whenReady() before its first BFF call —
SmartComponentApiClient.run() does it for you. The config hooks (onStartError,
onSessionError, onViewContextLost, onSmartLink) are where host-specific behaviour goes;
the error interceptor routes backend session errors into them.
View handlers
The backend names the view; the host says what renders it.
export const HANDLERS: SmartViewHandlerModel[] = [
{ name: Pages.ANY_PAGE, route: 'any' }, // ViewType.NORMAL
{ name: Pages.ANY_DIALOG, component: AnyDialogComponent }, // ViewType.DIALOG
{ name: Pages.ANY_COMPONENT, route: 'any', component: AnyComponent },
];A view the host did not register still renders, through the library's default components;
add your own with defaultViewComponents.
Smartlinks
Route /redirect/:channel/:uuid to SmartViewRedirect (or to your own component extending
it) and handle the hand-off in configure({ onSmartLink }).
const routes: Routes = [
{
path: `redirect/:${SmartLinkChannelVariableInPath}/:${SmartLinkUuidVariableInPath}`,
component: SmartViewRedirect,
},
];UiActions
The backend sends the actions; the host supplies their descriptors — caption, icon, button type, confirm/input dialog, feedback:
this.viewContext.setActionDescriptors(ACTION_DESCRIPTORS);
this.viewContext.commonFeedbackText = 'Done.';A toolbar renders the actions addressed to its id (uiAction.toolbar == id), pulling
them from the screen component above it; an explicit [uiActionModels] binding wins. Without
an id and without a binding it shows nothing.
<smart-ui-action-toolbar [id]="'myToolbar'"></smart-ui-action-toolbar>
<smart-ui-action-toolbar [uiActionModels]="myActions" [executor]="myService"></smart-ui-action-toolbar>[executor] defaults to the screen component the toolbar sits under. Bind it only when the
actions belong to another API — a tree service, a dialog service of your own — and implement
UiActionExecutor there:
export class MyDialogService implements UiActionExecutor {
submitForm(validate: boolean): void { … }
getInvalidFields(): SmartFormInvalidFields { … }
getAdditionalParams(uiAction: UiAction): UiActionAdditionalParams { … }
getModel(): any { … }
performUiActionRequest(request: UiActionRequest): Promise<any> { … }
handleSpecificDemandsAsynchronously(…): Promise<UiActionSpecificDemandResponse> { … }
}A UiActionModel is frozen: build an entry, never edit one. To change how an action
looks, rebuild the array — actions.map(a => a.uiAction.code === c ? { ...a, cssClass: 'x' } : a)
— because it is the array reference changing that re-renders the toolbar.
Keybindings
The server binds key chords (Mod+Shift+S, Mod = Cmd/Ctrl) to a view's actions, from a
keymap file and from code; the client fires them in the focused view and shows them in the
button's tooltip. Nothing to wire on the client. The keymap format, the chord grammar and the
rules for which view gets a key: MIGRATION-7.0.md, New in 7.3.2: keybindings.
Change detection
7.0 runs without zone.js and every library component is OnPush. Nothing forces your host to
drop zone.js — the library works either way — but if you do, the same rule applies to your
own components: state written from a subscription, a promise or a timer needs a signal or a
markForCheck(); state written from a template event or an input does not.
Dates
The browser's timezone on screen, Zulu (…Z) on the wire, the server converts. Date widgets
hold plain Date values and render yyyy.MM.dd.; the adapter is date-fns. If you provide
MAT_DATE_LOCALE, the string 'hu-HU' keeps working — see MIGRATION-7.0.md, Dates.
Dev tool
localStorage.setItem('smartDevToolActive', 'true'); // active
localStorage.setItem('useDevTool', 'true'); // button visibleFurther reading
| File | What it is |
|---|---|
| MIGRATION-7.0.md | the 6.x → 7.0 change list, and the codemod that does the mechanical part |
| WIDGETS.md | the widget-authoring protocol |
| MIGRATION-6.0.md, MIGRATION-4.5.md | the previous two majors |
Per-module notes and version logs live next to the sources, in
src/lib/<module>/README.md and src/lib/<module>/versionLogs.md.
