🚀 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."