5 Essential NPM Packages Every Angular Developer Should Know for Enhanced Productivity

As Angular developers, we constantly seek tools to enhance our productivity and streamline our development workflow. Our goal is to minimize boilerplate code, simplify project configuration, and reduce maintenance overhead, allowing us to focus on building robust features.

In this article, I will share five NPM packages that have significantly impacted my Angular development experience. These packages have proven invaluable in my projects, offering solutions to common challenges and promoting cleaner, more maintainable code. They have become essential tools in my development arsenal, drastically improving my workflow by smoothing out common pain points and allowing me to concentrate on creating great features instead of getting bogged down in repetitive tasks and configuration.

ngneat/query: The Entity State Management

When working with server-side data, developers often need to track the state of data requests. Questions like “Is the data loading?”, “Did it load successfully?”, “Is there an error?”, and “Is the data available?” are common. Traditionally, managing these states for each server call requires maintaining a complex nested object within the application’s state, including properties like loading, loaded, error, and data.

As the number of server-side entities increases, the complexity of state management grows exponentially. This complexity not only makes the code harder to maintain but also increases the risk of bugs. @ngneat/query addresses these challenges by automatically managing these states for you. It provides out-of-the-box support for tracking loading, success, error states, and the data itself, without requiring manual state management logic. This approach significantly reduces boilerplate code, leading to cleaner code and an improved developer experience.

@ngneat/query, inspired by the acclaimed TanStack Query for React, simplifies fetching, caching, and managing server-state in Angular applications. It automatically handles loading, success, error states, and the data itself, addressing common data state management challenges.

Installation is a simple npm command:

npm i @ngneat/query

Here’s how you can use @ngneat/query to fetch data and manage its state:

import { injectQuery } from ‘@ngneat/query’;

@Component({
…,
standalone: true,
template: `
@if (todos().isLoading) {
Loading…
}
@if (todos().data; as data) {
<p>{{ data[0].title }}</p>
}
@if (todos().isError) {
<p>Error</p>
}
`,
})
export class TodosPageComponent {
#http = inject(HttpClient);
#query = injectQuery();
todos = this.getTodos().result;

getTodos() {
return this.#query({
queryKey: [‘todos’] as const,
queryFn: () => this.#http.get<Todo[]>(‘https://jsonplaceholder.typicode.com/todos’),
});
}
}

The provided code snippet utilizes a new feature in Angular known as Signals. However, it’s worth noting that @ngneat/query also offers an observable option through results$. The properties present in the results$ are identical to those found in the signal object.

In Angular applications, refreshing data based on user actions can be complex. Traditionally, it involves manually triggering fetches and managing loading, success, and error states. This leads to boilerplate code, such as using Subject to emit events and updating the state when data arrives, increasing code complexity.

With @ngneat/query, refreshing data is simplified. Call the refetch function on the result object to initiate a new data fetch, and @ngneat/query handles the loading, success, and error states automatically.

@ngneat/query also offers options like refetchInterval and retry for robust data fetching:

