DEV Community

Cover image for I changed my mind. Angular needs a reactive primitive

I changed my mind. Angular needs a reactive primitive

Mike Pearson on December 07, 2022

YouTube Angular developers have waited 7 years for better integration with RxJS, but this doesn't seem to be happening. Instead, the Angular team ...
Collapse
 
mtmtmt profile image
MT • Edited

The question is, why not just implement signals with RxJS, that was promoted for almost a decade?
It's a BehaviorSubject + shareReplay + distinctUntilChanged + (perhaps) knowledge about the component lifecycle.
Why not just create something like EventEmitter? EventEmitter extends Subject, and Signal (the name may differ) could extend BehavriorSubject with ANY additional functionality needed, specific to Angular:

value$ = new Signal<...>(...); // Signal extends BehaviorSubject just like EventEmitter extends Subject.
Enter fullscreen mode Exit fullscreen mode

Or with decorators:

@Signal(...) value$;
Enter fullscreen mode Exit fullscreen mode

It may know about component's ngOnDestroy etc. and be aware about Angular's inner workings.

I've read your article, I understand that you're convinced, but to be honest I still see the main thing: Angular failed in integration with RxJS. And not they think that adding one more concept gonna change anything. No it won't.

Why not adopt rx-angular, improve RxJS debugging with e.g. Angular DevTools, build more creational operators, improve error handling, and most importantly: improve the communication with the community?

Let's face it: core of Angular team has left, and now the "new gen" Angular Team just uses it as their lab for experiments, following "trends" and trying to "sell" it. The future of the framework is vague. They have dropped improving the best parts of it, rewriting the engine every 2 years and changing their mind.

By the way, they officially fake HMR: the app is fully re-bootstrapped each time: github.com/angular/angular/issues/... And it's 2023.

So now they're gonna add "signals", then "hooks", then they would rewrite the Engine again "to support the brand new signals thing", then they'll drop RxJS "because signals are better", etc.
While they could improve RxJS and Angular's integration with it instead.

Now the question for you, Mike. Are you really really sure that RxJS can't cover what signals do? Maybe with another creational operator. Maybe with some improvements into RxJS itself as a lib. Are you really convinced or just gave up? :)

Collapse
 
mfp22 profile image
Mike Pearson

I've tried so hard. But it's such a hard thing to do to convince developers of the simplicity RxJS can bring when they haven't experienced a truly complex codebase, and ultimately the Angular team is downstream from the community. Something like this was inevitable.

But I've also been listening to Ryan Carniato's streams and I'm convinced there's just no way RxJS could be made to be as performant as signals for state synchronization. Syntax matters more to me personally. But performance is awesome too.

As for complexity, I think most people will be surprised at how much simpler this makes everything. It's a new concept, but we get to drop a few concepts too: No more Async pipe, no more *ngIf tricks, easy access to observable values in TS without subscribing... Probably more.

One thing I am concerned about is that they'll go too far and completely rip RxJS out even from places that work really well with RxJS. When your design decisions are driven by public opinion, you can only go so long before you create an inconsistent mess with breaking changes every year when everybody experiences pain that could have been foreseen and demands the knee-jerk reactionary opposite. If we move away from RxJS, the pain will be more and more spaghetti code.

Collapse
 
mtmtmt profile image
MT • Edited

And what about contributing in RxJS to add the signal as another entity there? Make it better, make the learning curve smoother, whatever.

import { signal, of, fromSignal } from 'rxjs';
import { toSignal } from 'rxjs/operators';

const observable: Observable = of([]);
const signal: Signal = signal(1);

const signal2 = observable.pipe(map(...), flatMap(...), toSignal());
const observable2 = fromSignal(signal2);

const signal3 = () => signal2() * 2;
Enter fullscreen mode Exit fullscreen mode

They already have e.g. Subjects, why not add Signals too...

Collapse
 
tmish profile image
Timur Mishagin

Yes, can agree with you. For me it's more and more clear that sooner or later it will be easier to migrate a project to Svelte rather than trying to get in touch with Angular's "cutting edge" point of view.

Why do I need to bother in choosing between RxJs or signals if I can just use Svelte's stores? It's just stupidly easier, isn't it?

Collapse
 
mfp22 profile image
Mike Pearson

Ugh, Svelte stores are the worst. They're just less-capable versions of BehaviorSubject.

Thread Thread
 
