DEV Community

Cover image for Why I like Signals in Angular and the importance of Declarative code

Why I like Signals in Angular and the importance of Declarative code

Michele Stieven on March 02, 2023

I've been reading some great posts from very smart folks, about React, about Solid, and about Angular. I basically love them all (both the framewo...
Collapse
 
fnh profile image
Fabian Holzer

The proposal makes a good first impression, especially the interoperability with RxJS looks promising.

For the rather large Angular codebase I am working on, I would't start integrating a development preview API, and even if they make signals stable, I won't go over hundrets of components until I can reap the the real benefit, which is not there yet: getting rid of zone.js. But zoneless application are in my opinion the most interesting change that is on the Angular roadmap since Ivy. Might also be worth to reevaluate ther angular-elements API when all this comes together. The last time I looked at it, the custom-elements had to bundle so much framework code that they became to bloated for the purpose of reuse outside of an angular app.

Collapse
 
michelestieven profile image
Michele Stieven

Sure you may want to wait! That's perfectly fine. However, it's not that there are no benefits other than the removal of zonejs. Like:

  • performance
  • glitch-free execution
  • less code and less noise than BehaviorSubjects + combineLatest + map (and updating the state is easier, this is quite ugly without a wrapper, state$.next(state$.getValue() + 1) becomes state.update(s => s + 1)
  • no async pipe needed and possibly avoiding source$ | async ?? defaultValue everywhere (or using | null on every input)

I found Angular Elements useful for migrations, other than that, if I'm writing a Custom Element from zero, I'd suggest something like Lit which is a fantastic library!

Collapse
 
fnh profile image
Fabian Holzer • Edited

Fair points, although in my particular setting, less pressing issues.

Starting from zero was what we did seven years ago (angular 2 beta.7 - a point in time where standalone components were not new because the NgModule was not there yet). The company has grown since then quite a bit and my thinking therefore is in the direction on how to pontially be able to share the internal ui component library (or with nearly 200 components in it, a specialized subset thereof) over with e.g. teams that got acquired without them having to re-write their UI completely.

Collapse
 
johnlandgrave profile image
John Landgrave

I'm not meaning to start any sort of flame war here — at all — but isn't this remarkably similar to Vue 3's setup function syntax? Like, if you just put those input()'s into a defineProps (if using script setup) and pull it out of the class (including all of the necessary this removal and such) then you have a Vue3 component.

I only bring this up to ask: are the frameworks converging on a common, good idea and syntax?

Collapse
 
michelestieven profile image
Michele Stieven

Yep it is indeed very similar! This type of reactivity is not new for Vue, and the fact that it uses objects instead of functions (React, Solid...) makes it easy to draw parallels with classes!

About converging, I really don't know! Each flavor has its merits and strengths, the React team for example appears to be very much against this type of reactivity, they like React's mindset better, so I guess they'll go in that direction. Who knows in the future! :)

Collapse
 
darknessm0404 profile image
Micaël Félix

That's what I thought when reading the article: go vue3 in component api mode and you will have what you're hoping for ;)

Collapse
 
dtrunin profile image
dtrunin

We could create a pipe for heading calculation. Functional, declarative, change detection friendly.

export class HeadingPipe implements PipeTransform {
  transform(videos, emptyHeading): string {
    const count = videos.length;

    return count > 0
      ?  count + ' ' + (count > 1 ? 'Videos' : 'Video')
      :  emptyHeading;
  }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
michelestieven profile image
Michele Stieven

That's a lot of extra code though, imagine writing that for each and every derived state! 😅 I suggest using Pipes for reusable utilities rather than specific use cases!

Collapse
 
vmohir profile image
Vahid Mohammadi

I agree it's a lot of boilerplate code but I think pipes can make components a lot simpler. I highly recommend them. Especially with the standalone pipes it's much less boilerplate code.

Collapse
 
michelestieven profile image
Michele Stieven

That's because it's 9 lines of code! I cannot use 500 lines of code to demonstrate a concept, no one would read the article. But that would be the reality, and I stand by my opinion that that'd be waaaaay harder to read: I do it every day :)

The performance tradeoff is negligible at best. FP trades a bit of performance (99% of the times it's unnoticeable) for declarativeness.

Collapse
 
aecea profile image
Duc-Thien Bui

I'm sitting in the same boat and the tide is changing for Angular.

Collapse
 
crisz profile image
crisz

If you don't use classes with mutable properties, where are you supposed to put the state?
As my uni prof. used to say "a completely functional programmed software is a completely useless software"

And if you answered the question with "Redux" then well... think that a redux state can be implemented with a class :D

Collapse
 
michelestieven profile image
Michele Stieven

With Signals, the mutable state is inside the signals. State is mutable but signals are not.

class MyComponent {

  state = signal(initialState);

  onClick() {
    // You change the inner value, you don't reassign the property
    this.state.set(newState);
  }
}
Enter fullscreen mode Exit fullscreen mode

Your prof was right, that's why there's no such thing as a useful completely functional program. It'd do nothing. Executing code by itself is a side-effect. The point of FP is not to avoid side-effects, but to isolate them. Or, wrap the effect so that it runs only when necessary (lazy evaluation), this gives you the ability to compose them without running them.

// RxJS

// This code does nothing, it's just the recipe.
// You must call `subscribe()` to run it.
const result$ = forkJoin([promise1, promise2])
Enter fullscreen mode Exit fullscreen mode
Collapse
 
michelestieven profile image
Michele Stieven

I agree 100%! :)