this.query({
queryKey: [“todos”],
queryFn: () => this.http.get<Todos>(“https://api.example.com/todos”),
refetchInterval: 10000,
retry: 5,
});

Effortless Infinite Scroll and Pagination with ngneat/query

Managing dynamic data loading in web development, particularly for features like infinite scroll or pagination, can be a tedious task. These features are crucial for improving user experience with large datasets, enabling seamless navigation without overwhelming browsers or patience. @ngneat/query simplifies implementation significantly, offering the injectInfiniteQuery function. This provides developers with a powerful tool for fetching data in chunks based on user actions or page navigation.

Consider the scenario where you’re building a feature that lists todos, and you want to load these todos in batches as the user scrolls down — a common pattern in social media feeds, content-rich sites, and other applications dealing with large amounts of data. Here’s how @ngneat/query simplifies this:

@Injectable({ providedIn: “root” })
export class TodosService {
private infiniteQuery = injectInfiniteQuery();
private api = inject(ApiService);

getTodos(limit = 5, searchQuery = {}) {
return this.infiniteQuery({
queryKey: [“todos”, searchQuery] as const,
queryFn: ({ pageParam }) =>
this.api.search({ …searchQuery, limit, offset: pageParam }),
initialPageParam: 0,
getPreviousPageParam: (firstPage) => firstPage.previousId,
getNextPageParam: (firstPage) => firstPage.nextId,
});
}
}
@Component({
template: `
<ng-container *ngIf=”todos() as result”>
<!– Your content display logic here –>
<form … (ngSubmit)=”setSearchParam($event)”>
<button
[disabled]=”!result.hasNextPage || result.isFetchingNextPage”
(click)=”result.fetchNextPage()”
>
Load More
</button>
</form>
</ng-container>
`,
})
export class TodosComponent {
#router = inject(Router);
#todosService = inject(TodosService);
todos = inject(ActivatedRoute).queryParamMap.pipe(
map((queryParams) => queryParams[“params”]),
switchMap(
(searchQuery) => this.#todosService.getTodos(5, searchQuery).result
)
);
setSearchParam(searchQuery: Record<string, string>) {
this.#router.navigate([], {
queryParams: searchQuery || null,
queryParamsHandling: “merge”,
});
}
}

The concept here is to invoke the “submit” function and modify the querystring with the form data to ensure data persistence and prevent its loss.

In the provided code snippet, we inject the ActivatedRoute to listen to changes in the querystring. When a change occurs, the code switches the stream to getTodos function and retrieves the result object from @ngneat/query. Upon form submission, the query string is updated, retriggering the flow.

The template’s button invokes the fetchNextPage function, available on the @ngneat/query result object, triggering the retrieval of the subsequent data chunk.

TanStack Query Package is Now Available for Angular:

The TanStack Query package, originally for React, now supports Angular with capabilities similar to @ngneat/query.

Open Props — Supercharged CSS Variables

Open Props is a collection of CSS custom properties (variables) that offer a comprehensive toolkit for modern web design. Built on a foundation of CSS variables, Open Props ensures developers can easily adapt their styles without being constrained by the framework. Its use of just-in-time compilation means only the CSS variables actually used in a project are generated, resulting in lean stylesheets and optimized application performance.

At its core, Open Props is designed with flexibility in mind. It provides a robust foundation to build any visual style you envision. CSS variables allow values to be reused and overridden with ease across stylesheets, promoting consistency and scalability in design.

Embracing Predefined Styling and Core Features

Open Props shines by providing a vast array of CSS variables for colors, typography, animations, and media queries. Embracing its thoughtfully curated design tokens significantly reduces the chances of inconsistency in UIs.

Installation is straightforward:

npm install open-props

Importing in your styles:

@import “open-props/style”;

Open Props offers comprehensive variables for typography, including fluid sizing:

body {
font-family: var(–font-sans);
font-size: var(–font-size-fluid-3);
line-height: var(–line-height-3);
}

It excels in color theming, supporting light and dark modes:

:root {
–background-color: var(–color-white);
–text-color: var(–color-black);
}
@media (prefers-color-scheme: dark) {
:root {
–background-color: var(–color-black);
–text-color: var(–color-white);
}
}

Complex animations can be applied with ease:

.shake-in {
animation: var(–animation-shake-y), var(–animation-fade-in), var(–animation-slide-in-left);
}

Responsive design is facilitated through gradients and media queries:

.element {
background-image: var(–gradient-5);
}
@media (–OSdark) {
.element {
background-image: var(–gradient-15);
}
}

Integration with Angular and Other Frameworks

Open Props integrates seamlessly with various frontend frameworks, including Angular. It can be used to define global styles in an Angular project:

@import “open-props/style”;
:root {
–font-primary: var(–font-sans);
}

Open Props can also enhance and personalize third-party libraries like Angular Material:

@use “@angular/material” as mat;
@include mat.core();

$typography: mat.define-typography-config(
$font-family: “var(–font-primary)”
);

Whether you’re using Angular, React, Vue, Astro, or Qwik, Open Props can be incorporated without friction, making it a versatile choice for developers working in diverse ecosystems.