tmish profile image
Timur Mishagin • Edited

Maybe, but I don't have any example which might be a proof that BehaviorSubject is superior...

By the way, signals are not bad, but the way it's implemented in Angular it's bsht as for me. If Angular wants to catch up the others why should I go with it if others do it better (less verbose, more elegant and so on)?

I just looked at how ngrx is adopting signals and I was about to bloat. I wanted to say there the same crytics as here, but I didn't. I don't want upset the developers since they're at least trying. As it was mentioned it's not NgRx or RxJs, but Angular...

Thread Thread
 
mfp22 profile image
Mike Pearson

Anything with .pipe() is an example where BehaviorSubject is superior.

If you watch the video with Ryan Carniato and understand how the Angular team made signals, I actually think it's the best designed they could have possibly come up with, given the constraint that they are to be used in classes. The interop with RxJS could be better, but the signals themselves are very, very good.

Collapse
 
mpolutta profile image
Mat Polutta

And now I get it. I transitioned from AngularJs to Angular years back. I then transitioned from Promises to RxJS. Later I was assigned to a Dynamics CRM team where I created an Angular Library to cover all of the custom and missing components for our CRM apps. Well, whenever I went back to RxJS Observables for bug fixes or enhancements, it made my head hurt. Your examples make it very clear why. I am currently converting the Angular library to the Dynamics 365 PCF (which uses React). The Dynamics API provides for Promises, and that is what I first ported. Very timely, because my next component is invoking an LDAP API which I wrapped with RxJS Observables. It seems I'll keep it that way.

Collapse
 
omergronich profile image
Omer Gronich

SolidJS signals wait for each dependency to finish running before they run.

Not sure this is true TBH. Just tested this in the solid playground and it seems solid memos behave the same way as combineLatest here.

try running this solid code and check the console:

import { render } from "solid-js/web";
import { createMemo, createSignal } from "solid-js";

function Counter() {
  const [count, setCount] = createSignal(0);
  const [count2, setCount2] = createSignal(1);
  const increment = () => {
    setCount(count() + 1);
    setCount2(count2() + 1);
  };
  const derived = createMemo(() => {
    console.count('run calculation');
    return count() + count2();
  })

  return (
    <button type="button" onClick={increment}>
      {count()} 
      <br / >
      {count2()}
      <br />
      {derived()}
    </button>
  );
}

render(() => <Counter />, document.getElementById("app")!);
Enter fullscreen mode Exit fullscreen mode

The memo runs twice even though the signals were changed "at the same time". It can be solved using batching or scheduling but that's true for RxJs also.

The quotes are because values in JavaScript rarely ever change at the same time, I think multiple component inputs in angular can't ever change at the same time because angular always creates them in order synchronously.

Great article regardless, I enjoyed it thoroughly :)

Collapse
 
mfp22 profile image
Mike Pearson

Glad you liked it!

So what I mean by "same time" is literally same time, not just synchronous. It's called the diamond problem because you'll have a state at the top, then 2 derived states from that one, then a final derived state that combines them back again. So it's shaped like a diamond. So more like this:

import { render } from "solid-js/web";
import { createMemo, createSignal } from "solid-js";

function Counter() {  
  const [count, setCount] = createSignal(0);

  const derived1 = createMemo(() => count() * 10);
  const derived2 = createMemo(() => count() * 100);

  const combined = createMemo(() => {
    console.count('run calculation');
    return derived1() + derived2();
  })

  return (
    <button type="button" onClick={() => setCount(count() + 1)}>
      {count()} 
      <br / >
      {derived1()}
      <br />
      {derived2()}
      <br />
      {combined()}
    </button>
  );
}

render(() => <Counter />, document.getElementById("app")!);
Enter fullscreen mode Exit fullscreen mode

It's actually expected if you were to set the signal values sequentially that they would run twice like in your example. In reactive programming, you never update more than one thing for each event. (Technically you shouldn't update anything, and there should just be streams that listen to events, but whatever.) You probably know, but in React it would queue the updates and rerender only once. This actually makes imperative programming easy in React. That's something I'm not interested in.

Thanks for the comment. I didn't actually verify this before now; I just assumed based on things Ryan Carniato has said that signals behaved the optimal way. And I just checked it and it is right:

SolidJS Diamond

The diamond problem actually does come up quite often in real world apps. Not all the time, but often.

Collapse
 
omergronich profile image
Omer Gronich

Ohhh gotcha. That's cool. Thanks for the reply!

Collapse
 
amitbeck profile image
Amit Beckenstein • Edited

Very interesting read. There's indeed lots of issues with Angular and RxJS and I do hope that all the reworks of Angular prove to be good solutions. Making NgModules optional was already a great step towards better DX.

Collapse
 
mfp22 profile image
Mike Pearson

Definitely

Collapse
 
tronicboy1 profile image
井上 オースティン

I really enjoyed your article, thank you!

I think Angular’s decisions to lower the initial learning curve is going to lead to more interest in the long term and Signals could be a big part in helping that.

The biggest reason why I use angular is that it can solve code organization problems that other libraries frankly are ill equipped to solve. The injection systems, Services, Angulars Router, Module system, these are all very well done and can be used to organize big code bases.

I am worried like others that angular may drift from RxJS as RxJS is very very much a high end master level library.

Interoperability between signals and Observables as seen in SolidJs could give us the best of both worlds, removing the clutter of the Async pipe.

The angular team is saying that they are prioritizing interoperability so this could be a way to fix the problems of RxJS integration.

Id love to see as @InputSignal decorator which could also be converters to an Observable then piped.

Collapse
 
antischematic profile image
Michael Muscat

Signals make sense for functional components. I'm not convinced that signals alone will make Angular class-based components better.

I hope that whatever solution Angular comes up with works as well in TypeScript code as it does in templates, has interop with RxJS and handles things like inputs, host bindings and queries seamlessly, and isn't an eyesore to look at.

Collapse
 
mfp22 profile image
Mike Pearson

I think it will, as long as it doesn't try to mimic something from somewhere else beyond what makes sense for Angular. If these basic requirements are met, I'm sure people will like it.

I prefer function components, but besides syntax, it doesn't make a big difference

Collapse
 
sebosek profile image
Sebastian

Partially agree with @oz, about negative under tone.
However, I think it creates nice contrast to proposed bright future.
And your proposed API? Love it! ❤️
Short, simple, keen, easy to use, located to scope of the component. It would play soooo nicely with Standalone components.
You know what? You've restored my faith in Angular 😄

Collapse
 
mfp22 profile image
Mike Pearson

I'm completely clueless about the implementation details, so there might be unexpected limitations from that. And I thought for about 5 minutes before coming up with this, so there's a good chance there's something better. But I like concise things.

As for the negativity, on certain topics I can't help it. This is my experience and I don't see a point in dressing it up. I couldn't decide what audience to write for, because there's something for every perspective to hate and love in this. So I just included all the truth I thought was relevant.

Collapse
 
maslow profile image
Maslow

Very interesting read

Collapse
 
martinmcwhorter profile image
Martin McWhorter

Yes. A thousand times yes.

Collapse
 
jason_aunkst_9623286aac3b profile image
Jason Aunkst • Edited

RxJS is still more portable than an angular signal primitive.

Also you’re not going to deal with async situations like switch map with a signal.

Collapse
 
mfp22 profile image
Mike Pearson

Yes. I just think of signals as a good way of managing component-level reactivity. Like AsyncPipe++

Collapse
 
alexandis profile image
alexandis

Oh goodness... It's still too much for my brain. I got rid of state in my code. And I do use behaviorsubject and combinelatest. It does work. I know it's not optimal, but all these things and approaches stuffed into Angular apps... It's just too much to get hands into all this. It needs to be simpler.

Collapse
 
mfp22 profile image
Mike Pearson

You will like signals

Collapse
 
alexandis profile image
alexandis

I already use SignalR. But I still use BehaviorSubject and CombineLatest as well... :-D

Collapse
 
bretto profile image
Brett

Did you check ? rx-angular.io/

Collapse
 
timsar2 profile image
timsar2

‌Yes he did, He preferred rx-angular until stateAdapt being stable.

Collapse
 
timsar2 profile image
timsar2 • Edited

I would like to start a new project and use state-adapt, but this two week ppl are talking about signals and I think it would be good to use signal now instead of refactoring my app 6 month later.
It would be good if you can add a sample implemented with signals to state-adapt, like ngrx/signal-store:
github.com/ngrx/platform/discussio...

Collapse
 
mfp22 profile image
Mike Pearson • Edited

Here's what will not change with signals:

  • Sources
  • Selectors & state adapters

