vsavkin / ngselectors

Redux-style selectors for Angular 2.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

NgSelectors

Redux-style selectors for Angular 2.

Basic Usage

Let's define our store as follows:

export class Store {
  users:any = {1: "victor", 2: "thomas"};
  messages:any = {1: "victor's message", 2: "thomas's message"};

  notifications = new Subject<any>();

  updateUser(id: string, value: string): void {
    this.users[id] = value;
    this.notifications.next(null);
  }

  updateMessage(id: string, value: string): void {
    this.messages[id] = value;
    this.notifications.next(null);
  }
}

Then, let's define two basic selectors.

type GetUser = (id: string) => string;
export function getUser(store: Store) {
  return (id: string) => store.users[id];
}

type GetMessage = (id: string) => string;
export function getMessage(store: Store) {
  return (id: string) => store.messages[id];
}

Next, let's register them in our NgModule.

@NgModule({
  declarations: [TestComponent],
  providers: [
    Store,
    {
      // this tells the library when to check if selectors changed
      provide: CHANGES,
      useFactory: (s:Store) => s.notifications,
      deps: [Store]
    },
    {
      provide: getUser,
      useFactory: getUser,
      deps: [Store]
    },
    {
      provide: getMessage,
      useFactory: getMessage,
      deps: [Store]
    }
  ]
})
class TestNgModule { }

And finally, let's add a component using them.

@Component({
  selector: 'test',
  template: '',
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [
    // this wraps the functions defined in the ng module
    selector(getUser),
    selector(getMessage)
  ]
})
class TestComponent {
  recorded: any[] = [];

  constructor(@Inject(getUser) private u: GetUser,
              @Inject(getMessage) private m: GetMessage){}

  ngDoCheck() {
    this.recorded.push({
      user: this.u('1'),
      message: this.m('1')
    });
  }
}

Composition

Let's start by defining a new selector that uses the two previously defined selectors.

@NgModule({
  declarations: [TestComponent],
  providers: [
    //...
    {
      provide: 'getUserAndMessage', // compose the two values
      useFactory: (u: GetUser, m: GetMessage) =>
        (userId: string, messageId: string) => ({user: u(userId), message: m(messageId)}),
      deps: [getUser, getMessage]
    }
  ]
})
class TestNgModule { }

Updated component.

@Component({
  selector: 'test',
  template: '',
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [
    selector(getUser),
    selector(getMessage),
    // since the new function returns a new object every single time
    // we cannot rely on the default equality check (which is a reference check)
    selector('getUserAndMessage', {equality: shallowEqual})
  ]
})
class TestComponent {
  recorded: any[] = [];

  constructor(@Inject(getUser) private u: GetUser,
              @Inject(getMessage) private m: GetMessage,
              @Inject('getUserAndMessage') private um: any){}

  ngDoCheck() {
    this.recorded.push({
      user: this.u('1'),
      message: this.m('1'),
      userAndMessage: this.um('1', '1')
    });
  }
}

How Does it Work?

The only interesting part of this library is the selector helper. It takes a DI token, fetches it, and decorates it. The decorated function will record all the invocations to determine if the component needs to be rechecked.

For instance getUserAndMessage will record all the invocations of this function. Once the CHANGES observable emits an event, the library will recheck all the invocations of getUserAndMessage to see if any of them resulted in a different value. If this is the case, the library will request a change detection check for the test component.

So the library does the minimum amount of change detection checks required. In the example above, only when User 1 or Message 1 changes, the test component will be rechecked. If any other user or message changes, the component WON'T be rechecked.

How to Use

To make it work with Angular CLI, do the following:

Add ngselectors to your package.json:

"ngselectors": "https://github.com/vsavkin/ngselectors"

Add this to angular-cli-build.js

'ngselectors/**/*.js',

Update system-config.ts

const barrels: string[] = [
  // Thirdparty barrels.
  'ngselectors'
];

const cliSystemCo
System.config({
  map: {
    'ngselectors': 'vendor/ngselectors/build'},
  packages: cliSystemConfigPackages
});

About

Redux-style selectors for Angular 2.

License:MIT License


Languages

Language:TypeScript 59.3%Language:JavaScript 40.7%