Formly — Dynamic forms in Angular

A crucial part of many Angular applications is form handling. Whether it’s a simple contact form or a multi-step registration process, crafting forms can be meticulous. Formly has been my go-to when it comes to tackling this aspect of development.

Advantages of Using Formly

The core advantage of Formly lies in its simplicity and maintainability. By utilizing JSON schemas to define the structure of forms, Formly dramatically reduces the complexity typically associated with form creation. This approach not only makes forms easier to maintain but also facilitates dynamic updates, such as conditionally adding or removing fields, without touching the HTML template.

TypeScript Support and Integration with UI Frameworks

Formly’s seamless integration with TypeScript provides developers with auto-completion for field properties, significantly improving productivity and reducing the risk of errors.

Formly’s compatibility with various UI frameworks like Bootstrap, Material, Ionic, PrimeNG, Kendo, and NG-ZORRO ensures consistent styling across applications. To use Formly with these frameworks, install the corresponding package:

npm i @ngx-formly/{bootstrap/material/ionic/primeng/kendo/nativescript}

For instance, integrating Formly with Angular Material:

import { FormlyModule } from “@ngx-formly/core”;
import { FormlyMaterialModule } from “@ngx-formly/material”;

@NgModule({
imports: [ReactiveFormsModule, FormlyModule.forRoot(), FormlyMaterialModule],
})
export class AppModule {}

Creating Custom Form Controls

Formly simplifies the creation of custom form controls by abstracting the complexities of implementing ControlValueAccessor. Instead, you extend the FieldType class, which provides direct access to the formControl instance of the field.

Here’s an example of a custom input field:

@Component({
selector: “formly-field-custom-input”,
template: `<input
type=”text”
[formControl]=”formControl”
[formlyAttributes]=”field”
/>`,
})
export class CustomInputFieldType extends FieldType<FieldTypeConfig> {}

Masked Input Example

Creating a masked input field with Formly is straightforward. Here’s an example using @ngneat/input-mask:

@Component({
selector: “formly-field-masked-input”,
template: `<input
[formControl]=”formControl”
[inputMask]=”props.inputMask”
/>`,
imports: [InputMaskModule],
})
export class FormlyFieldMaskComponent extends FieldType<FieldTypeConfig> {
override defaultOptions = {
props: {
inputMask: createMask({ alias: “datetime”, inputFormat: “dd/mm/yyyy” }),
},
};
}

Using the custom masked input within a Formly form:

fields: FormlyFieldConfig[] = [
{
key: ‘birthdate’,
type: ‘mask’,
props: {
label: ‘Birthdate’,
placeholder: ‘DD/MM/YYYY’,
required: true,
inputMask: createMask({ alias: ‘datetime’, inputFormat: ‘dd/mm/yyyy’ }),
},
},
];

Formly’s support for dynamic JSON schemas, integration with popular UI frameworks, built-in validation, and extensibility through custom field types make it an exceptional tool for managing forms in Angular applications.

rx-angular/state — Performance & DX

rx-angular/state stands at the forefront of state management solutions tailored for Angular, offering an innovative approach to managing state at the component level. This library simplifies the complexities associated with state management in Angular applications, enhancing productivity and streamlining state management processes.

Installation and Functional Creation API

Begin by installing the package:

npm install @rx-angular/state

rx-angular/state introduces the Functional Creation API, which consolidates state initialization and data connection logic into a single, coherent block, reducing boilerplate and increasing readability:

@Component({
template: `<post *rxFor=”let post of posts$” [post]=”post” />`,
imports: [RxFor],
})
export class PostListComponent {
private postResource = inject(PostResource);

private state = rxState<{ posts: Post[] }>(({ set, connect }) => {
set({ posts: [] });
connect(“posts”, this.postResource.fetchPosts());
});

posts$ = this.state.select(“posts”);
posts = this.state.signal(“posts”);
}

Class-Based Integration and Provider Usage

rx-angular/state also supports class-based integration, offering flexibility in how state can be managed within Angular components. RxState can be injected directly using Angular’s inject method or through constructor injection:

