Dependency injection — demo
Two shops side-by-side, with a layered <Injector> tree showing both sharing (one catalog + tax rate at the outer scope) and isolation (one cart per shop). Adding to one cart doesn't show up in the other; changing the tax rate updates both.
What it shows
- An outer
<Injector>providingCatalogService— items + a reactivetaxRate. Lives at the page level. - Two inner
<Injector>subtrees, one per shop, each providing its ownCartStore. The cart's total usesinject(CatalogService)to apply tax — resolution walks up through the shop scope to the page scope. - A
<TaxBar>widget at the page level mutatestaxRateon the parent service; both shops re-render because they read it transitively through theirCartStore.
The logic
// Lives at the page-level <Injector>. Two shops share one instance.
class CatalogService {
items = [/* coffee, latte, croissant */];
taxRate = state(0);
withTax(subtotal: number) {
return subtotal * (1 + this.taxRate());
}
}
// Lives at each shop's <Injector>. inject() walks UP to find CatalogService.
class CartStore {
catalog = inject(CatalogService);
items = state<Item[]>([]);
subtotal = computedState(() => this.items().reduce((s, i) => s + i.price, 0));
total = computedState(() => this.catalog.withTax(this.subtotal()));
add(item: Item) { this.items([...this.items(), item]); }
}
<Injector provide={[CatalogService]}> {/* outer scope */}
<TaxBar />
<Shop label="Shop A" />
<Shop label="Shop B" />
</Injector>
const Shop = ({ label }) => (
<Injector provide={[CartStore]}> {/* inner scope, one per shop */}
<section>
<Header />
<List />
</section>
</Injector>
);
Resolution walk: <Header>'s logic runs inject(CartStore), which lands at the inner shop scope. The store's own inject(CatalogService) walks past the inner scope (no provider there) to the outer scope (provided). Two carts, one catalog, one tax rate.
Run it
nx serve demo-di