Here's what will change:

  • Not using the async pipe
  • Might add a convenience function, but seems a bit unlikely for now

StateAdapt takes advantage of RxJS behavior of doing nothing until something needs it. fromObservable subscribes immediately. This means the role for signals is pretty small in StateAdapt.

Signals are good for synchronously derived, non-reusable state. So, state that is forever local to a component. That's a very small part of overall state management.

All these libraries diving headfirst into signals are going down a path that restricts how reactive and flexible state management can be. It's not a good idea. Especially this early on... The best way to reduce the amount of refactoring you'll need to do is to start very conservatively with signals and learn all about them before you tie everything to them. Edit: Remember, this is just an RFC and the PR for fromObservable hasn't even merged yet. There is a high chance of the signal API itself changing.

Collapse
 
timsar2 profile image
timsar2 • Edited

I follow your content closely and thank you for sharing your experiences.
I was deciding to pick rx-angular and state-adapt to make zone-less my ionic mobile application (a mini social-media :D) until this days when signals come to angular preview.
So I'll start the app within a month later to learn and see more about signals from RFC.

Collapse
 
intermundos profile image
intermundos

Another great article of why not to use angular. Over engineering at its best.

Collapse
 
mfp22 profile image
Mike Pearson

Glad you liked it!

Collapse
 
jwp profile image
John Peters

To me Angular's glory days ended about 7 years ago. The core team left after Angular 2, and it took them all that time to address the Ngmodel clash in Angular 14 stand alone components. With Svelte we see simplicity where even React is heavy weight.

Collapse
 
mfp22 profile image
Mike Pearson

There are a lot of good people working on Angular. I love Svelte, but a lot of web apps depend on Angular and it's a lot better for them to have an improved Angular than to migrate to something else. Angular is on par with React in every way except syntax, and signals will bridge that gap a bit and hopefully enable a performance boost to something literally impossible with React, and faster than current Svelte. It would take a big effort but I think it's possible.

Collapse
 
xania profile image
Ibrahim ben Salah

Angular is making it hard to be happy Angular developer, it is not just RxJS.
e.g. I basically decided to never use @ContentChildren because of issues I had with it. I even tried to rewrite one of the core functionalities in Angular (the async filter) just find out that was not the issue. Also the templating issues typesafety and missing features still existing in v12 (last version I tried) was discouraging.

Collapse
 
oz profile image
Info Comment hidden by post author - thread only accessible via permalink
Evgeniy OZ

That negativity in your article doesn't help. It just brings some skepticism.

Collapse
 
mfp22 profile image
Mike Pearson

I'm feeling skeptical about this comment

And do you mean the general negativity, or is it something specific?

Collapse
 
jmroon profile image
Jason Rooney