@Injectable({ providedIn: ‘root’ })
export class UserFacade {
private state = rxState<{ users: User[] }>(({ set, connect }) => {
set({ users: [] });
connect(“users”, this.userResource.fetchUsers());
});
vm$ = this.state.select();
}
@Component({
// …
providers: [UserFacade],
})
export class RxStateInjectionComponent {
vm$ = inject(UserFacade).vm$;
}

Extending RxState

Alternatively, extending RxState directly in your component class encapsulates RxState’s functionality within your component’s logic:

@Component({
// …
})
export class RxStateInheritanceClass extends RxState<{ foo: string }> {
value$ = this.select();
}

Seamless Stream Integration

rx-angular/state absorbs streams into application state without explicitly subscribing to them, handling subscription and unsubscription life cycles to prevent memory leaks:

newData$ = this.someService.getDataStream();
this.state.connect(“data”, newData$);

rx-angular/state brings out the best in both reactive programming principles and Angular’s efficient framework, making it an essential tool in an Angular developer’s toolkit.

nx — Smart Monorepos Fast CI

Managing multiple interdependent projects within a single repository can be daunting. However, with nx, monorepo management not only becomes manageable but also an absolute delight. It’s the lynchpin in my workflow that enables me to unify codebases and optimize development processes.

Scaling Codebases Intelligently with nx

nx stands out with its robust CLI tools and thoughtful integrations. It supports a slew of technologies and brings coherence to the development lifecycle. After getting started with a simple command, we’re ushered into a well-structured world of development ease:

npx create-nx-workspace@latest myworkspace

What truly wins developers over is how nx streamlines code sharing between backend and frontend applications. Thanks to its support for Typescript, sharing interfaces or utilities is effortless:

// In some shared library
export interface MyModel {
id: string;
title: string;
}
// In an Angular app
import { MyModel } from “@myworkspace/mylib”;

This streamlined sharing approach has led to more cohesive project ecosystems everywhere it’s implemented.

Efficient CI Integration and Code Change Isolation

One of nx’s most innovative features is its ability to understand the dependency graph of your workspace. This intelligence means you can run tests, builds, or deployments on only what’s changed — saving time and resources in Continuous Integration (CI) environments:

nx affected:test
nx affected:build

As someone who’s advised numerous clients on Nx integration, I can attest that this feature alone makes it an indispensable tool for modern development workflows.

The sophistication nx brings to the table is not about enhancing a single facet of our work; it’s about transforming our entire approach to architectural scalability. It’s become one of those rare tools that I firmly believe every developer should at least consider, especially when working within larger teams or complex projects.

Conclusion

Embracing these five essential NPM packages — ngneat/query, open-props, formly, rx-angular/state, and nx — has transformed my Angular development journey. Each one has earned its place by streamlining facets of application development that typically consume disproportionate amounts of time and energy.

I encourage you to delve into each of these packages, weave them into your workflow, and experience firsthand the uplift they bring to your developer life. And if you’ve found other tools that similarly elevate your DX in Angular projects, I’d love to hear about them — after all, we’re all in this constant journey of improvement together.

ngneat/query: https://ngneat.github.io/query/open-props: https://open-props.style/formly: https://formly.dev/rx-angular/state: https://www.rx-angular.io/Nx: https://nx.dev/

As a Software Architecture, I focus on designing specific architectures tailored to your project, evaluating different approaches, and offering critiques on your architectural designs. Through my review workshops, I provide valuable insights and practical recommendations to help your project achieve its long-term quality goals. Moreover, I offer extensive project assistance, encompassing question and answer sessions, as well as direct expert engagement for projects of critical importance.

How do these findings fit with your experience? I’d love to hear your thoughts! Share them in the comments. Let’s continue this conversation and learn from each other.

Follow me on Medium or Twitter to read more about Angular and web development!

5 Essential NPM Packages Every Angular Developer Should Know for Enhanced Productivity was originally published in ngconf on Medium, where people are continuing the conversation by highlighting and responding to this story.

Leave a Comment

Your email address will not be published. Required fields are marked *