Welcome to my RU-vid channel! I'm Rainer, a full-stack developer from Austria specializing in Angular and Spring. As a Google Developer Expert and a trainer/consultant at AngularArchitects.io, I'm here to share my expertise with you. Subscribe and stay updated with my latest content! Don't forget to connect with me on X (@rainerhahnekamp).
Regarding 46:00 I think it's much simpler: the effect() takes an arrow function. If no signals inside that function emitted changed value since last change detection cycle, then the whole arrow function is not executed, but if any one or more have their values changed, then the whole arrow function is executed. Also, I believe it uses reference equality to tell if signal should produce a new value to consumers. That's why immutability of the internal state is so important.
@@RainerHahnekamp I mean the logic to run or not run the effect lambda is probably determined by whether any one or more of the tracked signals associated with it have a new value or not.
Karma does still exist, because they are still working on the migration to Web Test Runner. To be fair, one has to say that Jasmine/Karma doesn't have any issues - it is running fine - so they don't see this task as main priority. Jest will come after Web Test Runner is done and Jasmine will stay (as I said in the video). Btw, in th meantime, I would use Jasmine for a new project. Given the troubles we have with Jest/Angular and the pace of the migrations, I am afraid official Jest support is something we get in 2025. Last year, I had the impression that Jest will already be available in Angular 17.
This is great content as always. The concern I have about moving to standalone components is the bundle size. I haven't taken a deep look into this, but I've read here and there that there's a lot of code duplicated and your app actually grows in size. I'm sure it can be mitigated with lazy loading, but it's something to consider.
Hi, where did you hear about the bundle size issue? Could it be that you are mistake standalone with the barrel files? Having a lot of barrel files (index.ts) will problems to your bundler because it might not be able to tree-shake them efficientyl, and you may end up with a huge initial bundle. Regarding Standalone vs NgModule, there is no impact on the bundle size.
Hi, great video again! I'm using createActionGroup method to organize actions from the source which uses them. This way, different actions can lead to the same effect like calling the loadCustomer method from a service for example. This is where I'm struggling with the facade pattern... How can I implement the customerFacade? Do I have to implement several methods like fromPageXLoadCustom(), fromPageYLoadCustomer()... in order to dispatch the right action? I mean that I don't want to loose the origin of each action for debugging purposes.
I see, you are the action hygiene. So you have one facade method per action. If you have multiple actions, then you also have multiple methods. As a side note: If you have multiple actions just for debugging purposes, why don't you add {context: string} property to the action's payload. Saves the the same purpose. On the other side, if you have different load actions, stick to what you have.
@@RainerHahnekamp Thank you for your reply! So It seems that using createActionGroup method and a facade service is not really appropriate. The source property in createActionGroup method is the interesting property (as well as the organisation of the actions of course) and unfortunately we can't benefit to use it.
I'm experiencing the following issues: 1) When I uncomment the webserver section in playwright.config.ts, then playwright reports "No tests" 2) In my test *.spec.ts file, await page.goto('') results in an error with no explanation. Thoughts?
You typically get that message, if you have an invalid configuration file. Check that your commenting also wasn't applied to some things (parenthesis for example). If you uncomment it, and Playwright works, it is definitely an invalid config. You can post the contents of it here as well.
@@RainerHahnekamp Great, RxJS is also very interesting topic so I will surely watch it. Btw. RxJs is broad topic so I'm not expecting that you will touch this subject in mentioned video but for future content you could consider adding to your 'pool of ideas' reactive/declarative vs imperative style of programming in context of RxJs (it's not so obvious for many programmers who are used to write imperative code - me included)
@@youpeekayeyI touched a little bit on it. I think that RxJs forces you to do declarative programming and I have seen very often that people overuse that paradigm. So I have an example where I show how simple it can become if you add a little bit of side effects into it. We will see 😅
I don't see any benefit in using Selenium anymore. I've been using Selenium (Selenide) for so many years, and we had to deal with so many flaky tests that we gave up one time. When Cypress came out, we gave E2E testing with Cypress another chance and were more than happy. Since then, I've never heard that somebody really enjoyed working with any webdriver-based derivate. Since I would not use it, I don't want to make a video about it. I know that there is now WebDriver Bidi. It has been hailed to bring the same quality as CDP (Cypress, Playwright) but download statistics show a completely different picture. Did I miss anything, respectively is there a particular reason why you are asking?
@@RainerHahnekamp thank you. I asked because a lot of testers QA teams use Selenium from what I see in jobs posts and I wonder why it was not used with Angular in any of your videos, and you are the best person to ask for it when talking about testing 🙂 .Have a great day
@@etexalbania7301 Yes, you also have remember that Selenium was the standard framework for more than a decade. In huge environments you don't change that quickly. So I don't think that there will be demand for Selenium developers in the future as well. For new projects, though, Selenium doesn't seem to be the first choice anymore (diplomatically said).
I was talking with a teammate the other day about how most content explains the easy and "obvious" scenarios, but you also cover real scenarios, so thank you
That's an important issue you are showing here. Thank you for that. I think i will stick with rxjs on the data layer and use signals in components only. I may try out a signal store for state management. Currently I am quite happy with rxAngular StateManagement and event driven reactivity.
Serwas Markus, yeah so using Signals in the templates is definitely a safe bet. I don't think that we have to use RxJs all the time. There are quite a lot of applications out there who might be well off with relying only on Promises but you need to know when Promises aren't sufficient anymore and you need to level up. I intend to come up with a video on that topic. It is not easy one. Although I have very good connections to the RxAngular team, I still haven't tried it out. Shame on me 😅
@@RainerHahnekamp I know it sounds odd, but do you think it would make a difference to wrap the promise into an observable and embed it to the computed with toSignal()?
@@ShiftedBit I don't know, you could try it out if you want to. I've added the link to the repository. But as I said in the video, it must not concern you that you might miss the intermediate change where the holidays are emptied. If it does, that don't use a Signal for the holidays but an Observable. "The problem" is not the Promise, but the Signal itself which skips some values (glitch-free).
Thank you for the bombshell video Rainer! While these Signal APIs vastly simplify how components/directives React to Changes, I feel that we still have not arrived at a good pattern for the Initialization phase of components/directives using Signal APIs. Scheduling Initialization in constructor using the afterNextRender() seems to be the most sensible approach for me, with the added benefit of being SSR-friendly I also tried setting up Initialization using effect() and untracked() but in the end it felt a bit "unsafe" because effect execution is asynchronously dependent on Change Detection Scheduling, which we have very little control over. I am also concerned about when the executed effect is itself an asynchronous fetch, like in your demo (the async search function). ngOnInit, on the other hand does not have access to viewChild() and contentChild(), unless these queries are marked static. Perhaps you can provide more insight regarding the advantages/disadvantages of the above approaches in future videos, when best practices emerge!
Hey, it sounds to me like you are afraid of loosing the control over things? I don't see it that way to be honest. Yes, an effect runs asynchronously but if it triggers a side-effect which is also asynchronous (e.g. HTTP request), it doesn't change anything. I was never in control of an HTTP request either. Would it be possible to elaborate your issue a little bit? Can you think of a use case where the new API might be a problem? --- I would be careful when it comes to afterNextRender and SSR. It is not executed on the serve. So your rendered page might be missing crucial content. --- Cheers!
I would say it's the timing of the availability of various component signals during the initialization that causes me some confusion. Like if I tried to 1. read an input.required() signal, 2. then fetch some data in an effect(), 3. and then use that data to populate a 3rd-party chartJS node in the DOM using viewChild() => I'm not exactly sure where should I schedule this chain of behaviour, because input.required() cannot be read too early in the constructor (input not yet available error would be thrown), while effect() itself must be scheduled in an injection context (constructor or field declaration), when viewChild() result definitely is not yet available in constructor. With decorator based approach, I would say ngOnInit for reading @Input and fetch data, then afterViewInit for populating chartJS DOM node. I'll need to study that part of your video again to familiarize myself with these new timings. Thanks
@51:37 In the constructor, when using an effect and setting type to 'city', it knows not to update it because its the same value as it was before. What if instead of type being a string, it was an object? where we set a property of an object? Does it deep compare objects as well? or does it compare only the primitive types for detection?
Hey, nope for the deep compare. The signal does the equal check based on object reference. That's why a you need to do an immutable update of the state, i.e. clone the object. You can add an equals function, as second parameter of the computed function. Then you are in charge. That is something I haven't shown in the video.
Hi how do you feel about calling a private method in a computed signal that could hide a lot, i.e. what signal(s) the computed one depends on? I kind of try to read the signals value first and pass them as function parameters for better readability...
Hi, you would have to show me some kind of code so that I can get an understanding of your use case. In general, I would avoid calling methods inside a computed. You don't know what the private method is doing exactly. If the method calls another Signal you have an implicit/hidden tracking of that one. If your method starts an asynchronous task, then you are problay using it wrong and have to find further workarounds, which doesn't really improve the code. On the other side, if the computed is derived by a very complicated algorithm which you want to re-use outside of the computed as well, then yes. That makes sense. But please be free to share a code snippet. You might have a use case I haven't thought about so far.
I've subscribed. You're superb! Both in terms of explaining and choosing the content! Bottom right useful. Already shared your video with my Java circles. Wonderful work!!
Some time ago we had services as a 'store', then we went to ngRx, now we go to signal store - which is basically a service. So tell me where is the win? I mean withComputed = getter where you can specify some calc logic
Your example on toSignal() appears a simple solution, but in my experience, sometimes it is not appropriate to provide an arbitrary initial value, in which case signals don't work. I think this is an anti pattern. To me it seems more logical an architecture to wait for the real value from the database, rather than scattering around arbitrary starter values, which may have unexpected consequences (e.g. in effects that do calculations).
Well, I wouldn't say it is an anti-pattern. Especially not in the way how I used it. If it comes to HTTP Response, as you mentioned it, it is a little bit more complicated. First of all, a Signal forces us to provide a value. The alternative would be to have a union type with undefined. Sticking to an Observable is not really option if we consider that Angular is heading towards Signal Components. Another option is that you add metadata to that Signal which tells you, if you already have the response or if the signal's value is still the initial one. But again, with that approach you'll find yourself very quickly in a situation where you want to use a Signal-based state management library. So it starts to become more complicated although you might have a simple use case.
Hmmm, with anglar 18, adding @ngrx/signals fails with resolve dependency errors (both with ng add and npm install). Are we expecting a bump in the ngrx signals soon, or is there a workaround? does npm i --force work properly in this case?
Hi, would you know if I have a component A which extends a directive B. And in comp. a I am in injecting a service A with inject and inside of the constructor I am also calling super and injecting the service A. The question is, am I creating 2 instances os service A? Thanks in advance.
@@RainerHahnekamp see if that peace helps. private processStepPackService: ProcessStepPackService = inject(ProcessStepPackService); constructor(){ super ("ProcessStepPack", inject (ProcessStepPackService)); } In that case where I am injecting ProcessStepPackService twice. Would that be 2 instances or still just one?
If you mean the redux pattern of the globa store, then yes. If you say Signals will replace State management libraries then I would definitely say no. Because you have Signals and a structure that is suited for state management, you need a library on top of it. This is something which has already been confirmed by Angular itself. They said, the default signal function will not be enough as soon as your Signal contains larger values.
Hi Rainer. Brilliant video, thanks for taking the time! We usually cover DOM testing using Cypress E2Es. Do you think there is any advantage using DOM based tests in our unit tests too? We had been simply "newing" components before and supplying mocks in the constructor (without TestBed). Looks like we've have to move to TestBed in any case to use effects in the constructor.
Yeah, so independent that you don't have any other options to test effects, you need to ask yourself what benefits you get of non-DOM tests? I guess, you are directly calling methods of your TypeScript component then? If yes, here is an official statement from the Angular team on that topic: github.com/angular/angular/issues/54438#issuecomment-1971813177 Tests without DOM access are usually done against Services or heavy-logic based functions.
@@RainerHahnekamp Thanks Rainer! Yeah, we normally call the functions directly and test that the logic is correct. We do extract a lot of the logic into standalone (pure) util functions a lot of the time and test them separately. My thoughts were that DOM tests will test the angular wiring which is already tested by Cypress, but from your video I can see the benefit that you can refactor and the tests will still run (and still be meaningful) as they are abstracted a little from the nitty-gritty of the implementation. They are also a lot faster than e2e tests.
Effects don't see all value updates because the JavaScript event loop is single threaded and the effects are only executed after the current execution context has finished and the deferred event loop is drained. I'm pretty sure the only way that computed signals and effects automatically register their dependencies is that they record what signals are called during their execution; and more importantly this is only possible because JavaScript is single-threaded. I assume that there is a global register that is used to accumulate a list of called signals during the execution of an effect or computed and that is used to register the dependencies and listen for updates. This only works if only one effect or computed is able to be executed at a time.
You are absolutely right with your description on how computed and effects register themselves. I would describe the execution of an effect a little bit different though. It is when Angular decides that it is now time to start updating the DOM. So effects run asynchronously but if you have scheduled asynchronous task with a timeout, the the effect will run before them.
@@RainerHahnekamp If that is the case (setTimeout executes aftwards), then that means the effect is executed within the same callstack as the update to the dependency signal and does not use the JS event loop. The event loop processes queued tasks (aka messages) sequentially, in the order in which they are added to the queue. It is my understanding that there is no way to prioritize those tasks, other than using the delay argument of the setTimeout and setInterval methods.
@@sheariley1910 Angular itself is the one that triggers the effect when it does the change detection. So it is not like the effect is natively executed by the engine. You could also see it in a different way. The signals update their consumers immediately about the change. So the consumers always know that they are "dirty" but it is up to the framework when to actually tell them "to put their cards on the table". I've tried to explain it in my video with the animation where I used the term "caller". I might also quote from the official documentation, where they say the time when the effect is called is unspecified: github.com/angular/angular/tree/main/packages/core/primitives/signals#side-effects-createwatch
@RainerHahnekamp Angular is written in JavaScript and runs in the JavaScript environment. So I'm not sure what you mean by "the effect is not executed natively by the engine". Angular's signal framework code is either executing the dependent effects as part of the callstack initiated by an update to a dependency signal or it is queuing that execution as a deferred task which is processed by the event loop. Its one or the other. Those are the only 2 possibilities. So if what you are saying is true, then it has to be the former. This is fine. I was just making an observation. Also, this should make it easier to follow the stack trace when debugging and makes the whole thing more deterministic; both great things.
@@sheariley1910 I hope we don't misunderstand each other and talk about two different things. Angular has an own scheduler when the effect execution happens. When it comes down, it will of course be the JavaScript engine that runs it. You can find the scheduler here: github.com/angular/angular/blob/main/packages/core/src/render3/reactivity/effect.ts Little bit complicated but you get the idea.
Excellent video. Many thanks. Been using react for sevral years, with mobx for state management. Signals seem very close to how mobx works. What is your thought on using signals for application state as well?
Thanks, so representing state which changes over time is the primiary purpose of Signals. Or did you mean as an alternative to a full-blown state management library?
@@bellosthomas Ah, I see. So I don't think this is a good idea because you need a little bit more functionality. For example dealing with parts of the Signal, and improving updating them. Usually there is always some logic around Signals which you also want to have next to the Signal itself. In this case, a state management library which is built on top of Siganls does make sense. I would go with the Signal Store (I've actually made two topics about it, I'd recommend the second one as a starter). You might be interested in watching this Short as well, where Alex Rickabaugh, tech lead of the Angular framework, discusses the same question: ru-vid.comvgzC9xHedyw
@@RainerHahnekamp if am not mistaken ngrx is the redux counterpart of react. I am not a fan of redux so I try to avoid it. Will check the link. Many thanks
@@bellosthomasSo NgRx has the global store, the component store and now the signal Store. The global store is the one with the redux pattern. Signal and Component Store are without it.
A really nice demonstration. I have a question, If I have 2 signalStore for Customer and User for 2 different pages. I have a global loading bar, how can I apply isLoading on the global component
Hi, please take a look at my example. I have an http interceptor which sets the loading status in a service which is then read by the global component. If that doesn't answer your question, let me know and I can provide alternatives.
@@RainerHahnekamp I see that interceptor but sometimes we have calls that we don't want to show a loading bar. I have just come up with the idea of making a LoaderStore with isLoading property. Inject this store into withMethods of each Entity store where I want to show or hide the loading bar. This LoaderStore will be injected in LoaderComponent and bind isLoading into our HTML element. I am not sure if this is also the correct way to do it.
@@SonPham-zy2zpIf it is just about being able to exclude certain requests from not showing the loader spinner, please take a look at the HttpContext. Whenever you execute an HTTP request you have the option to add contextual data to the request which could be read by the interceptors. For example I use the context for a specific error message. You can find it at github.com/rainerhahnekamp/ngrx-signal-store-demo/blob/main/src/app/shared/http/error-message.context.ts. If you need more option, then going with a service as you did is definitely something I would also then. If it is just about a simple property, I think I would skip the Signal Store for that and just use a simple Signal.