All quizzesHard
DI Internals
Preview — 3 of 10 questions
In Angular's Ivy (R3) DI system, how does the injector resolve a token?
javascript
// Conceptual internals of NodeInjector resolution:
function lookupToken<T>(injector: NodeInjector, token: Type<T>): T {
// Step 1: Check bloom filter — O(1) negative check
if (!bloomHasToken(token, injector.bloomBit)) {
return lookupToken(injector.parent, token); // definitely not here
}
// Step 2: Check the injector's records
const record = injector.records.get(token);
if (!record) {
return lookupToken(injector.parent, token); // false positive — go up
}
// Step 3: Instantiate if needed (lazy)
if (record.value === UNINITIALIZED) {
record.value = record.factory();
}
return record.value;
}AIt performs a linear scan of a flat providers array
BIt uses a bloom filter for fast negative lookups, then resolves via a slot-indexed record array on the injector's _records map
CIt traverses the component tree using breadth-first search
DIt uses a WeakMap keyed by the token's class reference
What is the fundamental architectural difference between NodeInjector and EnvironmentInjector?
javascript
// Conceptually, in an LView (component view data):
// Index 0-9: reserved
// Index INJECTOR_INDEX: bloom filter bits for this node's injector
// Index INJECTOR_INDEX+1: bloom filter accumulated (inherited)
// Index INJECTOR_INDEX+2: parent TNode index
// When you call inject(SomeService) in a component constructor,
// Angular reads the current LView from a global stack:
const currentLView = getLView();
const tNode = getCurrentTNode();
// Then resolves via the NodeInjector protocol on this tNode/lView pair
// EnvironmentInjector is a true object:
class R3EnvironmentInjector implements EnvironmentInjector {
private records = new Map<ProviderToken<unknown>, Record>();
get<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T { /* ... */ }
}ANodeInjector is for components; EnvironmentInjector is for directives only
BNodeInjector supports signals; EnvironmentInjector supports only class-based services
CNodeInjector can only hold one provider; EnvironmentInjector supports unlimited providers
DNodeInjector is embedded in the view's tNode data structure (no separate object per node); EnvironmentInjector is a standalone object with its own records map
How does providedIn: 'root' achieve tree-shaking for services?
javascript
// What Ivy generates for @Injectable({ providedIn: 'root' })
class ReportService {
static ɵprov = ɵɵdefineInjectable({
token: ReportService,
factory: () => new ReportService(ɵɵinject(HttpClient)),
providedIn: 'root',
});
}
// The root ɵinj only creates a dependency edge when ReportService is actually used:
// If no component/service imports ReportService → the factory is dead code → tree-shaken
// With NgModule providers (NOT tree-shakeable):
@NgModule({
providers: [ReportService], // ← always bundled, regardless of usage
})AThe service's ɵprov static property contains the factory; the module's ɵinj references the factory only when the service is injected somewhere, allowing static analysis to prune unused entries
BWebpack/esbuild analyzes all @Injectable decorators and removes unused ones
CAngular compiles a manifest of all services and the CLI removes unused ones during ng build
DTree-shaking works because @Injectable classes extend a base class that is conditionally included
Sign up free to play
Answer all 10 questions (7 more), see explanations for every answer, and track your score.