{"id":14108667,"url":"https://github.com/ngxp/store-service","last_synced_at":"2025-08-01T06:32:43.368Z","repository":{"id":33117202,"uuid":"141180178","full_name":"ngxp/store-service","owner":"ngxp","description":"Adds an abstraction layer / facade between Angular components and the @ngrx store","archived":false,"fork":false,"pushed_at":"2025-04-30T17:42:10.000Z","size":2046,"stargazers_count":25,"open_issues_count":4,"forks_count":6,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-24T22:18:08.059Z","etag":null,"topics":["angular","hacktoberfest","ngrx","rxjs"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ngxp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-07-16T18:50:06.000Z","updated_at":"2025-03-19T13:04:51.000Z","dependencies_parsed_at":"2024-06-21T16:43:39.374Z","dependency_job_id":"12c20ff6-7897-49c5-a857-e9cd4544e439","html_url":"https://github.com/ngxp/store-service","commit_stats":{"total_commits":127,"total_committers":6,"mean_commits":"21.166666666666668","dds":"0.45669291338582674","last_synced_commit":"67b9fe632959f28f555fb1d72b5bc56024ca51d6"},"previous_names":["ngx-patterns/store-service"],"tags_count":29,"template":false,"template_full_name":null,"purl":"pkg:github/ngxp/store-service","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngxp%2Fstore-service","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngxp%2Fstore-service/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngxp%2Fstore-service/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngxp%2Fstore-service/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ngxp","download_url":"https://codeload.github.com/ngxp/store-service/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngxp%2Fstore-service/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266936395,"owners_count":24009411,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-24T02:00:09.469Z","response_time":99,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","hacktoberfest","ngrx","rxjs"],"created_at":"2024-08-14T10:01:33.960Z","updated_at":"2025-08-01T06:32:42.962Z","avatar_url":"https://github.com/ngxp.png","language":"TypeScript","readme":"# @ngxp/store-service\n\nAdds an abstraction layer between Angular components and the [@ngrx](https://github.com/ngrx/platform) store and effects. This decouples the components from the store, selectors, actions and effects and makes it easier to test components.\n\n# Table of contents\n\n* [Installation](#installation)\n* [Comparison](#comparison)\n    * [Before](#before)\n    * [After](#after)\n* [Documentation](#documentation)\n    * [StoreService](#storeservice)\n    * [Selectors](#selectors)\n    * [Actions](#actions)\n    * [Observers](#observers)\n        * [Observe multiple types](#multiple-types)\n        * [Use objects with type property](#objects-with-type-property)\n        * [String action types](#string-action-types)\n        * [Custom mapper](#custom-mapper)\n    * [Deprecated Annotations](#deprecated-annotations)\n* [Testing](#testing)\n    * [Testing Components](#testing-components)\n        * [Testing Selectors](#testing-selectors)\n        * [Testing Actions](#testing-actions)\n        * [Testing Observers](#testing-observers)\n    * [Testing StoreService](#testing-storeservice)\n        * [Testing StoreService Selectors](#testing-storeservice-selectors)\n        * [Testing StoreService Actions](#testing-storeservice-actions)\n        * [Testing StoreService Observers](#testing-storeservice-observers)\n* [Examples](#examples)\n    * [Example Store Service](#example-store-service)\n    * [Example Tests](#example-tests)\n\n# Installation\n\nGet the latest version from NPM \n\n```sh\nnpm install @ngxp/store-service\n```\n\n# Comparison\n![Dependency diagram comparison](docs/diagram.png)\n\n## Before\n\n\u003e Component\n\n```ts\nimport { Component } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { Book } from 'src/app/shared/books/book.model';\n// Tight coupling to ngrx, state model, selectors and actions\nimport { Store } from '@ngrx/store'; \nimport { Actions, ofType } from '@ngrx/effects'; \nimport { AppState } from 'src/app/store/appstate.model';\nimport { getAllBooks, getBook } from 'src/app/store/books/books.selectors'; \nimport { addBookAction, booksLoadedAction } from 'src/app/store/books/books.actions'; \n \n@Component({\n    selector: 'nss-book-list',\n    templateUrl: './book-list.component.html',\n    styleUrls: ['./book-list.component.scss']\n})\nexport class BookListComponent {\n\n    books$: Observable\u003cBook[]\u003e;\n    book$: Observable\u003cBook\u003e;\n    booksLoaded: boolean = false;\n\n    constructor(\n        private store: Store\u003cAppState\u003e\n        private actions: Actions\n    ) {\n        this.books$ = this.store.select(getAllBooks);\n        this.book$ = this.store.select(getBook, { id: 0 });\n        this.actions\n            .pipe(\n                ofType(booksLoadedAction),\n                map(() =\u003e this.loaded = true)\n            )\n            .suscribe();\n    }\n\n    addBook(book: Book) {\n        this.store.dispatch(addBookAction({ book }));\n    }\n}\n```\n\n## After\n\n\u003e Component\n\n```ts\nimport { Component } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { Book } from 'src/app/shared/books/book.model';\nimport { BookStoreService } from 'src/app/shared/books/book-store.service'; \n// Reduced to just one dependency. Loose coupling\n\n@Component({\n    selector: 'nss-book-list',\n    templateUrl: './book-list.component.html',\n    styleUrls: ['./book-list.component.scss']\n})\nexport class BookListComponent {\n\n    books$: Observable\u003cBook[]\u003e;\n    book$: Observable\u003cBook\u003e;\n    booksLoaded: boolean = false;\n\n    constructor(\n        private bookStore: BookStoreService // \u003c- StoreService\n    ) {\n        this.books$ = this.bookStore.getAllBooks(); // \u003c- Selector\n        this.book$ = this.bookStore.getBook({ id: 0 }); // \u003c- Selector\n        this.bookStore.booksLoaded$ // \u003c-- Observer / Action stream of type\n            .pipe(\n                map(() =\u003e this.loaded = true)\n            )\n            .subscribe();\n    }\n\n    addBook(book: Book) {\n        this.bookStore.addBook({ book }); // \u003c- Action\n    }\n}\n```\n\n\u003e BookStoreService\n\n```ts\nimport { Injectable } from '@angular/core';\nimport { select, StoreService, dispatch, observe } from '@ngxp/store-service';\nimport { Book } from 'src/app/shared/books/book.model';\nimport { getBooks } from 'src/app/store/books/books.selectors';\nimport { State } from 'src/app/store/store.model';\nimport { addBookAction, booksLoadedAction } from 'src/app/store/books/books.actions';\n\n@Injectable()\nexport class BookStoreService extends StoreService\u003cState\u003e {\n\n    getAllBooks = select(getBooks); // \u003c- Selector\n\n    getBook = select(getBook); // \u003c- Selector\n\n    addBook = dispatch(addBookAction); // \u003c- Action\n\n    booksLoaded$ = observe([booksLoadedAction]); // \u003c- Observer / Action stream\n}\n```\n\n\n# Documentation\n\n## StoreService\n\nThe `BookStoreService` Injectable class has to extend the `StoreService\u003cState\u003e` class where `State` is your ngrx state model.\n\n```ts\nimport { StoreService } from '@ngxp/store-service';\nimport { AppState } from 'app/store/state.model';\n\n@Injectable()\nexport class BookStoreService extends StoreService\u003cAppState\u003e {\n    ...\n}\n```\n\n## Selectors\n\nTo use selectors you wrap the ngrx selector inside the `select(...)` function:\n\n```ts\n// Define the selector function\nexport const selectAllBooks = createSelector(\n    (state: State) =\u003e state.books;\n};\n\n//Or with props\nexport const selectBook = createSelector(\n    (state: State, props: { id: number }) =\u003e state.books[id];\n};\n...\n\n// Use the selector function inside the select(...) function\nallBooks = select(selectAllBooks); // () =\u003e Observable\u003cBook[]\u003e\n\nbook = select(selectBook); // (props: { id: number }) =\u003e Observable\u003cBook\u003e\n```\nThe `select(...)` function automatically infers the correct typing according to the props and return type of the selector.\n\n\n\n\n## Actions\n\nTo dispatch actions add a property with the `dispatch(...)` function.\n\n```ts\n// Defined the Action as a class\nexport const loadBooksAction = createAction('[Books] Load books');\n\nexport const addBookAction = createAction('[Books] Add book' props\u003c{ book: Book}\u003e())\n\n...\nloadBooks = dispatch(loadBooksAction); // () =\u003e void\n\naddBook = dispatch(addBookAction); // (props: { book: Book }) =\u003e void\n```\n\nThe `dispatch(...)` function automatically infers the parameters according to the props of the action.\n\n\n## Observers\n\nObservers are a way to listen for specific action types on the `Actions` stream from [@ngrx/effects](https://github.com/ngrx/platform/blob/master/docs/effects/README.md).\n\n```ts\nbooksLoaded$ = observe([booksLoadedAction]);\n```\n\n### Multiple types\nYou can provide multiple types, just like in the `ofType(...)` pipe.\n\n```ts\nbooksLoaded$ = observe([booksLoadedAction, booksLoadFailedAction]);\n```\n\n### Custom mapper\nThe `observe(...)` function has an additional parameter to provide a custom `customMapper` mapping function.\nInitially this will be:\n```ts\naction =\u003e action\n```\n\nTo use a custom mapper, provide it as second argument in the `observe(...)` function.\n\n```ts\nexport const toData = action =\u003e action.data;\n\n...\ndataLoaded$ = observe([dataLoadedAction], toData);\n```\n\n## Deprecated Annotations\n\nBefore Version 12 the Store Service used annotations instead of functions. This old way still works but is deprecated and _will be removed in the future_.\n\n```ts\nimport { Select, StoreService, Dispatch, Selector, Dispatcher, Observe } from '@ngxp/store-service';\n\n@Select(getBooks)\ngetAllBooks: Selector\u003ctypeof getBooks\u003e;\n\n@Dispatch(addBookAction)\naddBook: Dispatcher\u003ctypeof addBookAction\u003e;\n\n@Observe([dataLoadedAction], toData)\ndataLoaded$: Observable\u003cBook[]\u003e;\n```\n\n# Testing\nTesting your components and the StoreService is easy. The `@ngxp/store-service/testing` package provides useful test-helpers to reduce testing friction.\n\n## Testing Components\n\n### Testing Selectors\n\nTo test selectors you provide the `StoreService` using the `provideStoreServiceMock` method in the testing module of your component. Then get the `StoreServiceMock\u003cT\u003e` instance using the `getStoreServiceMock` helper function.\n\n\n```ts\nimport { provideStoreServiceMock, StoreServiceMock, getStoreServiceMock } from '@ngxp/store-service/testing';\n...\nlet bookStoreService: StoreServiceMock\u003cBookStoreService\u003e;\n...\nTestBed.configureTestingModule({\n    declarations: [\n        BookListComponent\n    ],\n    providers: [\n        provideStoreServiceMock(BookStoreService)\n    ]\n})\n...\nbookStoreService = getStoreServiceMock(BookStoreService);\n```\n\nThe `StoreServiceMock` class replaces all selector functions on the store service class with a `BehaviorSubject`. So now you can do the following to emit new values to the observables:\n\n```ts\nbookStoreService.getAllBooks().next(newBooks);\n```\n\nThe `BehaviorSubject` is initialized with the value being `undefined`. If you want a custom initial value, the `provideStoreServiceMock` method offers an optional parameter. This is an object of key value pairs where the key is the name of the selector function, e.g. `getAllBooks`.\n\n```ts\nimport { provideStoreServiceMock, StoreServiceMock, getStoreServiceMock } from '@ngxp/store-service/testing';\n...\nlet bookStoreService: StoreServiceMock\u003cBookStoreService\u003e;\n...\nTestBed.configureTestingModule({\n    declarations: [\n        BookListComponent\n    ],\n    providers: [\n        provideStoreServiceMock(BookStoreService, {\n            getAllBooks: []\n        })\n    ]\n})\n...\nbookStoreService = getStoreServiceMock(BookStoreService);\n```\n\nThe `BehaviorSubject` for `getAllBooks` is now initialized with an empty array instead of `undefined`.\n\n### Testing Actions\n\nTo test if a component calls the dispatch methods you provide the `StoreService` using the `provideStoreServiceMock` method in the testing module of your component. Then get the `StoreServiceMock\u003cT\u003e` instance using the `getStoreServiceMock` helper function.\n\nYou can then spy on the method as usual.\n\n```ts\nimport { provideStoreServiceMock, StoreServiceMock } from '@ngxp/store-service/testing';\n...\nlet bookStoreService: StoreServiceMock\u003cBookStoreService\u003e;\n...\nTestBed.configureTestingModule({\n    declarations: [\n        NewBookComponent\n    ]\n    imports: [\n        provideStoreServiceMock(BookStoreService)\n    ]\n})\n...\nit('adds a new book', () =\u003e {\n    const book: Book = getBook();\n    const addBookSpy = jest.spyOn(bookStoreService, 'addBook');\n\n    component.book = book;\n    component.addBook();\n\n    expect(addBookSpy).toHaveBeenCalledWith({ book });\n});\n```\n\n### Testing Observers\n\nTo test observers inside components you provide the `StoreService` using the `provideStoreServiceMock` method in the testing module of your component. Then get the `StoreServiceMock\u003cT\u003e` instance using the `getStoreServiceMock` helper function.\n\n\n```ts\nimport { provideStoreServiceMock, StoreServiceMock, getStoreServiceMock } from '@ngxp/store-service/testing';\n...\nlet bookStoreService: StoreServiceMock\u003cBookStoreService\u003e;\n...\nTestBed.configureTestingModule({\n    declarations: [\n        BookListComponent\n    ],\n    providers: [\n        provideStoreServiceMock(BookStoreService)\n    ]\n})\n...\nbookStoreService = getStoreServiceMock(BookStoreService);\n```\n\nThe `StoreServiceMock` class replaces all observer properties on the store service class with a `BehaviorSubject`. So now you can do the following to emit new values to the subscribers:\n\n```ts\nbookStoreService.booksLoaded$().next(true);\n```\n\nThe `BehaviorSubject` is initialized with the value being `undefined`. If you want a custom initial value, the `provideStoreServiceMock` method offers an optional parameter. This is an object of key value pairs where the key is the name of the observer property, e.g. `booksLoaded$`.\n\n```ts\nimport { provideStoreServiceMock, StoreServiceMock, getStoreServiceMock } from '@ngxp/store-service/testing';\n...\nlet bookStoreService: StoreServiceMock\u003cBookStoreService\u003e;\n...\nTestBed.configureTestingModule({\n    declarations: [\n        BookListComponent\n    ],\n    providers: [\n        provideStoreServiceMock(BookStoreService, {\n            booksLoaded$: false\n        })\n    ]\n})\n...\nbookStoreService = getStoreServiceMock(BookStoreService);\n```\n\nThe `BehaviorSubject` for `booksLoaded$` is now initialized with `false` instead of `undefined`.\n\n\n## Testing StoreService\n\nTo test the `StoreService` itself you use the provided test helpers from `@ngrx/store/testing` and `@ngrx/effects/testing`.\n\n### Testing StoreService Selectors\n\nYou can provide mocks for selectors with the `provideMockStore` from `@ngrx/store/testing` a. See (@ngrx/store Testing)[https://ngrx.io/guide/store/testing] for their documentation.\n\nMock the selectors using the `provideMockStore` function and check if the `StoreService` returns an Observable with the mocked value.\n\n```ts\nimport { MockStore, provideMockStore, getStoreServiceMock } from '@ngrx/store/testing';\nimport { BookStoreService } from 'src/app/shared/books/book-store.service';\nimport { selectBook, selectBooks } from '../../store/books/books.selectors';\n\ndescribe('BookStoreService', () =\u003e {\n    let bookStoreService: BookStoreService;\n    let mockStore: MockStore\u003c{ books: BookState }\u003e;\n\n    const books: Book[] = [\n        {\n            author: 'Joost',\n            title: 'Testing the StoreService',\n            year: 2019\n        }\n    ];\n\n    beforeEach(waitForAsync(() =\u003e {\n        TestBed.configureTestingModule({\n            providers: [\n                BookStoreService,\n                provideMockStore({\n                    selectors: [\n                        {\n                            selector: selectBooks,\n                            value: books\n                        },\n                        {\n                            selector: selectBook,\n                            value: books[0]\n                        }\n                    ]\n                })\n            ]\n        });\n    }));\n\n    beforeEach(() =\u003e {\n        bookStoreService = getStoreServiceMock(BookStoreService);\n        mockStore = TestBed.inject(Mockstore);\n    });\n\n    it('executes the getBooks Selector', () =\u003e {\n        const expected = cold('a', { a: books });\n\n        expect(bookStoreService.getAllBooks()).toBeObservable(expected);\n    });\n    it('executes the getBook Selector', () =\u003e {\n        const expected = cold('a', { a: books[0] });\n\n        expect(bookStoreService.getBook({ id: 0 })).toBeObservable(expected);\n    });\n});\n```\n\n### Testing StoreService Actions\n\nYou can provide mocks for selectors with the `provideMockStore` from `@ngrx/store/testing` a. See (@ngrx/store Testing)[https://ngrx.io/guide/store/testing] for their documentation.\n\nMock the selectors using the `provideMockStore` function and check if the `StoreService` returns an Observable with the mocked value.\n\nTo test if the `StoreService` dispatches the correct actions the `MockStore` from @ngrx has a property called `scannedActions$`. This is an Observable of all dispatched actions to check if an action was dispatched correctly.\n\n```ts\nimport { MockStore, provideMockStore, getStoreServiceMock } from '@ngrx/store/testing';\nimport { cold } from 'jest-marbles';\nimport { BookStoreService } from 'src/app/shared/books/book-store.service';\nimport { addBookAction, loadBooksAction } from '../../store/books/books.actions';\n\ndescribe('BookStoreService', () =\u003e {\n    let bookStoreService: BookStoreService;\n    let mockStore: MockStore\u003c{ books: BookState }\u003e;\n\n    beforeEach(waitForAsync(() =\u003e {\n        TestBed.configureTestingModule({\n            providers: [\n                BookStoreService,\n                provideMockStore()\n            ]\n        });\n    }));\n\n    beforeEach(() =\u003e {\n        bookStoreService = getStoreServiceMock(BookStoreService);\n        mockStore = TestBed.inject(MockStore);\n    });\n\n    it('dispatches a new addBookAction', () =\u003e {\n        const book: Book = getBook();\n        bookStoreService.addBook({ book });\n\n        const expected = cold('a', { a: addBookAction({ book }) });\n        expect(mockStore.scannedActions$).toBeObservable(expected);\n    });\n    it('dispatches a new loadBooksAction', () =\u003e {\n        bookStoreService.loadBooks();\n\n        const expected = cold('a', { a: loadBooksAction() });\n        expect(mockStore.scannedActions$).toBeObservable(expected);\n    });\n});\n```\n\n### Testing StoreService Observers\n\nTo test the observers / actions stream, you import the `provideMockActions` from `@ngrx/effects/testing` inside the testing module.\nThen check if the Observer filters the correct actions.\n\n```ts\nimport { getStoreServiceMock } from '@ngxp/store-service/testing';\nimport { provideMockActions } from '@ngrx/effects/testing';\nimport { BehaviorSubject } from 'rxjs';\nimport { BookStoreService } from 'src/app/shared/books/book-store.service';\nimport { booksLoadedAction } from '../../store/books/books.actions';\n\ndescribe('BookStoreService', () =\u003e {\n    let bookStoreService: BookStoreService;\n    const mockActions = new BehaviorSubject(undefined);\n\n    beforeEach(waitForAsync(() =\u003e {\n        TestBed.configureTestingModule({\n            providers: [\n                BookStoreService,\n                provideMockActions(mockActions)\n            ]\n        });\n    }));\n\n    beforeEach(() =\u003e {\n        bookStoreService = getStoreServiceMock(BookStoreService);\n    });\n\n    it('filters the BooksLoadedActions in booksLoaded$', () =\u003e {\n        const expectedValue: Book[] = [{\n            author: 'Author',\n            title: 'Title',\n            year: 2018\n        }];\n\n        const action = booksLoadedAction({ books: expectedValue });\n        mockActions.next(action);\n\n        const expected = cold('a', { a: action });\n        \n        expect(bookStoreService.booksLoaded$()).toBeObservable(expected);\n    });\n});\n\n```\n\n# Examples\n\nFor detailed examples of all this have a look at the Angular Project in [the apps/store-service-sample folder](apps/store-service-sample/src/app).\n\n## Example Store Service\n\nHave a look at the [BookStoreService](apps/store-service-sample/src/app/shared/books/book-store.service.ts)\n\n## Example Tests\n\nFor examples on Component Tests please have look at the test for the [BookListComponent](apps/store-service-sample/src/app/components/book-list/book-list.component.spec.ts) and the [NewBookComponent](apps/store-service-sample/src/app/components/new-book/new-book.component.spec.ts)\n\nTesting the `StoreService` is also very easy. For an example have a look at the [BookStoreService](apps/store-service-sample/src/app/shared/books/book-store.service.spec.ts)\n","funding_links":[],"categories":["State Management"],"sub_categories":["NgRx"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fngxp%2Fstore-service","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fngxp%2Fstore-service","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fngxp%2Fstore-service/lists"}