Component vs Directive vs Service vs Pipe: The Decision Matrix In short: Four tools, four jobs. Ask what the code's primary job is — draw UI, change an existing element, transform a value for display, or hold logic and state — and the answer picks itself. When you first learn Angular, the instinct is to put everything in a component. Need to format a date? Write a method. Need data? Call in a lifecycle hook. Need a hover colour? Add a host binding. That is fine in a prototype. In a system it produces components that are 400 lines long, untestable, and impossible to reuse. Let's fix the tanker intake module we built in Chapter 1 by moving code to where it belongs. The decision matrix Tool Primary job Has a template? Owns business state? --- --- --- --- Component Draw UI. Arrange children. Yes Local UI state only Directive Change how an existing element behaves or looks No No Pipe Transform a value for display No No — pure function Service Business logic, I/O, shared state No Yes The question to ask is what is the primary job of this code? Not "what does it touch" — most code touches several things. Pipes: transforming values for display In Chapter 1, printed raw numbers: . Users want . You could write a method in the component, but the dashboard, the sidebar, and the payout statement all need the same formatting. Copying a method into three components is how drift starts. Rule: if you are taking a value and reshaping it purely for the screen, that is a pipe. Two things make this a good pipe: It is pure — same input, same output, no side effects. Angular caches pure pipe results and only re-runs the transform when the input changes. It has no dependencies . It does not inject anything, does not read global state, and can be unit tested in three lines. If a "pipe" needs to inject a service and read changing state, it is usually a in disguise. Services: logic and shared state In Chapter 1, held the master signal for the active tanker. That worked because only its own children needed it. Now add a that also shows the active tanker's volume. The header is not a child of the dashboard, so there is no input to pass it through. Rule: when data or logic is needed by components that are not in a parent-child line, move it to a service. Note . The store exposes a signal that components can read but not write. Every change goes through a named method, which means when the volume is wrong you have one place to look instead of forty. The refactored coordinator gets noticeably thinner. It no longer owns state or mutations — it injects the store and routes events to it. Where the tanker module ended up Piece Tool Job --- --- --- Pipe Turn into Directive Put a warning class on an existing element Service Own the tanker state and the rules for changing it Component Draw the card Component Arrange children, route events Five pieces, five jobs, no overlap. That is the whole goal. "But I thought pipes were slower than doing it in the class" This gets repeated a lot, and there is a real problem underneath it — it is just attached to the wrong feature. The performance disaster people remember is the impure pipe. Setting tells Angular to re-run the transform on every change detection cycle, regardless of whether the input changed. That is genuinely expensive and is why some teams banned pipes outright. Pure pipes — the default — are the opposite. Angular memoises them by input. Given the same input, the transform does not run again. Here is the comparison that matters, on a dispatch table with 200 rows where one unrelated signal changes: Approach Transform executions Notes --- --- --- (method) 200 per check Never cached (pure pipe) 0 Inputs unchanged, result reused with 200 per check This is the one you were warned about in the class 0 Also fine, and better when the logic is not display-only So the belief is backwards for the common case: a pure pipe is the cheaper option compared to a method in a template, because it is the one Angular caches. The real reason to choose a over a pipe is not speed. It is ownership. A pipe is display formatting that any component may reuse. A is derived state that belongs to one component. If you find yourself passing four arguments into a pipe to give it enough context, you wanted a . Cheat sheet Ask these in order and stop at the first "yes": 1. Does it render markup of its own? → Component 2. Does it change an element somebody else rendered? → Directive 3. Is it a pure value-in, value-out transform for display? → Pipe 4. Anything else — rules, I/O, state shared across the tree? → Service And the two escape hatches: If it is derived state used by exactly one component → , not a pipe. If it is local UI state nobody else needs → , not a service. Recap Four tools, four jobs. Components draw, directives modify, pipes transform, services think. Pure pipes are cached. A pure pipe beats a method call in a template; impure pipes are the thing that earned pipes their bad reputation. Expose state read-only f…