Introducing Socket Firewall: Free, Proactive Protection for Your Software Supply Chain.Learn More
Socket
Book a DemoInstallSign in
Socket

@ngx-patterns/store-service

Package Overview
Dependencies
Maintainers
3
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ngx-patterns/store-service

Adds an abstraction layer between Angular component and the ngrx store with powerful testing helpers

Source
npmnpm
Version
1.1.0
Version published
Weekly downloads
3
200%
Maintainers
3
Weekly downloads
 
Created
Source

@ngx-patterns/store-service

Adds an abstraction layer between Angular components and the @ngrx store. This decouples the components from the store, selectors and actions and makes it easier to test components.

How to use

Before

Component

import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { Book } from 'src/app/shared/books/book.model';
// So many dependencies
import { Store } from '@ngrx/store'; 
import { AppState } from 'src/app/store/appstate.model';
import { getAllBooks } from 'src/app/store/books/books.selectors'; 
import { AddBookAction } from 'src/app/store/books/books.actions'; 
 
@Component({
    selector: 'nss-book-list',
    templateUrl: './book-list.component.html',
    styleUrls: ['./book-list.component.scss']
})
export class BookListComponent {

    books$: Observable<Book[]>;

    constructor(
        private store: Store<AppState>
    ) {
        this.books$ = this.store.select(getAllBooks());
    }

    addBook(book: Book) {
        this.store.dispatch(new AddBookAction(book));
    }
}

After

Component

import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { Book } from 'src/app/shared/books/book.model';
import { BookStoreService } from 'src/app/shared/books/book-store.service'; // <- Reduced to just one dependency

@Component({
    selector: 'nss-book-list',
    templateUrl: './book-list.component.html',
    styleUrls: ['./book-list.component.scss']
})
export class BookListComponent {

    books$: Observable<Book[]>;

    constructor(
        private bookStore: BookStoreService // <- StoreService
    ) {
        this.books$ = this.bookStore.getAllBooks(); // <- Selector
    }

    addBook(book: Book) {
        this.bookStore.addBook(book); // <- Action
    }
}

BookStoreService

import { Injectable } from '@angular/core';
import { Selector, StoreService, Action } from '@ngx-patterns/store-service';
import { Observable } from 'rxjs';
import { Book } from 'src/app/shared/books/book.model';
import { getBooks } from 'src/app/store/books/books.selectors';
import { State } from 'src/app/store/store.model';
import { AddBookAction } from 'src/app/store/books/books.actions';

@Injectable()
export class BookStoreService extends StoreService<State> {

    @Selector(getBooks) // <- Selector
    getAllBooks: () => Observable<Book[]>;

    @Action(AddBookAction) // <- Action
    addBook: (book: Book) => void;
}

StoreService

The BookStoreService Injectable class should extend the StoreService<State> class where State is your ngrx state model.

Selectors

To use selectors you have to use the @Selector(...) decorator inside the StoreService. Add the selector function inside the @Selector(...) annotation:

// Define the selector function
export function selectAllBooks() {
    return state => state.books;
}

...

// Use the selector function inside the @Selector(...) annotation
@Selector(selectAllBooks)
allBooks: () => Observable<Book[]>;

The selector needs to be a function.

Be sure to use correct typing for the property inside the StoreService. If a parameter is required inside the selector function it also has to be required in the property typing.

export function selectBookById(id: number) {
                              ^^^^^^^^^^^^
    return state => state.books[id];
}

...

@Selector(selectBookById)
getBook: (id: number) => Observable<Book>;
         ^^^^^^^^^^^^
// The typing of the selector function and the property have to match!

Actions

To dispatch actions a similar approach as mentioned in the selectors is used. Add a property with the @Action(...) annotation.

// Defined the Action as a class
export class LoadBooksAction implements Action {
    public type = '[Books] Load books';
}

...
// Use the Action class inside the @Action(...) annotation
@Action(LoadBooksAction)
loadBooks: () => void;

If the Action class expects parameters, the typings on the property inside the StoreService have to match the class constructor.

export class AddBookAction implements Action {
    public type = '[Books] Add book';
    constructor(
        public payload: Book
               ^^^^^^^^^^^^^
    ) {}
}

...
@Action(AddBookAction)
addBook: (book: Book) => void;
         ^^^^^^^^^^^^
// The typing of the action constructor and the property have to match!

Prerequisites

Selectors need to be functions

// This will not work
const selector = state => state.property;
// This works
function selector() {
    return state => state.property;
}

Otherwise the typing of the StoreService Class won't work: () => Observable<any>

Actions need to be classes

export class LoadAction implements Action {
    public type: 'Load action';
    constructor(
        public payload: any
    ) { }
}

The actions are instantiated using the new keyword.

Testing

Selectors

Testing is made easy inside your components. You don't need to import the whole StoreModule.forRoot(...).

Simply provide the StoreService using the provideStoreServiceMock method. Then cast the store service instance using the StoreServiceMock<T> class to get the correct typings.

import { provideStoreServiceMock, StoreServiceMock } from '@ngx-patterns/store-service/testing';
...
let bookStoreService: StoreServiceMock<BookStoreService>;
...
TestBed.configureTestingModule({
    imports: [AppModule],
    providers: [
        {
            provide: BookStoreService,
            useValue: provideStoreServiceMock(BookStoreService)
        }
    ]
})
...
bookStoreService = TestBed.get(BookStoreService);

The StoreServiceMock class replaces all selector function on the store service class with BehaviourSubjects. So now you can do the following:

bookStoreService.getAllBooks().next(newBooks);

To emit a new list of books to the components observable. Super easy testing.

Actions

To test if a component dispatches actions import the NgrxStoreServiceTestingModule inside your Testing Module.

To get the injected Store instance use the MockStore class to get the correct typings.

import { NgrxStoreServiceTestingModule, MockStore } from '@ngx-patterns/store-service/testing';
...
let mockStore: MockStore;
...
TestBed.configureTestingModule({
    imports: [
        NgrxStoreServiceTestingModule
    ]
})
...
mockStore = TestBed.get(Store);

Optionally use the withState(...) function on the NgrxStoreServiceTestingModule to provide an object used as the state.

import { NgrxStoreServiceTestingModule} from '@ngx-patterns/store-service/testing';
...
const state = {
    books: []
}
...
TestBed.configureTestingModule({
    imports: [
        NgrxStoreServiceTestingModule.withState(state)
    ]
})

The MockStore class has a dispatchedActions property which is an array of dispatched actions. The last dispatched action is added at the end.

const lastDispatchedAction = mockStore.dispatchedActions[mockStore.dispatchedActions.length - 1];

// Or with lodash

const lastDispatchedAction = last(mockStore.dispatchedActions);

Example

For and example of all this have a look at the Angular Project in the src/app folder.

Store Service

Have a look at the BookStoreService

Testing

For examples on Component Tests please have look at the test for the BookListComponent and the NewBookComponent

Testing the StoreService is also very easy. For an example have a look at the BookStoreService

Keywords

ngrx

FAQs

Package last updated on 18 Jul 2018

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts