Skip to content

docs(signals): apply new naming convention from Angular styleguide #4790

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 28, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 29 additions & 29 deletions projects/ngrx.io/content/guide/signals/rxjs-integration.md
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ import { map, pipe, tap } from 'rxjs';
import { rxMethod } from '@ngrx/signals/rxjs-interop';

@Component({ /* ... */ })
export class NumbersComponent {
export class Numbers {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to this comment from Matthieu, maybe a more specific name?

https://x.com/Jean__Meche/status/1925866491506831784

Suggested change
export class Numbers {
export class NumbersDoubler {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no recommendation in the official docs that components with a single word or generic name should be avoided.

https://next.angular.dev/style-guide#naming

This component is created just to show how rxMethod should be used. It does not have a template that visualizes a specific use case, and because of that, I haven't named it NumberList, NumbersCard, NumberDetails, or similar. In real-world scenarios, we would not create a component for logging double numbers anyway.

// 👇 This reactive method will have an input argument
// of type `number | Signal<number> | Observable<number>`.
readonly logDoubledNumber = rxMethod<number>(
@@ -32,20 +32,20 @@ Each invocation of the reactive method pushes the input value through the reacti
When called with a static value, the reactive chain executes once.

```ts
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { map, pipe, tap } from 'rxjs';
import { rxMethod } from '@ngrx/signals/rxjs-interop';

@Component({ /* ... */ })
export class NumbersComponent implements OnInit {
export class Numbers {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export class Numbers {
export class NumbersDoubler {

readonly logDoubledNumber = rxMethod<number>(
pipe(
map((num) => num * 2),
tap(console.log)
)
);

ngOnInit(): void {
constructor() {
this.logDoubledNumber(1);
// console output: 2

@@ -58,20 +58,20 @@ export class NumbersComponent implements OnInit {
When a reactive method is called with a signal, the reactive chain is executed every time the signal value changes.

```ts
import { Component, OnInit, signal } from '@angular/core';
import { Component, signal } from '@angular/core';
import { map, pipe, tap } from 'rxjs';
import { rxMethod } from '@ngrx/signals/rxjs-interop';

@Component({ /* ... */ })
export class NumbersComponent implements OnInit {
export class Numbers {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export class Numbers {
export class NumbersDoubler {

readonly logDoubledNumber = rxMethod<number>(
pipe(
map((num) => num * 2),
tap(console.log)
)
);

ngOnInit(): void {
constructor() {
const num = signal(10);
this.logDoubledNumber(num);
// console output: 20
@@ -85,20 +85,20 @@ export class NumbersComponent implements OnInit {
When a reactive method is called with an observable, the reactive chain is executed every time the observable emits a new value.

```ts
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { interval, map, of, pipe, tap } from 'rxjs';
import { rxMethod } from '@ngrx/signals/rxjs-interop';

@Component({ /* ... */ })
export class NumbersComponent implements OnInit {
export class Numbers {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export class Numbers {
export class NumbersDoubler {

readonly logDoubledNumber = rxMethod<number>(
pipe(
map((num) => num * 2),
tap(console.log)
)
);

ngOnInit(): void {
constructor() {
const num1$ = of(100, 200, 300);
this.logDoubledNumber(num1$);
// console output: 200, 400, 600
@@ -119,15 +119,15 @@ The `rxMethod` is a great choice for handling API calls in a reactive manner.
The subsequent example demonstrates how to use `rxMethod` to fetch the book by id whenever the `selectedBookId` signal value changes.

```ts
import { Component, inject, OnInit, signal } from '@angular/core';
import { Component, inject, signal } from '@angular/core';
import { concatMap, filter, pipe } from 'rxjs';
import { rxMethod } from '@ngrx/signals/rxjs-interop';
import { tapResponse } from '@ngrx/operators';
import { Book } from './book.model';
import { BooksService } from './books.service';
import { BooksService } from './books-service';
import { Book } from './book';

@Component({ /* ... */ })
export class BooksComponent implements OnInit {
export class BookList {
readonly #booksService = inject(BooksService);

readonly bookMap = signal<Record<string, Book>>({});
@@ -147,7 +147,7 @@ export class BooksComponent implements OnInit {
)
);

ngOnInit(): void {
constructor() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a question for my understanding of the new guide.
When is the constructor preferred over the lifecycle hook?
(because I noticed other samples that make use of the OnInit hook)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven’t found anything in the documentation that explicitly recommends preferring the constructor over lifecycle hooks. The closest reference is this guideline about keeping lifecycle methods short: https://next.angular.dev/style-guide#keep-lifecycle-methods-simple

There are also some hooks which will get deprecated for Signal Components, but OnInit is not among them: angular/angular#49682

That said, I personally default to using the constructor—unless I need to implicitly access an InputSignal. The main reason is that the constructor allows for early field initialization and any setup that needs to happen as soon as possible. It also runs within the injection context, which can be important for certain use cases.


Summary: I'm fine for moving to constructor, even if it is not listed in the new style guide.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When is the constructor preferred over the lifecycle hook?

@timdeschryver

When a reactive method is called with a signal or observable, it should be called within the injection context (constructor). Otherwise, the warning will be displayed in development mode.

At the time when I wrote this guide, we didn't have this warning. The guide is now in accordance with the latest changes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks for both answers 👍

// 👇 Load book by id whenever the `selectedBookId` value changes.
this.loadBookById(this.selectedBookId);
}
@@ -173,15 +173,15 @@ Further details can be found in the [Reactive Store Methods](guide/signals/signa
To create a reactive method without arguments, the `void` type should be specified as a generic argument to the `rxMethod` function.

```ts
import { Component, inject, OnInit, signal } from '@angular/core';
import { Component, inject, signal } from '@angular/core';
import { exhaustMap } from 'rxjs';
import { rxMethod } from '@ngrx/signals/rxjs-interop';
import { tapResponse } from '@ngrx/operators';
import { Book } from './book.model';
import { BooksService } from './books.service';
import { BooksService } from './books-service';
import { Book } from './book';

@Component({ /* ... */ })
export class BooksComponent implements OnInit {
export class BookList {
readonly #booksService = inject(BooksService);
readonly books = signal<Book[]>([]);

@@ -197,7 +197,7 @@ export class BooksComponent implements OnInit {
})
);

ngOnInit(): void {
constructor() {
this.loadAllBooks();
}
}
@@ -221,7 +221,7 @@ export class NumbersService {
}

@Component({ /* ... */ })
export class NumbersComponent implements OnInit {
export class Numbers implements OnInit {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export class Numbers implements OnInit {
export class NumbersDoubler implements OnInit {

readonly #injector = inject(Injector);
readonly #numbersService = inject(NumbersService);

@@ -250,15 +250,15 @@ If the injector is not provided when calling the reactive method with a signal o
If a reactive method needs to be cleaned up before the injector is destroyed, manual cleanup can be performed by calling the `destroy` method.

```ts
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { interval, tap } from 'rxjs';
import { rxMethod } from '@ngrx/signals/rxjs-interop';

@Component({ /* ... */ })
export class NumbersComponent implements OnInit {
export class Numbers {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export class Numbers {
export class NumbersDoubler {

readonly logNumber = rxMethod<number>(tap(console.log));

ngOnInit(): void {
constructor() {
const num1$ = interval(500);
const num2$ = interval(1_000);

@@ -277,15 +277,15 @@ When invoked, the reactive method returns the object with the `destroy` method.
This allows manual cleanup of a specific call, preserving the activity of other reactive method calls until the corresponding injector is destroyed.

```ts
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { interval, tap } from 'rxjs';
import { rxMethod } from '@ngrx/signals/rxjs-interop';

@Component({ /* ... */ })
export class NumbersComponent implements OnInit {
export class Numbers {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export class Numbers {
export class NumbersDoubler {

readonly logNumber = rxMethod<number>(tap(console.log));

ngOnInit(): void {
constructor() {
const num1$ = interval(500);
const num2$ = interval(1_000);

@@ -310,7 +310,7 @@ import { tap } from 'rxjs';
import { rxMethod } from '@ngrx/signals/rxjs-interop';

@Component({ /* ... */ })
export class NumbersComponent implements OnInit {
export class Numbers implements OnInit {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export class Numbers implements OnInit {
export class NumbersDoubler implements OnInit {

readonly #injector = inject(Injector);

ngOnInit(): void {
@@ -322,4 +322,4 @@ export class NumbersComponent implements OnInit {
logNumber(10);
}
}
```
```
26 changes: 13 additions & 13 deletions projects/ngrx.io/content/guide/signals/signal-method.md
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ import { Component } from '@angular/core';
import { signalMethod } from '@ngrx/signals';

@Component({ /* ... */ })
export class NumbersComponent {
export class Numbers {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export class Numbers {
export class NumbersDoubler {

// 👇 This method will have an input argument
// of type `number | Signal<number>`.
readonly logDoubledNumber = signalMethod<number>((num) => {
@@ -21,7 +21,7 @@ export class NumbersComponent {

```ts
@Component({ /* ... */ })
export class NumbersComponent {
export class Numbers {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export class Numbers {
export class NumbersDoubler {

readonly logDoubledNumber = signalMethod<number>((num) => {
const double = num * 2;
console.log(double);
@@ -44,27 +44,27 @@ export class NumbersComponent {
## Automatic Cleanup

`signalMethod` uses an `effect` internally to track the Signal changes.
By default, the `effect` runs in the injection context of the caller. In the example above, that is `NumbersComponent`. That means, that the `effect` is automatically cleaned up when the component is destroyed.
By default, the `effect` runs in the injection context of the caller. In the example above, that is the `Numbers` component. That means, that the `effect` is automatically cleaned up when the component is destroyed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
By default, the `effect` runs in the injection context of the caller. In the example above, that is the `Numbers` component. That means, that the `effect` is automatically cleaned up when the component is destroyed.
By default, the `effect` runs in the injection context of the caller. In the example above, that is the `NumbersDoubler` component. That means, that the `effect` is automatically cleaned up when the component is destroyed.


If the call happens outside an injection context, then the injector of the `signalMethod` is used. This would be the case, if `logDoubledNumber` runs in `ngOnInit`:

```ts
@Component({ /* ... */ })
export class NumbersComponent implements OnInit {
export class Numbers implements OnInit {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export class Numbers implements OnInit {
export class NumbersDoubler implements OnInit {

readonly logDoubledNumber = signalMethod<number>((num) => {
const double = num * 2;
console.log(double);
});

ngOnInit(): void {
const value = signal(2);
// 👇 Uses the injection context of the `NumbersComponent`.
// 👇 Uses the injection context of the `Numbers` component.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 👇 Uses the injection context of the `Numbers` component.
// 👇 Uses the injection context of the `NumbersDoubler` component.

this.logDoubledNumber(value);
}
}
```

Even though `logDoubledNumber` is called outside an injection context, automatic cleanup occurs when `NumbersComponent` is destroyed, since `logDoubledNumber` was created within the component's injection context.
Even though `logDoubledNumber` is called outside an injection context, automatic cleanup occurs when the `Numbers` component is destroyed, since `logDoubledNumber` was created within the component's injection context.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Even though `logDoubledNumber` is called outside an injection context, automatic cleanup occurs when the `Numbers` component is destroyed, since `logDoubledNumber` was created within the component's injection context.
Even though `logDoubledNumber` is called outside an injection context, automatic cleanup occurs when the `NumbersDoubler` component is destroyed, since `logDoubledNumber` was created within the component's injection context.


However, when creating a `signalMethod` in an ancestor injection context, the cleanup behavior is different:

@@ -78,7 +78,7 @@ export class NumbersService {
}

@Component({ /* ... */ })
export class NumbersComponent implements OnInit {
export class Numbers implements OnInit {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export class Numbers implements OnInit {
export class NumbersDoubler implements OnInit {

readonly numbersService = inject(NumbersService);

ngOnInit(): void {
@@ -89,7 +89,7 @@ export class NumbersComponent implements OnInit {
}
```

Here, the `effect` outlives the component, which would produce a memory leak.
Here, the `effect` used internally by `signalMethod` outlives the component, which would produce a memory leak.

<div class="alert is-important">

@@ -103,13 +103,13 @@ When a `signalMethod` is created in an ancestor injection context, it's necessar

```ts
@Component({ /* ... */ })
export class NumbersComponent implements OnInit {
export class Numbers implements OnInit {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export class Numbers implements OnInit {
export class NumbersDoubler implements OnInit {

readonly numbersService = inject(NumbersService);
readonly injector = inject(Injector);

ngOnInit(): void {
const value = signal(1);
// 👇 Providing the `NumbersComponent` injector
// 👇 Providing the `Numbers` component injector
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 👇 Providing the `Numbers` component injector
// 👇 Providing the `NumbersDoubler` component injector

// to ensure cleanup on component destroy.
this.numbersService.logDoubledNumber(value, {
injector: this.injector,
@@ -127,10 +127,10 @@ The `signalMethod` must be initialized within an injection context. To initializ

```ts
@Component({ /* ... */ })
export class NumbersComponent implements OnInit {
export class Numbers implements OnInit {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export class Numbers implements OnInit {
export class NumbersDoubler implements OnInit {

readonly injector = inject(Injector);

ngOnInit() {
ngOnInit(): void {
const logDoubledNumber = signalMethod<number>(
(num) => console.log(num * 2),
{ injector: this.injector },
@@ -145,7 +145,7 @@ At first sight, `signalMethod`, might be the same as `effect`:

```ts
@Component({ /* ... */ })
export class NumbersComponent {
export class Numbers {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export class Numbers {
export class NumbersDoubler {

readonly num = signal(2);
readonly logDoubledNumberEffect = effect(() => {
console.log(this.num() * 2);
Loading
Oops, something went wrong.