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

angular-pharkas

v7.0.0

Published

> Pharkas, Freddy Pharkas. / Frontier Pharmacist bourgeoisie — The Ballad of Freddy Pharkas (Frontier Pharmacist)

Downloads

31

Readme

Angular-Pharkas

Pharkas, Freddy Pharkas. / Frontier Pharmacist bourgeoisie — The Ballad of Freddy Pharkas (Frontier Pharmacist)

It could stand for Powerful Hooks for Angular ReactiveX Components, Attributes, and States. It could, but it probably doesn’t.

The angular-pharkas library is a wild, frontier pharmacist approach to building Angular components, in that it is all about that RX:

  • "Observable only"
    • No Subjects leaking out of this API!
    • No intentional imperative escape hatches
  • Zone-free
    • Observables are already, always push no reason for any "change detection" strategy than "OnPush"
    • Zone-free means no need for zone.js: noop that bloated crud
  • AsyncPipe-free
    • AsyncPipe is great, but who needs | async everywhere in your templates when that should have been the default? It's especially unnecessary when "Observable only"
  • Managed subscriptions
    • No ngOnit and ngOnDestroy dances, the last ngOnDestroy you'll ever need is the automatic one in the BaseComponent
    • "Smarter" subscriptions by default
      • Template binding change "pushes" are throttled to requestAnimationFrame for smooth as magic views

It is inspired by ReactiveUI (.NET), the Hooks of React, and rx-angular, but it is also none of those.

The Inevitable Angular Compatibility Chart

| Pharkas version | Supported Angular | | --------------- | ----------------- | | 7.0.0 | Angular 15 | | 4.0.0 | Angular 13 | | <=3.0.0 | Angular 11 |

Start a Component

A basic empty component starts like this:

@Component({
  selector: 'app-my-example',
  template: 'Example Template', // or templateUrl or whatever
  // …
  // if in a mixed project with Zone.JS, you can be sure to tell Angular you don't need it
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MyExampleComponent extends PharkasComponent<MyExampleComponent> {
  constructor(ref: ChangeDetectorRef) {
    super(ref)
  }
}

The generic argument to PharkasComponent gives you type safe magic when you need to provide property names, and is a sign of where the redundancies start to get good types and also fit inside Angular's box for how a component needs to look.

Need to pass the ChangeDetectorRef from Angular's DI because where we are going, we need no Zone.

General Flow, Tips and Tricks

The general flow for a Pharkas-based component is (Create)/Use/Bind:

  1. Create a resource, if necessary
  2. Use that resource to get an observable of changes to it
  3. Bind observables to resources that need to observe them

Create isn't always needed, many "use hooks" will create things implicitly.

In general:

  • If everything is Observable throwing $ on everything is tedious
    • This probably isn't the right sort of saloon for throwing all those dollar bills around, partner
  • If it changes, it should be an observable
  • If it needs to update the view somehow, it needs to bind to something
    • But also, not all binds update the view
  • The more words in a Pharkas function the more of an edge case it is intended to serve, try the "defaults" first
  • Observables, observables, observables; stop the escape hatches!
  • Avoid using this as much as possible
  • Use readonly for Observables or other template bindings that should not change

Angular Inputs

The Angular Input pattern, for anything and everything that might possibly move or change:

@Component({
  // …
})
export class MyExampleComponent extends PharkasComponent<MyExampleComponent> {
  @Input() set testInput(value: Observable<string>) {
    this.setInput('testInput', value)
  }

  constructor(ref: ChangeDetectorRef) {
    super(ref)

    const testInput = this.useInput('testInput', 'Hello World')

    // Use testInput to build observable pipelines…
  }
}

Inputs are set only, no get. To use the input you must use useInput to get an Observable. No imperative escape hatch here!

Good Inputs themselves are Observable of changes and you should encourage your consumers to also use Observables. Also good Observables shouldn't be bound more than once and you'll see a warning in the console in Dev Builds if a consumer does that.

But sometimes you need backward compatibility with older consuming components and setInput has your back also supporting that if you need it:

export class … {
  @Input() set testInput(value: string | Observable<string>) {
    this.setValue('testInput', value)
  }
}

It will give you console warnings in development builds for non-Observable inputs.

this.useInput accepts as an optional second parameter a default value or a function that returns a default Observable if an Observable was not bound to the input property prior to ngOnInit.

export class … {
  constructor(ref: ChangeDetectorRef, service: SomeService) {
    super(ref)

    const testInput = this.useInput('testInput', () => service.GetSomeObservable())

    // Use testInput to build observable pipelines…
  }
}

Note that using a service class dependency adds tighter coupling for what may be more useful as an always provided input, which would be looser coupled.

Also, yeah the string property names and duplication isn't great, but we are working with what Angular gives us.

Template Bindings (View Variables, Display Slots, Whatever You Call Them)

The template binding pattern:

@Component({
  // …
})
export class MyExampleComponent extends PharkasComponent<MyExampleComponent> {
  get testDisplay() {
    return this.bindable<string>('testDisplay')
  }

  constructor(ref: ChangeDetectorRef) {
    super(ref)

    // Build some observables…

    this.bind('testDisplay', someObservable, 'Default Value')
  }
}

This pattern, like Inputs, needs a bunch of redundant property names, sorry.

Template variables are get only. Set them by binding an Observable to them.

Don't use the getter imperatively in your own code. You bound the observable, observe the Observable.

"Smart" Bindings By Default

By default many Pharkas bindings are scheduled around requestAnimationFrame. This helps give smoother feeling performance, and avoids Angular's detectChanges() work and rerendering templates faster than the browser can possibly render them.

Some things you may need to make sure get to the DOM ASAP, such as user inputs when strongly managing the DOM. (If you were to build a "push" alternative to Angular's ReactiveForms, for instance.) In such cases there are "Immediate" variants of bindings. You may not need them, and the defaults should do what you need in most cases.

Suspense

A component may bind a suspense observable:

@Component({
  // …
})
export class MyExampleComponent extends PharkasComponent<MyExampleComponent> {
  constructor(ref: ChangeDetectorRef) {
    super(ref)

    // Build some observables…

    this.bindSuspense(someSuspenseObservable)
  }
}

When this Observable<boolean> emits true, template change notifications are skipped for non-immediate template bindings until the next false is emitted. The pharkasSuspense blinkenlight reflects this suspense state.

This may be useful for instance while a loading operation is taking place to display some simple loading indicator and reduce "UI bouncing" with intermediate states.

Note that this only suspends normal change detection pushes. It will not entirely eliminate such "UI bouncing" as for instance an immediate binding will still trigger Angular change detection.

Blinkenlights

Pharkas provides a set of blinkenlights (lights intended to blink a status) for very basic error status indication or suspense states (such as loading).

The error blinkenlights are "last chance error reporting" blinkenlights for generic error situations when any observable provided to bind or bindEffect or an "Immediate" variant of such has thrown an error.

It is encouraged to move error detection and avoidance strategies such as retries up into your observable pipelines themselves, but sometimes you need a last chance way to detect that the worst has happened, the component may be stopped/stuck, and in that case render some sort of frowny face 😟.

Callbacks

Callbacks are very useful for everything from consuming output bindings of other Angular controls to events from non-Angular components to stashing local state in an easy to call to update manner.

The callback pattern:

@Component({
  // …
})
export class MyExampleComponent extends PharkasComponent<MyExampleComponent> {
  // Type only, no implementation:
  readonly testCallback: (e: MouseEvent, somethingElse: string) => void

  constructor(ref: ChangeDetectorRef) {
    super(ref)

    // create the implementation
    this.testCallback = this.createCallback('testCallback')
    // get an observable of the callback calls: Observable<[MouseEvent, string]>
    const testCallback = this.useCallback('testCallback')

    // Use the observable to build pipelines, eventually bind it to something…
  }
}

Note that callbacks on their own do not update view state. Only binding an observable downstream somewhere to a template binding/display property will do that.

"Last Resort" Side Effects

When all else fails and you absolutely must subscribe to do a side effect:

this.bindEffect(observable, (value) => {
  /* side effect */
})

bindEffect is a last resort when you absolutely must subscribe a side effect to something. It's an escape from the comfy observable management of Pharkas. (Though not a true escape to imperative code either, Pharkas still manages the subscription on your behalf and any escape hatches you build on the class with effects are your problem to manage, Pharkas is still not leaking RxJS Subjects here.)

bindEffect does not call Angular change detection on its own, and you may need to do it manually, but don't do that, figure out out move that stuff to bindable and observables you can bind instead.

Angular Outputs

The Angular Output pattern:

@Component({
  // …
})
export class MyExampleComponent extends PharkasComponent<MyExampleComponent> {
  @Output() readonly testOutput = new EventEmitter<string>()

  constructor(ref: ChangeDetectorRef) {
    super(ref)

    // Build some observables…

    this.bindOutput(this.testOutput, someObservable)
  }
}

EventEmitter<T> is the only class Angular supports for outputs. It is an awful class, leaking BehaviorSubject<T> APIs and is an inescapable imperative escape hatch that shouldn't exist in the core of the framework, much less be the only way to do this, but here we are.

Don't use the EventEmitter<T> directly, ever. Only use this.bindOutput().

readonly is a useful Typescript type hint to keep us from doing some stupid things with it such as breaking our bindings, at least.