My frustration with Angular these days is the lack of consistency, simplicity, and compile time safety. This hits the nail on the head when it comes to RXJS.

  • There are too many ways to react to state in inputs (setters, onChanges, observable).
  • There are too many ways to implement forms (reactive, template)
  • There are too many ways to create reusable form elements (inject ngcontrol, explicitly pass in formcontrol, implement ControlValueAccessor)
  • There is no safety on inputs (can't mark as required, undefined is different than not binding to an input, inputs can't be made readonly to avoid violating 1-way data flow

When it comes to reactive code, I believe you should be able to mix reactive code and imperative code. For derived reactive state, reactive code is fine. But reactive code gets ugly to maintain once you need to start incorporating multiple sources, state change triggers, and conditional flows.

Consider a component which calls several http endpoints, with conditionals along the way (if service returns this then do this, otherwise do that), with triggers on formcontrol value changes, and a trigger to cancel requests on resetting the form. All along the way, you want to return data, loading state, error state. This becomes an absolute behemoth in rxjs. Is it terse? Sure. Is it easy to compose? Nope. Is it easy to refactor? Not at all.

So many times when getting new requirements I've had to completely refactor RXJS pipes and a code change that would have been adding a simple if statement with imperative code becomes a two hour long refactoring job.

Collapse
 
mfp22 profile image
Mike Pearson • Edited

From the Angular API, I wish everything was more reactive. That would simplify things. (So, template driven forms.)

Can you please show me an example of what you're talking about? I've had the complete opposite experience. I want to know what I might be missing. So show a current component that's completely reactive and has the stuff you mentioned, then describe a feature change that needs to be made, and we can compare how hard it is with RxJS vs imperative code.

For now, I'm just happy that completely reactive code gives me no more race conditions or inconsistent state, improved code organization, reduced page load times, simpler state management and better function names.

dev.to/this-is-learning/5-reasons-...

Collapse
 
hiumesh profile image
Umesh Chandra Dani

good

Collapse
 
hiumesh profile image
Umesh Chandra Dani

good

Collapse
 
hiumesh profile image
Umesh Chandra Dani

good

Thread Thread
 
hiumesh profile image
Umesh Chandra Dani

good

Thread Thread
 
dinhphuong20z1 profile image
Dương Đình Phương

good

Collapse
 
oleksandr profile image
Oleksandr

Isnt RxJS zip do the trick for derived observables combination?
And yes it will work if adding operators to hot observable(Subject) wouldnt make derived observable cold

Collapse
 
mfp22 profile image
Mike Pearson

I remember thinking a lot about that, but sometimes an input observable will emit when others don't.

Collapse
 
oleksandr profile image
Oleksandr • Edited

I think RxJS Observable needs kind of .pipeShare method which applies operators to hot observable and still keep it hot - it is regarding derivatives.
But regarding combineLatest - interesting how signals do it (wait until all settled for all derivatives). curious if appying debounceTime(0) can reach same effect.

Collapse
 
hiumesh profile image
Umesh Chandra Dani

good

Collapse
 
lucaster profile image
lucaster

Some things that take 4 lines of code with RxJS + Angular only take a single character with RxJS + Svelte.

This sounds amazing! Would you provide an example?
Thanks!

Collapse
 
mfp22 profile image
Mike Pearson

Angular

data: Data | undefined;
sub = this.data$.subscribe(data => this.data = data);

onDestroy() {
  this.sub.unsubscribe();
}
Enter fullscreen mode Exit fullscreen mode

Svelte

$: data = $data$;
Enter fullscreen mode Exit fullscreen mode

That little $ prefix is telling Svelte to subscribe and unsubscribe appropriately. It's concise and declarative, but it isn't valid JavaScript; Svelte is a compiler. But it's worth it imo, because Angular's syntax is both verbose and imperative. Subscribing only breaks out of the reactive paradigm if you don't have another reactive paradigm on the other side. Svelte has synchronous reactivity built in.

It even works in callback functions. Here's another example:

Angular

(submit)="saveData()"
Enter fullscreen mode Exit fullscreen mode
saveData() {
  this.data$.pipe(first()).subscribe(
    data => this.service.saveData(data);
  );
}
Enter fullscreen mode Exit fullscreen mode

(Or you could use rxLet and pass up the unwrapped value from the template into the callback function.)

Svelte

on:submit={() => saveData($data$)}
Enter fullscreen mode Exit fullscreen mode

You can play with a Svelte demo I made here

Collapse
 
mfp22 profile image
Mike Pearson

Updated Example with Signals

Angular

data = toSignal(this.data$);
Enter fullscreen mode Exit fullscreen mode

Svelte

$: data = $data$;
Enter fullscreen mode Exit fullscreen mode

Angular Callback

data = toSignal(this.data$);
Enter fullscreen mode Exit fullscreen mode
(submit)="saveData(data())"
Enter fullscreen mode Exit fullscreen mode

Svelte Callback

on:submit={() => saveData($data$)}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
giusliso profile image
Giuseppe • Edited

I understand Signal’s advantages but If it will be a substitute of rxjs means that all current applications that use rxjs will probably need a deep refactoring. It’s like what we had when we moved from Angularjs to Angular 2!!! If my thoughts will became reality i will not choose anymore google framework for develop applications!

Collapse
 
mfp22 profile image
Mike Pearson

I think that's a bit of an exaggeration :)

I've been helping teams upgrade from AngularJS to Angular for the past 2 years and this is like 10% the size of that change. It's incrementally adoptable and will drastically simplify components. It will make it easier to use RxJS with Angular.

And believe me, if Angular made it even more difficult to use RxJS in Angular, I would start making tools to help teams migrate away from Angular. Declarative code is more important than any single framework.

Collapse
 
hanss profile image
Hans Schenker

I like signals for handling street traffic but not for handling state in Angular!

Collapse
 
spock123 profile image
Lars Rye Jeppesen

Happy now? :)

Collapse
 
mfp22 profile image
Mike Pearson

Yes, about to get lunch

Some comments have been hidden by the post's author - find out more