Michał Grzegorczyk: Not long ago, Daniel shared 18 interview questions on LinkedIn, Twitter and Discord. Questions crafted in a way to identify real Senior Angular developers. Using this opportunity we have asked Angular Experts to answer some of these.
Daniel Glejzner: Make sure to check back here regularly as this post is going to be updated with answers from even more experts in coming days/weeks :). I hope this is going to provide you necessary perspective on how answers in tech interview can vary. There is no one specific answer that is expected. Enjoy!
But first, let's meet our Experts!
Kevin Kreuzer
A trainer, consultant, streamer, content creator and senior front-end engineer with a focus on the modern web, as well as a Google Developer Expert for Angular & Web technologies.
- angularexperts.io
- https://twitter.com/kreuzercode
- Checkout latest Angular Signals Masterclass e-book by Kevin here: https://angularexperts.io/products/ebook-signals
Rainer Hahnekamp
Experienced software developer and architect for enterprise applications. For over 15 years, he worked as a team lead for the well-known US confectionery manufacturer Mars. For his community activities, Rainer was recognized as a Google Developer Expert for Angular.
Alex Inkin
Angular Google Developer Expert, front-end developer, tech writer, musician and open-source author. Alex is one of the core creators and maintainers of Taiga UI. He has been instrumental in its development and promotion within the Angular community.
Tomas Trajan
A Google Developer Expert for Angular & Web Technologies working as a consultant and Angular trainer. Currently empowering teams in enterprise organizations worldwide by implementing core functionality and architecture, introducing best practices, sharing know-how and optimizing workflows.
- x.com/tomastrajan
- angularexperts.io
- Latest book by Tomas about Architecture!! https://angularexperts.io/products/ebook-angular-enterprise-architecture
Mateusz Łędzewicz
Principal Software Engineer Focused on @angular and Frontend technologies @ngLodz Organizer. Now, he serves as a Principal Consultant and Trainer at Lowgular, where he teaches Angular from scratch and helps clients enhance developer performance. His focus is on training developers to adopt best engineering practices and reduce technical debt, all while sharing his deep expertise in Angular.
Erick Rodriguez
Lead Software Engineer at Doran Jones Inc. I specialize in full stack software development. I like Angular and love to do things with Nx Framework.
Q1: If you had to pick only 5 libraries as dependencies for your Angular app, which ones would you pick?
A1: Kevin Kreuzer
The essential library I use for all my projects is prettier. If I use prettier I usually also bring something like Husky or lint-staged that allows me to run prettier on all changed files on every commit.
When I implement a UI I usually pick some UI library such as Material or Taiga UI or if I do not need pre-packaged components I often grep Tailwind.
A2: Rainer Hahnekamp
@ngrx/store, date-fns, @playwright/test, @softarc/sheriff-core, angular-eslint
A3: Alex Inkin
Taiga Family is all I need 😄
A4: Erick Rodriguez
Angular Per see includes incredible features that are out of the box. For example, controlling an HTTP request could be either done by HTTPClient, or if I were to modify the request, I would use an interceptor. Because my job is related to IU, I would like to find more Styled Components to combine with DaisyUI with is a real time-saver.
Just that: Angular Styled components with DaisyUI would be my choice.
Q2: What’s your go-to strategy for debugging Angular components?
A1: Kevin Kreuzer
When debugging Angular components, I employ a combination of techniques to efficiently identify and resolve issues:
- Console.log: Yes, console.log. Inserting
console.log()
statements at crucial points in the code helps me inspect variable values and understand the flow of execution. It's a quick way to verify that data is being passed correctly between components and services. - String Interpolation in Templates By using string interpolation (e.g.,
{{ variable }}
) within the component's template, I can display the values of variables directly in the UI. This method has the advantage of showing whether Angular's change detection (CD) is running correctly, as it updates the displayed values in real-time. - Angular DevTools for Dependency Injection Errors: For more complex issues, especially related to dependency injection (DI), I utilize the Angular DevTools browser extension. It provides a detailed view of the component tree, services, and DI tokens, allowing me to trace and resolve DI errors effectively.
- Debugger in Browser DevTools with Sourcemaps: I use the browser's built-in developer tools to set breakpoints and step through the code. Sourcemaps are essential here, as they map the transpiled JavaScript back to the original TypeScript code, making it easier to debug. This method helps in understanding the execution context and identifying logical errors.
A2: Rainer Hahnekamp
I'm using the debugger from the Chrome DevTools. If crunch-time starts, console.log
as well 😊
A3: Alex Inkin
console.log
😊
A4: Erick Rodriguez
My favorite way to debug Angular components is based on the following strategy:
- Use Angular Devtools to inspect the component tree and see the data flow. If where I am I cannot inspect the component tree, I would use VSCode to debug the component.
- VsCode is particularly useful for debugging Angular Components if what you want is to see how your data is flowing. Personally I HATE adding "debugger" or force a breakpoint on the browser because THE CLIENT will not do that, however VSCode presents to me what the browser has in a more friendly way. From here I can inspect many things, like the data flow, the state of the component, and the state of the data returned by a particular service.
Q3: Have you ever created a custom RxJS operator? What problem did it solve?
A1: Kevin Kreuzer
Yes, I have created a custom RxJS operator to address a specific need in a project involving a custom dropdown UI component. Inside the Dropdown we wanted to give the user the opportunity to filter results based on keystrokes, but at the same time we wanted to exclude Key up, Key down and other keys which were doing other things in the dropdown, for example key down selects the next element. Therefore we wrote this operator.
export function onlyAlphaNumericKeys(src: Observable<KeyboardEvent>): Observable<KeyboardEvent> {
return src.pipe(filter((keyboardEvent: KeyboardEvent) =>
/[a-z0-9-_äöü]/i.test(keyboardEvent.key)));
}
A2: Rainer Hahnekamp
Yes, for type predicates, since that didn't always work with the existing operators. Not sure how it is with the latest version of TypeScript though. Error-safe versions of switchMap
, concatMap
, etc, and a debug operator that logs out the values.
A3: Alex Inkin
Many times. Most often I used operators that enter/leave NgZone
.
A4: Erick Rodriguez
I have not had the opportunity to write my own RxJS Operators. While I love it, I have not had the opportunity to do so. However, RxJS presents many operators to play with that makes life easy, for example:
- I have used the "debounceTime" operator to prevent a user from making too many requests to the server.
- I have also used the "switchMap" operator to cancel the previous request if a new request is made.
- I have also used the "catchError" operator to handle errors in the request.
So far, I had not had the need to create my own operator, but I am looking forward to doing so.
Q4: What unconventional challenges did you have while developing/architecting Angular apps?
A1: Kevin Kreuzer
One of the unconventional challenges I encountered was dealing with circular dependencies in a large-scale Angular application. As the application grew, the interconnectedness of modules, components, and services increased, leading to scenarios where two or more modules depended on each other directly or indirectly. Circular dependencies often caused build failures or runtime errors that were difficult to trace. Furthermore the tight coupling between modules made the codebase hard to maintain and understand.
A2: Rainer Hahnekamp
- SSR & asynchronous task issues
- Leaking side-effects from unsubscribed Observables
- Failing E2E tests with enabled hydration
- Odd error messages due to old TypeScript configurations
- Crashing Redux DevTools, because of too nested objects
- Non-Functioning Change Detection for
window.navigator.online
A3: Alex Inkin
As I often work on low level UI components there's a ton of browser quirks to deal with, especially with Safari/iOS. I would argue iOS is the only source of frustration at my job. If you use iPhone and any site shows correctly at your phone — remember that the cost was irreversible nerve cell loss suffered by some poor guy like me.
A4: Erick Rodriguez
Dealing with people tends to be one of the critical parts of developing Angular apps, since everyone has their own way to kill fleas. For this, when I join as lead for a team, the first thing I do is to setup guardrails to prevent the team to go monkey coding.
Things like Architecture Decision Records (or ADRs) are a good way to prevent the team to go wild: it has rules, why we use such rules and a clear justification on certain patterns. Merge requests are based on such decisions and keeps the team in the same page.
Ah, one more thing: I believe Senior Angular developers should be able to understand concepts of Software Architecture, and I find incredible frustrating to find Seniors not understanding that the code they are making is not just for our current project, but it is part of a bigger set of elements that interact with the application. Not having that kind of perspective affects the team and the project in the long run.
Q5: How do you manage API type consistency between frontend, mobile app, and backend?
A1: Kevin Kreuzer
Based on your setup and tech stack there are different approaches, in practice I have seen the following methods, each has advantages and downsides:
- Shared TypeScript Interfaces: By defining data models and interfaces in a shared package or library (e.g., an NPM package), both the frontend and backend can import and use the same type definitions. This approach ensures that any changes to data structures are immediately reflected across all layers.
- Swagger/OpenAPI Specifications: Utilizing Swagger to define API contracts allows automatic generation of client SDKs and server stubs. Tools like Swagger Codegen can generate TypeScript clients that the frontend can use, ensuring consistency.
- Contract Testing: Implementing contract tests verifies that the backend services adhere to the expected API contracts defined by the frontend and mobile applications. This helps catch discrepancies early in the development process.
- tRPC: Using tRPC provides end-to-end type safety by sharing types directly between the client and server in TypeScript applications. This eliminates the need for manual type definitions and keeps the API contracts in sync. If you want to know more checkout our blogpost here: https://angularexperts.io/blog/angular-trpc
- Monorepo Architecture: Adopting a monorepo setup (e.g., using Nx or Lerna) allows all parts of the application—frontend, backend, and mobile—to reside in a single repository. Shared code and types can be easily managed and imported where needed.
A2: Rainer Hahnekamp
I use OpenAPI as often as I can. If it is very critical, I add an additional layer of Zod for runtime validation of the types.
A3: Alex Inkin
Use DI tokens that detect platform through user agent and then branch CSS and JS depending on it when necessary.
A4: Erick Rodriguez
While desirable to have a common stack where the types are shared across a monorepo, not all the time we will find this scenario. If you are blessed to have OpenAPI, you can generate the types for the frontend and the mobile app from the OpenAPI specs, which is a real time-saver.
If you don not have it, but you have swagger, it will be more difficult as swagger might not be as accurate as OpenAPI, and you depend on verbal contracts between your backend team and your frontend team and record those decisions on the respective ticket.
If you are bestowed with wisdom, you could use Zod to evaluate the response and ensure it matches the expected response. This way, you can ensure that the response is what you expect, but at the same time, backend wont have any clue of what you are doing.
Documented contracts, in this case, works better. Not all the time you will find the toy you want, but you can always play with the toys you have.
Q6: What’s your approach to optimizing the performance of large Angular components?
A1: Kevin Kreuzer
- Change Detection Strategy OnPush: Setting the component's change detection strategy to
OnPush
reduces the frequency of change detection cycles. This means Angular only checks for changes when the component's inputs change or when an event occurs within the component, improving performance. - Defer Loading with defer: Using the
defer
directive to load heavy or non-critical parts of the template lazily. This defers the loading and initialization until it's necessary. - Cleanup Subscriptions: Ensuring that all
Observable
subscriptions are properly unsubscribed when the component is destroyed. This prevents memory leaks and unnecessary processing. - Use Async Pipe: Leveraging the
async
pipe in templates automatically handles subscription and unsubscription, reducing boilerplate code and potential memory leaks. - Using Signals: Embracing Signals resolves the subscription issue since the subscriptions are automatically managed by RxJs interop like toSignal for example.
- Avoid Expensive Computations in Templates: Moving complex calculations out of templates and into component code or using memoization to cache results.
- Optimize Loops and DOM Manipulations: Avoiding deep nested loops or large iterations in the template. Using trackBy functions in
*ngFor
directives to help Angular optimize rendering. With new @for track is mandatory but in past it wasnt.
A2: Rainer Hahnekamp
Don't preoptimize. Before zoneless, I avoided even OnPush. If there is a performance issue, try to locate it. The profiler from the Angular DevTools shows the change detection cycles and how long they run. They are the starting point for me.
A3: Alex Inkin
Don't make it bad in the first place.
A4: Erick Rodriguez
It is a rule of thumb to keep the components as small as possible. If a component is too big, it is a sign that the component is doing too much. For this, I would split the component into smaller components and use the "OnPush" change detection strategy to prevent Angular from checking the component tree every time a change is made. If I had Nx, I could write custom eslint rules that can prevent the component from growing too much. For example, compoents for more than 500 lines? a custom eslint rule will prevent that.
BTW, there is an incredible article about atomic composition that every angular developer should read: https://atomicdesign.bradfrost.com/chapter-2/ as it resumes the idea of keeping components small and reusable from the perspective of design.
Q7: How have you used TypeScript generics to simplify handling complex interfaces in Angular? If so, any examples?
A1: Kevin Kreuzer
Yes, TypeScript generics are a powerful feature that I've used to create flexible and reusable code structures. A simple example would be a generic ApiService:
export class ApiService<T> {
constructor(private http: HttpClient, private url: string) {}
getAll(): Observable<T[]> {
return this.http.get<T[]>(this.url);
}
getById(id: string): Observable<T> {
return this.http.get<T>(`${this.url}/${id}`);
}
create(item: T): Observable<T> {
return this.http.post<T>(this.url, item);
}
update(id: string, item: T): Observable<T> {
return this.http.put<T>(`${this.url}/${id}`, item);
}
delete(id: string): Observable<void> {
return this.http.delete<void>(`${this.url}/${id}`);
}
}
This service can be instantiated with different types:
const userService = new ApiService<User>(httpClient, '/api/users');
const productService = new ApiService<Product>(httpClient, '/api/products');
A2: Rainer Hahnekamp
I did some proof-of-concepts/drafts for the NgRx Signal Store and am also working on the ngrx-toolkit, which heavily utilizes advanced TypeScript concepts.
A3: Alex Inkin
I always try to work with generics rather than particular data models because my job is reusable flexible components.
A4: Erick Rodriguez
Writting the right type generics will help you not only to keep your code totally reusable, but also easy to understand.
For example, your HttpObservable will return something of a expected type. If you are using a service that returns a list of items, you can use generics to define the type of the list, and have those type definitions to be out on the service implementation on the component side of things, as the subscription will resolve automatically your generic type, and you do not need to assert its type.
Services should hold absolute control over the type handling on your request/response to data services, as it will help you to keep your code clean and easy to understand.
Q8: Can you use ::ng-deep
?
A1: Kevin Kreuzer
Yes, I can use ::ng-deep
in certain scenarios, but with caution. ::ng-deep
is a pseudo-selector that allows you to apply styles to child components that are encapsulated, effectively penetrating Angular's style encapsulation.
A2: Rainer Hahnekamp
Yes, but I try to avoid them. If I want styles to apply to subcomponents, I usually disable view encapsulation and create rules that apply to the component selector.
A3: Alex Inkin
Yes.
A4: Erick Rodriguez
I CAN, but implementation now a days seems a trade off between Angular Material Components and your needs. If you are using other UI library, it might depend on the implementation you use on it. Personally, I used in extremelly few cases, and I tend to avoid it as much as possible to avoid polluting the design system.
Q9: How do you ensure reusability in your SCSS for large-scale Angular projects?
A1: Kevin Kreuzer
To promote SCSS reusability and maintainability, I employ the following practices:
- Mixins: Creating SCSS mixins allows me to define reusable style patterns.
- Functions: Using SCSS functions for calculations or color manipulations.
- Variables: Defining variables for colors, fonts, spacing, etc. While SCSS variables are useful, I often prefer CSS variables for their runtime flexibility and ability to be manipulated via JavaScript.
- @extend : Using
%
selectors and@extend
to share common styles. - Modular Architecture: Organizing SCSS files into modules and partials for different components, themes, or features.
- BEM Methodology: Adopting the Block Element Modifier (BEM) naming convention for classes to enhance readability and reusability.
A2: Rainer Hahnekamp
In general, I have given up the idea of creating global SCSS mixins on my own. I use tailwind, Angular Material, PrimeNG and keep the individual CSS in the component.
A3: Alex Inkin
Angular is great at encapsulating styles so we do not really need to worry about it.
A4: Erick Rodriguez
Globals are a great tool to reuse scss across multiple systems exposed through a library, and it tends to be quite effective when you have a design system in place. Those globals can be overriden in specific apps to not only allow reusing the same global keys, but also to allow the app to have its own identity.
Q10: Have you contributed to the Angular ecosystem/community? If so, what was your contribution?
A1: Kevin Kreuzer
Yes, I've actively contributed to the Angular community by developing and maintaining several open-source projects:
- svg-to-ts: A tool that converts SVG files into TypeScript modules. It helps developers include SVGs directly into their Angular applications with type safety and without the overhead of HTTP requests.
- pretty-html-log: A utility for logging HTML content prettily in the console, making it easier to debug and inspect tests.
- ng-sortgrid: An Angular library that provides a sortable grid layout, enabling drag-and-drop rearrangement of grid items.
- ng-parsel: A parser library for Angular applications that assists in processing and interpreting complex data formats.
- nx-release: A tool designed to streamline the release process in Nx monorepos. It automates versioning, changelog generation, and publishing of packages.
And more, in total I am currently maintaining 20+ OS projects.
A2: Rainer Hahnekamp
Yeah, so I contribute mainly via videos on my personal YouTube channel, and I also do ng-news, a weekly newsletter available on multiple platforms.
Other then that, I am:
- part of the NgRx team as a trusted collaborator
- the main developer behind Sheriff, which provides module rules for TypeScript projects.
- maintaining the ngrx-toolkit and contributing the Redux Devtools and Redux extensions.
- about to start to get more into Native Federation as well, which is a solution for MicroFrontends based on web standards like import maps.
A3: Alex Inkin
I'm a tech writer and an open source maintainer.
A4: Erick Rodriguez
I have been active on Angular Spaces for almost 4 months, and Writting articles has been a way to contribute to the community. Hello to our Discord community in Angular Spaces!
Q11: What is your strategy for refactoring a bloated Angular component?
A1: Kevin Kreuzer
Refactoring a bloated component involves careful planning and execution to improve its maintainability and performance. My strategy includes:
- Identify and Split Responsibilities: Analyze the component to identify distinct functionalities or responsibilities that can be extracted.
- Create Child Components: Break down the UI into smaller, reusable child components.
- Extract Services: Move business logic, data fetching, or state management into services.
- Separate View Logic from Business Logic: Ensure that the component focuses on presentation logic while business logic resides in services or state management solutions (e.g., NgRx).
- Reusable Templates: Use structural directives (
*ngIf
,*ngFor
) and template references to create reusable templates or directives that can be shared across components. - Simplify Template: Clean up the template by removing unnecessary bindings or complex expressions.
A2: Rainer Hahnekamp
First, I will make sure that there are good E2E tests available. If I have to make big changes, they are the only ones that keep me and the application safe. Then, I'd start to cut the application into feature and shared modules. Once that is done, I'd go into each feature module and split it into different module types: feature, ui, data, and model.
That should give me a good start for further refactoring, which typically depends on the use case.
A3: Alex Inkin
Break it down to small independent pieces first, then rewrite code better.
A4: Mateusz Łędzewicz
A bloated Angular component is a common issue in almost every project I've been a part of or consulted on. Unfortunately, there’s no magic pill that can solve all the problems at once, but there are strategies that can help. Let’s start with some actionable steps.
Tactical Level: Layers
I like to think of every project as having three key layers: UI, Application Layer, and Data Layer. If you're not aware of this separation, it’s likely because all these layers are crammed into your component.
Here’s the first round of refactoring:
- Move all data-related logic to data services.
- Move all application logic (state, models, etc.) to application services.
- Keep only the UI logic in the component. It should:
- Fetch data through the application layer (using queries).
- React to events and trigger commands in the application layer.
This approach will undoubtedly make your component slimmer, but that’s just the beginning.
Operational Level: Domain Focus
Now, let’s zoom out and take a broader view. The strategy mentioned above is a good start, but there’s another common issue in many projects: multi-domain components.
This problem arises because developers often think of a component as a "page." However, to create reusable and extendable components, we should break them down into smaller, more focused pieces that can be reused across different routes. If we treat components as small, single-purpose blocks of code, we’ll end up with more granular and maintainable components. Of course, there’s more to discuss about how to connect these smaller components, but I don’t want to make this article as "bloated" as the components we're trying to fix! 😄
Do I Even Need a Component?
Here’s another trick for handling bloated components: Don’t create a component at all. I know it sounds weird, but as Angular developers, we often default to creating components when it’s not always necessary. In some cases, a directive might be a better solution.
Summary
The question may seem straightforward, but it touches on several important aspects of development craftsmanship. From separating layers and applying Domain-Driven Design (DDD) principles to splitting features and leveraging the full potential of the framework, there’s a lot to consider. However, the first step is always to keep your layers clean and separate!
A5: Erick Rodriguez
As I have explained before, large components are a total headache, not only for the application to hold it, but also for the dev to read it. Do you want to read 3000 lines of beautiful spagghettified components? I pass, thank you. However if you have no choice but to refactor that mammoth of component, I would always use principles of atomic design to prevent not only to insult myself by writting bloated code (I am a Senior, I should know better), but also to prevent the component to grow too much.
That said, small components are EASY to test, larges pieces made of this small components ARE ALSO SMALL, and so on. If you keep atomic composition in place (and discipline yourself in the process) you will note that refactoring mammoths of components will be easier the more you practice such atomic principles.
Q12: What was the most complex form you worked with?
A1: Kevin Kreuzer
The most complex form I've worked with was a highly dynamic layout builder which basically allowed you to create any kind of grid layout via a GUI. Here’s a simplified drawing of it:
The form was complex because it had had the following characteristics:
- Dynamic Fields: Users could add, remove, and reorder form fields on the fly.
- Conditional Logic: The visibility and validation of certain fields depended on the values entered in other fields.
- Nested Form Groups: The form included nested groups and arrays to handle complex data structures like lists and subforms.
A2: Rainer Hahnekamp
It wasn't that big. A wizard containing four different pages. The biggest challenge was to keep the validation rules in sync (frontend & backend).
A3: Alex Inkin
A dynamic settings for a Bitcoin node service that is defined by a spec object coming from backend.
A4: Erick Rodriguez
Dynamic forms tends to be the trade off of everyone that has ever touch Angular projects. In my personal perspective, handling reactive forms tends to be the way to go as you can register new fields on the fly, and remove them as well, also its validation is more programatically than template driven forms. So far, medical application bills tends to be the one of the most complex forms I have ever worked with, as it has a lot of fields, and conditioning responses on certain fields forces you to generate new sets of input data with their respective validations to be made.
Q13: How do you implement validation for in complex forms?
A1: Rainer Hahnekamp
ngx-formly allows advanced validation rules. Then there is also vest, which I still want to try out. Another option is to put the formGroup into a Service or state management. That should be enough for most cases.
A2: Alex Inkin
Mostly through validators on reactive form and sometimes with NG_VALIDATORS
directives to declaratively apply validation in template. For example, when fields are cross-dependent like "confirm password"
A3: Erick Rodriguez
I always go for complex forms by using a reactive form approach. Common validations can be placed by using directives on the requested inputs to lower the amount of programatical validation on the main form component. For example, if you have a date input, you can use a directive to validate the date, and if the date is not valid, you can prevent the form to be submitted.
Q14: Signals, Observables, Promises - tell me all about differences and when to use which.
A1: Kevin Kreuzer
Promises deliver a single value, therefore they are good for things that just deliver single values such as HTTP requests.
Observables deliver one or multiple values either async or sync. Therefore they are good for event based APIs. A great use case for Observables is a stream of clicks.
Signals are kinda in between Push and Pull. They also deliver multiple values. They have the possibility to notify you when something changes but they still have to actively be called (either by you or Angular) in order to get the value therefore it’s kinda Push / Pull based.
I see that many people are confused about when to use RxJs and when to use Signals. Signals should generally be used whenever it makes sense to ask the question: “What is the current state?”.
Therefore HTTP requests will never be Signals since it doesn’t make sense to ask for a current value of an HTTP request. You either get a value or an error, since its only one value Promises would make sense here. Form on the other hands are an ideal candidate for Signals since it makes sense to ask for the current value.
A2: Rainer Hahnekamp
Signals whenever you are in the template. We know that Signal Components are coming and we can prepare for that. Generally speaking, I would try Promises first. As soon as I see it is going to be about managing race conditions, or I need to make use of the powerful pipe operators it is going to be RxJs.
So RxJs for the harder stuff and Signals when it is easier, or I am in the template.
A3: Alex Inkin
Signals for all things possible, promises for one time async data, observables for event-like multiple async data processing.
A4: Erick Rodriguez
Signals are a way to handle events in Angular, and has become a daily trade now a days. Signals can be read-only or writable, and you have to determine the best case scenario on how to use it. Observables handles streaming of data, and it is a way to handle data in a more reactive way. Observables can be used to handle data that will be resolved in the future, and it is a way to handle data in a more reactive way. Promises handles one multiple expected results from asynchronous operations that can be used on the future. It is quite useful for the reactivity of your component.
Q15: Have you implemented custom decorators? If so, what was the use case?
A1: Rainer Hahnekamp
No, never. I also never implemented an annotation in Java. Guess, it is not my kind of style.
A2: Alex Inkin
A few times. Most popular was memoization decorator that implemented lazy getter pattern and for functions it worked kind of like pipe, meaning it recalculated method only when arguments changed.
A3: Erick Rodriguez
Nah, so far not yet. I have not had the need to implement custom decorators.
Q16: What kind of states do we have in the app and how do you manage them?
A1: Rainer Hahnekamp
I differentiate between local state which I don't manage explicitly. Values in a single component for example. Then UI and "entity/server" state. For entity/state I tend to go with the Signal Store. UI state doesn't happen that often because my applications are most of the times forms and grids, but if, then also state management.
A2: Alex Inkin
I mostly work on low level UI, I never really manage global state. But when I do it's basically a behavior subject.
A3: Erick Rodriguez
Traditionally we have a global state which can be used by something like NgRx, and a local state that can be used by the component itself using Subjects. That is how traditionally we manage the state of the application.
Q17: What’s your approach to lazy-loading modules in Angular?
A1: Rainer Hahnekamp
So if I have my feaure/domain groups, I load them lazily. I don't lazily load every component that is attached to a route.
A2: Alex Inkin
Lazy routing mostly, sometimes lazy loaded dynamic components.
A3: Tomas Trajan
Now that Angular provides standalone APIs, I think is start to talk about lazy loading features (views, or sometimes pages, even though a feature can definitely be smaller than a page). Additionally, we should always strive to minimize the amount of ways we’re doing things in our workspace.
Let’s take routing as an example, currently there are at least 4 ways to perform routing
- eager route to a component with component - is eager so doesn't apply directly for our example (even though first component of lazy feature can be referenced as eager component)
- lazy route to a component with loadComponent
- lazy route to a module with loadChildren
- lazy route to routes based feature (feature-x.routes.ts) with loadChildren
In this case, we want to pick the best one and stick with it. I would personally recommend always define a lazy route as a routes based feature which is referenced with loadChildren which is the most modern and flexible way of doing things. Then, in case our lazy feature contains sub navigation, we can lazy load additional components with loadComponent.
We should do this also (and especially) in the case if our lazy feature has only one component to start with because the chances are high the requirements will be extended or adjusted in the future.
Proposed approach allows us to seamlessly grow to any amount of complexity while maintaining a single unified way of doing this across the whole project which removes cognitive load because everything looks and is done the same way!
// app.routes.ts
export const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./features/dashboard/dahsboard.routes.ts').then(m => m.routes)
}
]
// dashboard.routes.ts (routes based lazy feature)
export const routes: Routes = [
{
path: '',
loadComponent: () => import('./dahsboard.component.ts').then(m => m.DashboardComponent) children: [ // additional routes can come here (always display DashboardComponent + nested routes) ]
},
// or here, replace dashboard component with the dashboard editor in the view (sibling views)
// which is easy to extend with in the future, eg
{
path: 'editor',
loadComponent: () => import('./dahsboard-editor.component.ts').then(m => m.DashboardEditorComponent)
},
// or even a larger sub-feature
{
path: 'forecast', // forecast lazy sub feature added later
loadChildren: () => import('./forecast/forecast.routes.ts').then(m => m.routes)
}
];
A4: Erick Rodriguez
If you have a big application with too many moving parts, routing your component in a way that allow the use of lazy loading is a must. This way, you can prevent the application to load all the components at once, and only load the components that are needed. This way, you can prevent the application to be slow and only load the components that are needed.
However, debugging lazy loaded modules can be a pain as you cannot assert an specific error caused by foreign aspects of the application that are difficult to control.
Q18: How do you handle HTTP errors globally?
A1: Rainer Hahnekamp
With an HttpInterceptor and have a global ErrorHandler as fallback for non-HTTP errors.
A2: Alex Inkin
By providing ErrorHandler service
A3: Tomas Trajan
In general I would nowadays try to handle errors as close to the part of the UI where they originated as that seems to be the UX best practice all big players are following in recent years.Such outcome could be achieved in many different ways:
- component based state management
- component store
- dedicated state slice of the global store with loading and error states for the entity which can be accessed locally using seelctors
But if the goal would be to really handle things globally, e.g. with a global notification or toast,then the best way to achieve this in Angular would be to implement a global HTTP interceptor which will filter HTTP events based on their status codes.Then if the error is detected, and there is some unified format of the errors provided by the API, such interceptor could extract the error metadata and opena locally implemented overlay (or some component like toast, or message from a 3rd party component library) to show this error to the user. Besides that, such an interceptor could also provide fallback values like empty array or entity for the target ui which could then display an empty state, e.g. no entities were found... Another way would be to implement a custom global error handler which is also possible to override in the Angular and filter for specific types of errors and implement similar global notification logic.
A4: Erick Rodriguez
You can always use the combination of Interceptors and a side UI service that helps you to leverage errors when something happens. For example, you can use a side UI service to show a toast message when an error happens, and you can use an interceptor to handle the error and trigger a change on the state of the application to turn on such specific toast.
The End
Thank you to all the experts for their time. As you can see, every expert has a different approach to answering, and we truly appreciate every contribution. We hope you could learn something from it. What do you think about these answers? Do you have any other questions you would add? Let us know in the comments 😎
More answers coming soon!