App start
onAppStart declares work that must run — and finish — before the application renders, owned by the service that needs it rather than by a global bootstrap file.
Use it when something must be true before the first paint: a loaded config, a restored session, a feature-flag fetch. Not when the work can happen after render — that is just an effect, and blocking on it costs your users a blank screen.
Import
import { onAppStart } from '@craft-ng/core';Overview
onAppStart(...) is used inside a craftService(..., function* () {}) generator to declare logic that should run when the application starts.
Important constraints:
- the owning service must be declared with
appStart: true - a service can declare
yield* onAppStart(...)only once - the callback can be a plain function or a generator function
- nested
onAppStart(...)calls inside the callback are not supported
craftAppConfig(...) runs registered app-start services during Angular application initialization.
Signature
function onAppStart(
run: () => Observable<unknown> | Promise<unknown> | void,
): Generator<unknown, void, unknown>;
function onAppStart<Yielded>(
run: () => Generator<
Yielded,
Observable<unknown> | Promise<unknown> | void,
unknown
>,
): Generator<unknown, void, unknown>;Plain Callback
Use a plain callback when startup logic does not need to yield* crafted dependencies.
import { craftService, onAppStart } from '@craft-ng/core';
export const { StartupFlag } = craftService(
{
name: 'StartupFlag',
scope: 'global',
appStart: true,
},
function* () {
yield* onAppStart(() => {
console.log('app started');
return Promise.resolve();
});
return true;
},
);Generator Callback
Use a generator callback when startup logic needs to yield* crafted dependencies.
import { Console, craftService, onAppStart } from '@craft-ng/core';
export const { AppStartLog } = craftService(
{
name: 'AppStartLog',
scope: 'toProvide',
appStart: true,
},
function* () {
yield* onAppStart(function* () {
yield* Console.log('This is a log from the appStart callback');
return new Promise((resolve) => setTimeout(resolve, 1000));
});
return 1;
},
);The callback generator supports the same dependency-yield semantics as a normal crafted generator for:
yield* X(...)yield*exposure tokens returned by derivation callbacks- browser boundaries such as
yield* Console.log(...)
Dependencies used only inside this callback are merged into the parent service dependency graph.
Registering it with craftAppConfig
Declaring onAppStart is only half of it — nothing runs until the service is registered. Two steps, and both are mechanical.
Augment the app-start registry so the service is known by name:
declare module '@craft-ng/core' {
interface CraftAppStartRegistry {
AppStartLog: typeof AppStartLog;
}
}Then list it in craftAppConfig:
export const appConfig = craftAppConfig({
appStart: {
AppStartLog,
},
providers: [
/* … */
],
});craftAppConfig runs every registered app-start service during Angular's application initialization, and the app renders once they have settled.
Here it is end to end:
import { Console, craftAppConfig, craftService, onAppStart } from '@craft-ng/core';
const { AppStartLog } = craftService(
{
name: 'AppStartLog',
scope: 'global',
appStart: true,
},
function* () {
yield* onAppStart(function* () {
yield* Console.log('startup log');
return Promise.resolve();
});
return true;
},
);
declare module '@craft-ng/core' {
interface CraftAppStartRegistry {
AppStartLog: typeof AppStartLog;
}
}
export const appConfig = craftAppConfig({
appStart: { AppStartLog },
});The registry augmentation is generated
The declare module block is written for you by the craft-ng ESLint plugin — you rarely type it by hand.
A declared hook that is never registered simply never runs
It is not an error: appStart: true and yield* onAppStart(...) describe the service, the appStart map in craftAppConfig is what activates it. If startup logic silently doesn't happen, check the map first.
Dependency Tracking
Generator callbacks are type-visible.
If the callback only uses Console, the owning service dependency graph includes ConsoleService as a normal dependency node, with browserBoundary: true.
This means startup-only dependencies are still visible to:
GetServiceDependencies<typeof X>- route/app DI checks built on top of service metadata
- test helpers that inspect crafted dependency graphs
Runtime Behavior
onAppStart(...) does not run when the service instance is created.
It registers a startup hook that is executed when the application initializer runs that service, typically through craftAppConfig(...).
If the callback returns:
void: startup continues immediatelyPromise: startup waits for the promise to resolveObservable: startup waits through Angular's initializer handling
Generator callbacks preserve the same waiting behavior. The generator itself resolves first, then its returned Promise / Observable / void is used as the startup result.
Common Errors
Missing appStart: true
yield * onAppStart(() => undefined);This throws at runtime if the owning service was not declared with appStart: true.
Nested onAppStart(...)
yield *
onAppStart(function* () {
yield* onAppStart(() => undefined); // unsupported
return undefined;
});Nested declarations are rejected at runtime.