├── src
├── styles.scss
├── assets
│ └── .gitkeep
├── favicon.ico
├── app
│ ├── 04-entities
│ │ ├── todos.helpers.ts
│ │ ├── todo.model.ts
│ │ ├── ui
│ │ │ ├── add-todo.component.ts
│ │ │ └── todo-list.component.ts
│ │ ├── todos.store.ts
│ │ └── todos.component.ts
│ ├── 03-rxjs-integration
│ │ ├── user.model.ts
│ │ ├── ui
│ │ │ ├── user-list.component.ts
│ │ │ └── search-box.component.ts
│ │ ├── users.component.ts
│ │ ├── users.service.ts
│ │ └── users.store.ts
│ ├── app.config.ts
│ ├── 02-signal-store-feature
│ │ ├── counter.store.ts
│ │ ├── counter.feature.ts
│ │ └── counter.component.ts
│ ├── app.component.ts
│ ├── app.routes.ts
│ └── 01-signal-store
│ │ ├── counter.component.ts
│ │ └── counter.store.ts
├── main.ts
└── index.html
├── .prettierrc
├── .vscode
├── extensions.json
├── launch.json
└── tasks.json
├── tsconfig.app.json
├── .editorconfig
├── .gitignore
├── README.md
├── tsconfig.json
├── package.json
└── angular.json
/src/styles.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true
3 | }
4 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markostanimirovic/ngrx-signals-example/HEAD/src/favicon.ico
--------------------------------------------------------------------------------
/src/app/04-entities/todos.helpers.ts:
--------------------------------------------------------------------------------
1 | let id = 1;
2 |
3 | export function getNextId(): number {
4 | return id++;
5 | }
6 |
--------------------------------------------------------------------------------
/src/app/04-entities/todo.model.ts:
--------------------------------------------------------------------------------
1 | export type Todo = {
2 | id: number;
3 | text: string;
4 | completed: boolean;
5 | };
6 |
--------------------------------------------------------------------------------
/src/app/03-rxjs-integration/user.model.ts:
--------------------------------------------------------------------------------
1 | export type User = {
2 | id: number;
3 | firstName: string;
4 | lastName: string;
5 | };
6 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { ApplicationConfig } from '@angular/core';
2 | import { provideRouter } from '@angular/router';
3 | import { routes } from './app.routes';
4 |
5 | export const appConfig: ApplicationConfig = {
6 | providers: [provideRouter(routes)],
7 | };
8 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { appConfig } from './app/app.config';
3 | import { AppComponent } from './app/app.component';
4 |
5 | bootstrapApplication(AppComponent, appConfig).catch((err) =>
6 | console.error(err),
7 | );
8 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app"
6 | },
7 | "files": ["src/main.ts"],
8 | "include": ["src/**/*.d.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NgrxSignalsExample
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/app/02-signal-store-feature/counter.store.ts:
--------------------------------------------------------------------------------
1 | import { effect } from '@angular/core';
2 | import { signalStore, withHooks } from '@ngrx/signals';
3 | import { withCounter } from './counter.feature';
4 |
5 | export const CounterStore = signalStore(
6 | { providedIn: 'root' },
7 | withCounter(),
8 | withHooks({
9 | onInit({ doubleCount }) {
10 | effect(() => {
11 | console.log('doubleCount changed', doubleCount());
12 | });
13 | },
14 | }),
15 | );
16 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component } from '@angular/core';
2 | import { RouterLink, RouterOutlet } from '@angular/router';
3 |
4 | @Component({
5 | selector: 'app-root',
6 | standalone: true,
7 | imports: [RouterOutlet, RouterLink],
8 | template: `
9 | signalStore |
10 | signalStoreFeature |
11 | RxJS Integration |
12 | Entities
13 |
14 |
15 | `,
16 | changeDetection: ChangeDetectionStrategy.OnPush,
17 | })
18 | export class AppComponent {}
19 |
--------------------------------------------------------------------------------
/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 |
3 | export const routes: Routes = [
4 | { path: '', redirectTo: '01', pathMatch: 'full' },
5 | {
6 | path: '01',
7 | loadComponent: () => import('./01-signal-store/counter.component'),
8 | },
9 | {
10 | path: '02',
11 | loadComponent: () => import('./02-signal-store-feature/counter.component'),
12 | },
13 | {
14 | path: '03',
15 | loadComponent: () => import('./03-rxjs-integration/users.component'),
16 | },
17 | {
18 | path: '04',
19 | loadComponent: () => import('./04-entities/todos.component'),
20 | },
21 | ];
22 |
--------------------------------------------------------------------------------
/src/app/04-entities/ui/add-todo.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | Component,
4 | EventEmitter,
5 | Output,
6 | } from '@angular/core';
7 |
8 | @Component({
9 | selector: 'app-add-todo',
10 | standalone: true,
11 | template: `
12 |
18 |
19 | `,
20 | changeDetection: ChangeDetectionStrategy.OnPush,
21 | })
22 | export class AddTodoComponent {
23 | @Output() add = new EventEmitter();
24 | }
25 |
--------------------------------------------------------------------------------
/src/app/03-rxjs-integration/ui/user-list.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
2 | import { User } from '../user.model';
3 |
4 | @Component({
5 | selector: 'app-user-list',
6 | standalone: true,
7 | template: `
8 | @if (isLoading) {
9 | Loading...
10 | }
11 |
12 |
13 | @for (user of users; track user.id) {
14 | - {{ user.firstName }} {{ user.lastName }}
15 | }
16 |
17 | `,
18 | changeDetection: ChangeDetectionStrategy.OnPush,
19 | })
20 | export class UserListComponent {
21 | @Input() users: User[] = [];
22 | @Input() isLoading = false;
23 | }
24 |
--------------------------------------------------------------------------------
/src/app/02-signal-store-feature/counter.feature.ts:
--------------------------------------------------------------------------------
1 | import { computed } from '@angular/core';
2 | import {
3 | patchState,
4 | signalStoreFeature,
5 | withComputed,
6 | withMethods,
7 | withState,
8 | } from '@ngrx/signals';
9 |
10 | export function withCounter() {
11 | return signalStoreFeature(
12 | withState({ count: 0 }),
13 | withComputed(({ count }) => ({
14 | doubleCount: computed(() => count() * 2),
15 | })),
16 | withMethods(({ count, ...store }) => ({
17 | increment() {
18 | patchState(store, { count: count() + 1 });
19 | },
20 | decrement() {
21 | patchState(store, { count: count() - 1 });
22 | },
23 | })),
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/src/app/03-rxjs-integration/ui/search-box.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | Component,
4 | EventEmitter,
5 | Input,
6 | Output,
7 | } from '@angular/core';
8 | import { FormsModule } from '@angular/forms';
9 |
10 | @Component({
11 | selector: 'app-search-box',
12 | standalone: true,
13 | imports: [FormsModule],
14 | template: `
15 |
20 | `,
21 | changeDetection: ChangeDetectionStrategy.OnPush,
22 | })
23 | export class SearchBoxComponent {
24 | @Input() query = '';
25 | @Output() queryChange = new EventEmitter();
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/02-signal-store-feature/counter.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
2 | import { CounterStore } from './counter.store';
3 |
4 | @Component({
5 | selector: 'app-counter2',
6 | standalone: true,
7 | template: `
8 | Counter (signalStoreFeature)
9 |
10 | Count: {{ store.count() }}
11 | Double Count: {{ store.doubleCount() }}
12 |
13 |
14 |
15 | `,
16 | changeDetection: ChangeDetectionStrategy.OnPush,
17 | })
18 | export default class CounterComponent {
19 | readonly store = inject(CounterStore);
20 | }
21 |
--------------------------------------------------------------------------------
/src/app/01-signal-store/counter.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
2 | import { CounterStore } from './counter.store';
3 |
4 | @Component({
5 | selector: 'app-counter',
6 | standalone: true,
7 | template: `
8 | Counter (signalStore)
9 |
10 | Count: {{ store.count() }}
11 | Double Count: {{ store.doubleCount() }}
12 |
13 |
14 |
15 | `,
16 | providers: [CounterStore],
17 | changeDetection: ChangeDetectionStrategy.OnPush,
18 | })
19 | export default class CounterComponent {
20 | readonly store = inject(CounterStore);
21 | }
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NgrxSignalsExample
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.0.0-rc.0.
4 |
5 | ## Development server
6 |
7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
8 |
9 | ## Code scaffolding
10 |
11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12 |
13 | ## Build
14 |
15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
16 |
17 | ## Further help
18 |
19 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
20 |
--------------------------------------------------------------------------------
/src/app/03-rxjs-integration/users.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
2 | import { SearchBoxComponent } from './ui/search-box.component';
3 | import { UserListComponent } from './ui/user-list.component';
4 | import { UsersStore } from './users.store';
5 |
6 | @Component({
7 | selector: 'app-users',
8 | standalone: true,
9 | imports: [SearchBoxComponent, UserListComponent],
10 | template: `
11 | Users (RxJS Integration)
12 |
13 |
17 |
18 |
19 | `,
20 | changeDetection: ChangeDetectionStrategy.OnPush,
21 | })
22 | export default class UsersComponent {
23 | readonly store = inject(UsersStore);
24 | }
25 |
--------------------------------------------------------------------------------
/src/app/04-entities/todos.store.ts:
--------------------------------------------------------------------------------
1 | import { patchState, signalStore, withMethods } from '@ngrx/signals';
2 | import {
3 | addEntity,
4 | removeEntity,
5 | updateEntity,
6 | withEntities,
7 | } from '@ngrx/signals/entities';
8 | import { Todo } from './todo.model';
9 | import { getNextId } from './todos.helpers';
10 |
11 | export const TodosStore = signalStore(
12 | withEntities(),
13 | withMethods((store) => ({
14 | addTodo(text: string) {
15 | const todo = { id: getNextId(), text, completed: false };
16 | patchState(store, addEntity(todo));
17 | },
18 | removeTodo(id: number) {
19 | patchState(store, removeEntity(id));
20 | },
21 | toggleTodo(id: number) {
22 | const changes = (todo: Todo) => ({ completed: !todo.completed });
23 | patchState(store, updateEntity({ id, changes }));
24 | },
25 | })),
26 | );
27 |
--------------------------------------------------------------------------------
/src/app/04-entities/todos.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
2 | import { TodosStore } from './todos.store';
3 | import { AddTodoComponent } from './ui/add-todo.component';
4 | import { TodoListComponent } from './ui/todo-list.component';
5 |
6 | @Component({
7 | selector: 'app-todos',
8 | standalone: true,
9 | imports: [AddTodoComponent, TodoListComponent],
10 | template: `
11 | Todos (Entities)
12 |
13 |
14 |
15 |
20 | `,
21 | providers: [TodosStore],
22 | changeDetection: ChangeDetectionStrategy.OnPush,
23 | })
24 | export default class TodosComponent {
25 | readonly store = inject(TodosStore);
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/03-rxjs-integration/users.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { delay, Observable, of } from 'rxjs';
3 | import { User } from './user.model';
4 |
5 | const usersMock: User[] = [
6 | { id: 1, firstName: 'John', lastName: 'Smith' },
7 | { id: 2, firstName: 'Jane', lastName: 'Doe' },
8 | { id: 3, firstName: 'Bob', lastName: 'Brown' },
9 | { id: 4, firstName: 'Mary', lastName: 'Jones' },
10 | { id: 5, firstName: 'Kate', lastName: 'Parker' },
11 | ];
12 |
13 | @Injectable({ providedIn: 'root' })
14 | export class UsersService {
15 | getAll(): Promise {
16 | return new Promise((resolve) => {
17 | setTimeout(() => resolve(usersMock), 1000);
18 | });
19 | }
20 |
21 | getByQuery(query: string): Observable {
22 | const filteredUsers = usersMock.filter(({ firstName, lastName }) =>
23 | `${firstName} ${lastName}`.toLowerCase().includes(query.toLowerCase()),
24 | );
25 |
26 | return of(filteredUsers).pipe(delay(500));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "outDir": "./dist/out-tsc",
6 | "forceConsistentCasingInFileNames": true,
7 | "strict": true,
8 | "noImplicitOverride": true,
9 | "noPropertyAccessFromIndexSignature": true,
10 | "noImplicitReturns": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "allowSyntheticDefaultImports": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "ES2022",
21 | "useDefineForClassFields": false,
22 | "lib": ["ES2022", "dom"]
23 | },
24 | "angularCompilerOptions": {
25 | "enableI18nLegacyMessageIdFormat": false,
26 | "strictInjectionParameters": true,
27 | "strictInputAccessModifiers": true,
28 | "strictTemplates": true
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/app/01-signal-store/counter.store.ts:
--------------------------------------------------------------------------------
1 | import { computed } from '@angular/core';
2 | import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
3 | import { interval } from 'rxjs';
4 | import {
5 | patchState,
6 | signalStore,
7 | withComputed,
8 | withHooks,
9 | withMethods,
10 | withState,
11 | } from '@ngrx/signals';
12 |
13 | export const CounterStore = signalStore(
14 | withState({ count: 0 }),
15 | withComputed(({ count }) => ({
16 | doubleCount: computed(() => count() * 2),
17 | })),
18 | withMethods(({ count, ...store }) => ({
19 | increment() {
20 | patchState(store, { count: count() + 1 });
21 | },
22 | decrement() {
23 | patchState(store, { count: count() - 1 });
24 | },
25 | })),
26 | withHooks({
27 | onInit({ increment }) {
28 | interval(2_000)
29 | .pipe(takeUntilDestroyed())
30 | .subscribe(() => increment());
31 | },
32 | onDestroy({ count }) {
33 | console.log('count on destroy', count());
34 | },
35 | }),
36 | );
37 |
--------------------------------------------------------------------------------
/src/app/04-entities/ui/todo-list.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | Component,
4 | EventEmitter,
5 | Input,
6 | Output,
7 | } from '@angular/core';
8 | import { Todo } from '../todo.model';
9 |
10 | @Component({
11 | selector: 'app-todo-list',
12 | standalone: true,
13 | template: `
14 |
25 | `,
26 | styles: `
27 | .completed {
28 | text-decoration: line-through;
29 | }
30 | `,
31 | changeDetection: ChangeDetectionStrategy.OnPush,
32 | })
33 | export class TodoListComponent {
34 | @Input() todos: Todo[] = [];
35 | @Output() toggle = new EventEmitter();
36 | @Output() remove = new EventEmitter();
37 | }
38 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngrx-signals-example",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "format": "prettier --write ."
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/animations": "^17.0.4",
14 | "@angular/common": "^17.0.4",
15 | "@angular/compiler": "^17.0.4",
16 | "@angular/core": "^17.0.4",
17 | "@angular/forms": "^17.0.4",
18 | "@angular/platform-browser": "^17.0.4",
19 | "@angular/platform-browser-dynamic": "^17.0.4",
20 | "@angular/router": "^17.0.4",
21 | "@ngrx/operators": "^17.0.1",
22 | "@ngrx/signals": "^17.0.1",
23 | "rxjs": "~7.8.1",
24 | "tslib": "^2.3.0",
25 | "zone.js": "~0.14.2"
26 | },
27 | "devDependencies": {
28 | "@angular-devkit/build-angular": "^17.0.3",
29 | "@angular/cli": "^17.0.3",
30 | "@angular/compiler-cli": "^17.0.4",
31 | "prettier": "^3.1.0",
32 | "typescript": "~5.2.2"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/src/app/03-rxjs-integration/users.store.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { debounceTime, distinctUntilChanged, pipe, switchMap, tap } from 'rxjs';
3 | import {
4 | patchState,
5 | signalStore,
6 | withHooks,
7 | withMethods,
8 | withState,
9 | } from '@ngrx/signals';
10 | import { rxMethod } from '@ngrx/signals/rxjs-interop';
11 | import { tapResponse } from '@ngrx/operators';
12 | import { User } from './user.model';
13 | import { UsersService } from './users.service';
14 |
15 | type State = { users: User[]; isLoading: boolean; query: string };
16 |
17 | const initialState: State = {
18 | users: [],
19 | isLoading: false,
20 | query: '',
21 | };
22 |
23 | export const UsersStore = signalStore(
24 | { providedIn: 'root' },
25 | withState(initialState),
26 | withMethods((store, usersService = inject(UsersService)) => ({
27 | updateQuery(query: string) {
28 | patchState(store, { query });
29 | },
30 | async loadAll() {
31 | patchState(store, { isLoading: true });
32 | const users = await usersService.getAll();
33 | patchState(store, { users, isLoading: false });
34 | },
35 | loadByQuery: rxMethod(
36 | pipe(
37 | debounceTime(300),
38 | distinctUntilChanged(),
39 | tap(() => patchState(store, { isLoading: true })),
40 | switchMap((query) =>
41 | usersService.getByQuery(query).pipe(
42 | tapResponse({
43 | next: (users) => patchState(store, { users }),
44 | error: console.error,
45 | finalize: () => patchState(store, { isLoading: false }),
46 | }),
47 | ),
48 | ),
49 | ),
50 | ),
51 | })),
52 | withHooks({
53 | onInit({ loadByQuery, query }) {
54 | loadByQuery(query);
55 | },
56 | }),
57 | );
58 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "cli": {
5 | "packageManager": "yarn"
6 | },
7 | "newProjectRoot": "projects",
8 | "projects": {
9 | "ngrx-signals-example": {
10 | "projectType": "application",
11 | "schematics": {
12 | "@schematics/angular:component": {
13 | "style": "scss"
14 | }
15 | },
16 | "root": "",
17 | "sourceRoot": "src",
18 | "prefix": "app",
19 | "architect": {
20 | "build": {
21 | "builder": "@angular-devkit/build-angular:application",
22 | "options": {
23 | "outputPath": "dist/ngrx-signals-example",
24 | "index": "src/index.html",
25 | "browser": "src/main.ts",
26 | "polyfills": ["zone.js"],
27 | "tsConfig": "tsconfig.app.json",
28 | "inlineStyleLanguage": "scss",
29 | "assets": ["src/favicon.ico", "src/assets"],
30 | "styles": ["src/styles.scss"],
31 | "scripts": []
32 | },
33 | "configurations": {
34 | "production": {
35 | "budgets": [
36 | {
37 | "type": "initial",
38 | "maximumWarning": "500kb",
39 | "maximumError": "1mb"
40 | },
41 | {
42 | "type": "anyComponentStyle",
43 | "maximumWarning": "2kb",
44 | "maximumError": "4kb"
45 | }
46 | ],
47 | "outputHashing": "all"
48 | },
49 | "development": {
50 | "optimization": false,
51 | "extractLicenses": false,
52 | "sourceMap": true
53 | }
54 | },
55 | "defaultConfiguration": "production"
56 | },
57 | "serve": {
58 | "builder": "@angular-devkit/build-angular:dev-server",
59 | "configurations": {
60 | "production": {
61 | "buildTarget": "ngrx-signals-example:build:production"
62 | },
63 | "development": {
64 | "buildTarget": "ngrx-signals-example:build:development"
65 | }
66 | },
67 | "defaultConfiguration": "development"
68 | },
69 | "extract-i18n": {
70 | "builder": "@angular-devkit/build-angular:extract-i18n",
71 | "options": {
72 | "buildTarget": "ngrx-signals-example:build"
73 | }
74 | }
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------