Functional Guards, Interceptors, and Resolvers In short: Guards, interceptors, and resolvers used to be classes. They are all plain functions now. The gain is bigger than shorter code — functions can be built and combined in ways classes could not. Guards The class version needed a class, an interface, a decorator, and a registration: The function version is the same logic with the wrapping removed: A guard function runs inside an injection context. That is why works at the top of the body, not just in field declarations. Why functions beat classes here Because it is a function, you can make guards. This is the thing classes made awkward: With classes you needed either a separate class per role, or a setting read back out of the route. That worked, but it pushed the setting away from where it applied and lost all type checking on the way. Interceptors Same story, with one extra thing worth knowing. The special registration token is gone. The chain is now a plain, ordered list in one place. That matters more than it sounds, because order is a real source of bugs and the old way spread it across whatever files happened to register things. The two interceptor systems registers the function kind. registers the old class kind. You can run both while moving: Note that class-based ones always run after the function ones, no matter what order you list them. So mixing the two makes the chain harder to follow. It is a bridge, not a destination. Resolvers carries the type, so the resolved value is typed. And if you pair it with , it arrives as a component input instead of something you dig out of : Route values, query values, and resolved data all arriving as normal typed inputs, with no and no subscription to clean up. It is one of the best small features of the modern router and still under-used. One warning about resolvers in general: they block the page change until they finish. A slow resolver is a frozen screen with no feedback. For anything not needed for the first paint, inside the component (Chapter 4) gives you a loading state instead of a freeze. "But doesn't interceptor order not matter?" It matters a great deal, and the old design actively hid that. Order came from registration order, spread across many files. Most teams never knew what their chain looked like. The model, stated once: List order going out. Reverse order coming back. That gives four rules: Job Position Why --- --- --- Timing / logging First It wraps everything after it Auth Before retry Or a retry repeats a request whose token expired Retry After auth It needs a valid request to repeat Error handling Last It sees errors once retries are exhausted Why the function form helps here: the chain is four names in one list, in one file. A reviewer can see the order at a glance. With registrations spread across the module graph, most teams genuinely did not know what their order was. The architect's view Why this change is more than syntax. A class registration is a fixed thing you point at. A function is a value you can build, wrap, and combine. Two consequences matter. Configurable guards keep their type safety. The class form could not take arguments, so teams put configuration in and read it back out — pushing the setting away from where it applied, and losing type checking on the way. A factory keeps both together: The chain becomes reviewable. That is the maintainability argument, and it is the strongest one, because interceptor order carries real correctness risk rather than just style. The rule that prevents data damage Retrying is only safe for requests that can run twice with no effect. and qualify. does not. Think about what a retried means. The server may have created the order and the reply may simply have been lost on the way back. Retrying creates a second one. This only happens on poor connections — which is precisely when nobody is watching, and it is very hard to reproduce deliberately. So it is a rule you apply up front, not a bug you find later: That method guard belongs in every retry interceptor, and it is worth a code-review rule. Choosing over blocks entry to a route that already matched. stops the route matching at all, so the router falls through to the next one. Prefer when you want a different page at the same address for a different role, and when you want to avoid downloading code the user cannot use — it prevents the lazy chunk being fetched at all. That is both a bundle decision and a small security-posture one. On resolvers Resolvers block the page change until they finish, with no feedback to the user. That makes them a deliberate trade: you get a fully-populated first render, and you pay with a frozen screen if the request is slow. Use one only when the route genuinely cannot render without the data. For everything else, inside the component gives you a loading state instead of a freeze — better perceived performance for the same real timings. Mentoring note. Give juniors the retry rule as a hard constraint before…