npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ngx-reactive-decorators

v1.0.0-rc1

Published

Set of decorators for Angular to make it more reactive

Downloads

9

Readme

ngx-reactive-decorators Build Status

Set of decarators for Angular to make it more delarative and reactive.

Reasoning behind

Angular is for sure great framework (mainly because of RxJS) but on the other hand it is not as reactive as it could be so developers are often forced to write ugly, imperative code. With RxJS dependency and this library we can go further and make Angular more Reactive.

Examples of poor reactivity

Problem 1: @ViewChild

Abstract use case: Every time you click the button it should take latest value of numbers$ stream and log it to console.

<button (click)="logToConsole()">Click me!</button>
@Component(...)
class MyComponent {
  public numbers$ = new BehaviorSubject(1)

  public logToConsole(): void {
    this.numbers$
      .take(1)
      .subscribe(console.log)
  }
}

Combining streams and outputs is painful and forces you to use take(1) to avoid memory leaks. So you basically subscribe, get latest value and unsubscribe, quite imperative. We can make it much more declarative by using streams which gives us full advantage of RxJS operators

<button #button>Click me!</button>
@Component(...)
class MyComponent {
  @ViewChild('button') public button: ElementRef

  public numbers$ = new BehaviorSubject(1)

  public logToConsoleSub = Observable
    .fromEvent('click', this.button.nativeElement)
    .flatMap(() => this.numbers$)
    .subscribe(console.log)

  ngOnDestroy() {
    this.logToConsoleSub.ubsubscribe()
  }  
}

Code is much cleaner now because you can just .flatMap clicks to numbers$. But...

TypeError: Cannot read property 'nativeElement' of undefined

At the moment of subscribing ViewChild has not been resolved yet so you need to use lifecycle hooks

<button #button>Click me!</button>
@Component(...)
class MyComponent {
  @ViewChild('button') public button: ElementRef

  public numbers$ = new BehaviorSubject(1)

  public numbersFromClicks$ = Observable
    .fromEvent(this.button.nativeElement, 'click')
    .flatMap(() => this.numbers$)

  public logToConsole: Subscription;

  ngAfterViewInit() {
    this.logToConsole = this.numbersFromClicks$
      .subscribe(console.log)
  }

  ngOnDestroy() {
    this.logToConsole.unsubscribe()
  }
}

Problem 2: Imperative ngOnChanges

Abstract use case: If Input a is resolved for first time I want to log 'foo'. If Input a is not resolved for the first time I want to log 'bar'.

Let's assume that our console.log() is just an external library we want to interact with. We often have situations where we want to call a method when data changes e.g. when working with D3.

@Component(...)
class MyComponent {
  @Input() a
  @Input() b
  @Input() c
  @Input() d

  ngOnChanges(change: SimpleChanges) {
    if (change.a.previousValue !== change.a.currentValue) {
      if (change.a.isFirstChange()) {
        console.log('foo')
      } else {
        console.log('bar')
      }
    }
  }
}

The only way to react to Input change is ngOnChanges. The problem is that it is called for any Input so you need to check if this change was triggered by a with classic if statement. Next you need to distinguish first change from another changes so another if statement... And we end up with imperative code.

There is a hacky way to react to changes of specific Input, but you loose possibility to check if it was first change. And of course getters should be pure (without any side effects) so we are doomed:

@Component(...)
class MyComponent {
  @Input() a: number
  @Input() b: number
  @Input() c: number

  public get myA(): number {
    console.log('foo') // Hell no :(
    return this.a
  }
}

If you go further and do a deep dive into how ngOnChanges works you will notice another problem. Here is how SimpleChange looks like:

class SimpleChange {
  constructor(previousValue: any, currentValue: any, firstChange: boolean)
  previousValue: any
  currentValue: any
  firstChange: boolean
  isFirstChange(): boolean
}

Have you noticed that previousValue and currentValue are just any? So you loose all advantages of static type checking and you can't make use of generic types on runtime.

ngx-reactive-decorators solution

Solution for Problem 1: @ViewChildObservable

<button #button>Click me!</button>
@Component(...)
class MyComponent {
  @ViewChildObservable('button') public button$: Observable<ElementRef>

  public numbers$ = new BehaviorSubject(1)

  public logToConsoleSub = this.button$
    .map(button => button.nativeElement)
    .flatMap(elem => Observable.fromEvent(elem, 'click'))
    .flatMap(() => this.numbers$)
    .subscribe(console.log)

  ngOnDestroy() {
    this.logToConsoleSub.ubsubscribe()
  }  
}

So instead of waiting for ViewChild inside lifecycle hook we just emit resolved value from stream. It gives you possibility to handle asynchronous Angular operations in reactive way.

Solution for Problem 2: @InputObservable

@Component(...)
class MyComponent {
  @InputObservable() a$: Observable<number>
  @InputObservable() b$: Observable<number>
  @InputObservable() c$: Observable<number>

  public logFoo = this.a$
    .take(1)
    .subscribe(() => console.log('foo'))

  public logBar = this.a$
    .skip(1)
    .distinctUntilChnged()
    .subscribe(() => console.log('bar'))  
}

By transforming each Input to stream of values we can use Rx operators to react to Input changes. Moreover, streams are typed as Observable so you have all advantages of static type checking. But does it have any limitations? Let's try to mimic ngOnChanges features:

Q: How to mimic currentValue and previousValue?

A: Use bufferCount()

public prevAndCurrent$ = this.input$
 .bufferCount(2, 1)
 .map(([previousValue, currentValue]) => ({
   previousValue,
   currentValue,
 }))

Q: How to mimic isFirstChange?

A: Use first(), startWith() and proper mapping if you really need to have boolean. But in general RxJS gives you operators like withLatestFrom(), concat() etc. to wait for observable

public isFirstChange$ = this.input$
 .first()
 .mapTo(true)
 .startWith(false)

Q: How to mimic complete SimpleChange?

A: Just combine previous examples and create the instance

public simpleChange$: Observable<SimpleChange> = Observable
 .combineLatest(
   this.prevAndCurrent$,
   this.isFirstChange$,
   ({ previousValue, currentValue }, firstChange) => new SimpleChange(
     previousValue,
     currentValue,
     firstChange,
   )
 )