🚀 Modern Angular Baseline Tips
Posted this to my team - do you agree/disagree? Anything I missed?
"Lots of new tech is stable and lots of old tech is becoming deprecated so it's a good time to take stock!
At this point I would go as far to say that not following any of these tips is effectively baking tech debt into the platform based on where Angular is and is intending to go.
Would love to hear your thoughts though. If we're aligned, we can factor them into code review etc going forward.
Control Flow/Queries
• No need to use *ngIf/*ngFor/*ngSwitch anymore
→ Use ﹫if/﹫for/﹫switch instead (we all know this one)
• No need to use ﹫Input/﹫Output()/﹫ViewChild[ren]/﹫ContentChild[ren] anymore
→ Use input()/output()/viewChild[ren]()/contentChild[ren]() instead
Styles
• No need to use ngClass or ngStyle anymore
→ Use [class] and [style] instead. These are functionally equivalent and avoid the NgClass and NgStyle imports
Modules/Imports
• No need to import CommonModule anymore
→ Just import the specific classes you need. In practice this tends to be things like NgTemplateOutlet or common pipes like DatePipe or JsonPipe. NgIf/NgFor/NgSwitch/NgClass/NgStyle were the reason that CommonModule used to be a standard import in components, but all of them are now deprecated
• No need to use NgModules anymore. They should not be created for new components
→ Components should now import what they need directly
→ Module-level imports (which are rarely needed now) should be placed in the root feature component
→ Module-level providers should be placed in either the root feature component or the route corresponding with that feature
• No need to manually create destroy$ subjects
→ Inject destroyRef and use takeUntilDestroyed(this.destroyRef) instead of takeUntil(this.destroy$)
State
• All new state variables should be being built with signals: signal()/computed()/linkedSignal().
• Services/repositories/general HTTP usage can still be RxJS heavy but by the time state is consumed by component templates they should be signals.
Implied here is that the AsyncPipe should no longer be used.
• Signals are a declarative paradigm so we should be looking to minimise imperative code as much as possible, but sometimes it’s inevitable (e.g. updating a Reactive form pre-v21).
In this case we should use effect() to watch signals for changes or .subscribe() to watch observables for changes.
Inherently imperative events like click-handlers will continue to need imperative solutions.
• To facilitate all of the above we should be looking to migrate core pieces of application state to signals when possible. As an example, I recently migrated currentUser to a signal from a BehaviourSubject. This now means any component in the application can reactively consume currentUser without any RxJS/subscriptions.
Lifecycle hooks
• Once all inputs in a component are migrated to input(), ngOnChanges is no longer necessary
• Once all queries in a component are migrated to viewChild[ren]()/contentChild[ren](), ngAfterViewInit is no longer necessary
• Once a component is using destroyRef, manual unsubscription is not needed so ngOnDestroy is not needed in a large number of cases
• If a component’s state can be modelled fully in signals, ngOnInit is also not necessary
In summary, in a fully modern Angular component, you should expect to encounter ngOnInit and rarely ngOnDestroy but never ngOnChanges/ngAfterViewInit.
There are more but I think this is a pretty solid list that impacts our day to day."
are there people out there who just want to refactor every day?
just wake up and find the worst code and just chip away at it and clean it up
wake up the next day do it again, infinitely improving things with zero external impact?
Something I've wanted for years.
Finally going to land in #Angular v22.
You'll be able to comment attributes/inputs.
What you're seeing here is the toggle support by Vscode (thx to the Angular language service extension).
@bjeaurn@angular Love this! Also "explicit types are unit tests for your variables" is a line that redefined the way I looked at types. Yes it's verbose to type out what can be inferred just as it's verbose to type out unit tests but both have their place! Give the control to the developer
I haven't found an auto-formatter that is sufficiently configurable, so I just use organise imports/auto semi-colons and format the rest by hand 😅 AI picks up the formatting of the project so it doesn't cause me too much grief! I have started the "Let's all use Prettier!" thing at 3 different companies and always backed out when we realised what we couldn't change
I haven't found an auto-formatter that is sufficiently configurable, so I just use organise imports/auto semi-colons and format the rest by hand 😅 AI picks up the formatting of the project so it doesn't cause me too much grief! I have started the "Let's all use Prettier!" thing at 3 different companies and always backed out when we realised what we couldn't change
Declarative programming = easier to reason about code = less bugs on new features/refactors
Signals = reactive primitives = state dependency chains = more efficient/relevant change detection = performance gains
@MichaelSmallDev No way! My original comment was actually inspired by injectQueryParams which has the same limitations haha
Incredible news, thanks for sharing ❤️
I just want a signal of URL params/queryParams/fragments that isn't tied to the component tree 😭
I assume this still isn't possible? I was hoping we could statically analyze the route configs and still keep the variable names without needing to be within a component to access them?
Present tense seems more generalisable to me. It conveys that the emission is an event not a state.
To test that idea, hold up the name against an inbuilt EventEmitter (click). Would (clicked) make sense, probably, but I think (click) is nicer because it is stateless
Of course you can consume state from outputs, but I would say they are more akin to observables (event is the primary use case, state is secondary) vs signals (state is primary, event is secondary)
@RManganelly@zheleshchenko@tomastrajan Even in a broader sense, "Default" is a pretty poor name for anything concrete. May as well call it "something" haha
Might be an opportunity to rename "Default" to anything more descriptive, "Always"/"Eager"/"Maximum" etc (I'm sure there are better names!)