Explore Angular signals in depth, and discover how the fundamental push & pull concepts explain every behavior—from assigning values to laziness and effect execution!
Tomas Trajan
@tomastrajan
Apr 11, 2023
10 min read
🤖 prompts & design by Tomas Trajan, gen by MidJouney
UPDATE 11th Dec 2023: Given the steady evolution and refinement of the signals internals, certain behaviors outlined here may no longer be entirely valid. For the most current picture, refer to my recent Angular Signals In-depth video on YouTube, where I explain the behavior of the newest Angular Signals release.
Angular Signals are currently all the rage, and for good cause!
Ever since the initial Angular release, we have been longing for a proper, officially supported approach to handling state in our apps—and that promise is finally here!
Add to that streamlined developer experience, better ergonomics, and signal-driven inputs, and even the most doubtful developers will concede that the Angular team has crafted something genuinely superior and impressive!
However, like any novel technology, there is inevitably a transition phase and some initial hurdles as we familiarize ourselves with these new APIs and build fresh mental frameworks for how signals operate in the real world!
A teaser
Everything kicked off with a small Angular Signals Quiz shared on Twitter…
🚦 #Angular Signals Quiz Time!
— Tomas Trajan (@tomastrajan) March 31, 2023
🧮 Computed edition
Given the following snippet,
how many times will the console.log
print the 'called'?
Bonus points for explaining why 😉 pic.twitter.com/FBWyPlKnXK
While the majority of respondents delivered the right answer in the comments, there was clearly some hesitation about why and how zero ends up being the proper result, given that we are explicitly setting the source signal's value several times…
A new piece of puzzle
Signals strongly echo the upheaval brought about by the adoption of RxJs-based APIs at the time Angular (2) first hit the scene.
When I was first getting to grips with RxJs, the best help came from this visual from the official RxJs documentation!
Organizing my fragmented, intuitive understanding of “how things operate” into a small set of well-defined concepts and their pairings made a real difference for me.
Looking back, this mental model turned out to be extremely useful as I dug into the new Angular signals and their behaviors, which are far from being always clear-cut or intuitive!
Let’s briefly walk through each concept, and after that, we’ll pinpoint where Angular signals actually fit into this framework.
- single / multi — straightforward: you get one value, or you get several (anywhere from zero to an infinite number)
- pull — you must actively “extract” the result by invoking the object
- push — the object will actively “send” a fresh result to you once it’s available, invoking a callback you supplied
Keeping this in mind, we start to see that
- function — you must invoke it (pulling a value out), and it returns a single outcome per invocation
- iterator — you must invoke it, and it returns multiple outcomes, one for every individual call (pull)
- promise — it triggers your callback as soon as the result is available (pushing the value to you)
- observable — it triggers your callback any number of times, from zero to n, whenever the next value arrives (pushing values to you)
What about signals, then?!
Let’s begin with what the official Angular Signals RFC had to say…
A signal acts as a wrapper around a value, with the ability to alert interested consumers whenever that value changes.
Up to this point, it resembles a push mechanism that can handle multiple notifications of potential changes…
Since reading a signal relies on a getter rather than accessing a plain variable or value, signals can keep track of the locations where they’re being read.
At the same time, “reading a signal (value) is done through a getter” strongly resembles a pull approach capable of being invoked multiple times…
Which leads us to…
Angular Signals operate on a push / pull reactive model!
That’s the headline, but what does it actually look like?
Plain signals
We’ll kick off by initializing the simplest signal we can write…
const count = signal(0);
The signal starts with 0 as its initial value, which is assigned to the count variable.
At this point, nothing has been triggered yet — so let’s go ahead and invoke it.
console.log(count()); // 0
The signal invocation retrieves its present value in a synchronous manner (pull), and you can invoke it without restriction (multiple). Every invocation delivers the signal’s value as it stands at that precise moment.
Now, consider this scenario…
const count = signal(0);
console.log(count()); // 0
count.set(1);
count.set(2);
From our earlier discussion, we know the following:
- when a signal’s value is updated—say, via the
setmethod—it broadcasts a push notification to every consumer, letting them know the value may have changed - signals maintain awareness of their read locations, meaning they track exactly which consumers depend on them
- in that earlier scenario, the
countsignal issues 2 push notifications for a possible update, but since we never subsequently read that value (the pull side), those notifications trigger no read or refresh operation whatsoever!
Signals within Angular templates
Now, let’s consider a more realistic case: an Angular component in action…
@Component({
template: `
<p>Count: {{ count() }}</p>
<button (click)="increment()">Increment</button>
`,
})
export class CounterComponent {
counter = signal(0);
increment() {
this.counter.update((current) => current + 1);
}
}
The component initially displays 0. When the user clicks the button twice, the component ends up showing 2—which works as expected, but how?
From what we've covered, setting or updating a signal's value dispatches a push notification indicating a potential change. Still, by itself, this does nothing—some consumer has to explicitly pull the latest value from the signal.
So how does the component know to pull the updated value, and why does it happen at the right moment?
It turns out that in this example, the signals themselves play no part in making this happen!
The pull of the signal's latest value occurs because the template bindings get re-executed. That re-run is triggered by Angular's conventional change detection, which the button click initiates with the help of
zone.js!
We'll see shortly that this behavior is set to change with Angular signals based components!
Angular signals eagerly push a notification to their registered consumers (those who have read the signal) that a value may have changed. However, nothing actually happens until the consumer pulls the value out via the signal's getter!
Follow me on Twitter because that way you will never miss new Angular, NgRx, RxJs and NX blog posts, news and other cool frontend stuff!😉
Computed Angular Signals
Let's now discuss the computed() signals, which are perfectly suited for implementing reactive derived state!
These signals are designed to replace and streamline logic that was previously handled by the ngOnChanges() lifecycle hook or by more sophisticated approaches like BehaviorSubject or ComponentStore patterns…
computed(() => {
return counter() % 2 === 0;
});
Whenever the computation (the function backing a computed signal) executes and reads another signal, Angular registers that signal as a dependency of the computed signal inside the reactive graph. Put differently, the counter signal takes on the role of a producer, while the computed signal acts as a consumer, and the edge between them records this relationship in the underlying data structure.
Now let's see what happens in a few typical situations…
For starters, let's apply two updates to the counter signal by calling .set(), for example counter.set(2).
Each such update triggers the *push** mechanism, yet the producer—the counter signal—doesn't deliver a fresh value. Instead, it sends merely a push notification flagging that something could have changed, and that notice goes only to the consumer, which is the computed signal.
As a result, the computed signal merely sets its state as stale, leaving the computation unexecuted for the moment.
Then did the computation actually execute at all?
Earlier, no variable captured the reference to the computed signal in our setup, so we never accessed it.
Consequently, although this computed signal exists inside the reactive dependency graph, it simply won't run—over its lifetime, it receives only those push alerts about possible updates.
Let's change the example by storing the computed signal inside a constant called isEven.
const isEven = computed(() => {
return counter() % 2 === 0;
});
Since the value hasn’t been read from the isEven computed signal yet—meaning no pull occurred—the behavior stays unchanged from before.
At this point, we finally need to call isEven() in some context, like an Angular component’s template. Following the pattern of the basic signal example from earlier:
- a user action (for instance, a button click) triggers the update to the
counterproducer signal - change detection through
zone.jsthen re-evaluates the component’s template bindings - the
isEven()signal gets invoked, verifying whether any push notification arrived since its last evaluation; if so, it re-executes its computation, which in turn pulls current values from every linked producer signal - the freshly computed value is ultimately rendered in the template
In short…
Computed signals in Angular are integrated into the reactive graph without delay, receiving immediate push alerts when their referenced producers might have changed!
Yet these computed signals defer running their calculation function until explicitly invoked, at which point they pull the freshest data from any potentially updated producer signals!
Reference Guide for Computed Angular Signals
- deferred execution
- any signal referenced within the computation function is automatically tracked as a producer
- producers issue eager push updates to the
computedconsumer, signaling that values may be outdated and require rechecking - the
computedsignal only runs upon an explicit call - recalculation happens solely when staleness is detected
- if stale, the
computedsignal pulls the latest values from its producers—this occurs just once, even after multiple notifications
Angular Signal Effects Explained
The effect() function is the most sophisticated part of the Angular signals API, enabling side-effects in response to changes in referenced signals.
Now, let’s examine how it works with a practical example.
@Component({
/* ... */
})
export class EffectExampleComponent {
constructor() {
const counter = signal(0);
effect(() => {
console.log('Effect runs with: ', counter());
});
}
}
This example deliberately wraps Angular component around the signal since
effect()has a deeper connection to the Angular core — most notably, it requires an injection context at constructor time because it leveragesDestroyRefinternally to handle its own disposal automatically.
Here, we initialize a counter signal with a starting value of 0, and we also register an effect that is intended to print the counter’s current value to the console whenever it is updated.
If we launch an Angular app with this exact component, what output would we see in the console?
In practice, the result is Effect runs with: 0 — this occurs because the effect’s status is flagged as dirty upon its creation, which triggers the very first pull of values from the producer signals that are referenced within the effect’s body.
Since effects currently execute in sync with Angular’s logic for updating a given view (and thus the change detection cycle), this initial run aligns with the parent component’s first render.
That reveals another important contrast with computed, which remains fully lazy in behavior and won’t run until it is invoked explicitly!
Let’s tweak our setup slightly to explore additional traits of Angular signal effects.
@Component({
/* ... */
})
export class EffectExampleComponent {
constructor() {
const counter = signal(0);
effect(() => {
console.log('Effect runs with: ', counter());
});
counter.set(1);
counter.set(2);
counter.update((current) => current + 1);
counter.update((current) => current + 1);
}
}
What happens in this scenario? In the component's constructor, we synchronously assign a new value to the counter signal through both the set and update methods.
As we discussed earlier, invoking these methods prompts the counter signal — acting as a producer — to push a series of notifications to the effect, which serves as the consumer. These notifications only signal that the value may be stale; they do not transmit the actual value.
Running this code in a live Angular app would print exactly once to the console:
Effect runs with: 4
Why? Because every producer update occurs before the effect's sole evaluation, since the effect starts out marked as dirty. That evaluation is triggered during the component's view refresh, which takes place only after the constructor has finished.
Angular signals effects receive eager push notifications when referenced signals may have changed, and *pull the values from those signals when Angular runs change detection
* soon we’re going to see that it’s a little but more nuanced
Time to introduce some user interaction and change detection, making the example more dynamic!
@Component({
template: `<button (click)="update()">Update</update>`,
})
export class EffectExampleComponent {
counter = signal(0);
constructor() {
effect(() => {
console.log('Effect runs with: ', this.counter());
});
// logs "Effect runs with: 0" when component is initialy rendered
}
update() {
this.counter.update((current) => current + 1);
this.counter.update((current) => current + 1);
this.counter.update((current) => current + 1);
}
}
When the user hits the “Update” button, what occurs then?
From what we’ve established, updating a signal emits a synchronous push notification to its consumers—in our scenario, that’s the effect—signaling that the value may have changed.
Furthermore, in a zone.js-driven Angular app, any DOM event, including the (click), triggers a full application-wide change detection pass. That causes the effect’s implementation function to execute again, and during that re-run, it will pull the current value directly from the signal.
Consequently, after the initial user click, we’d observe a single log entry: Effect runs with: 3.
The behavior mirrors the effect updates we saw earlier in the constructor, so where does the nuanced distinction we hinted at come into play?
Angular Signals Effects are Push -> Poll -> Pull
Let’s tweak our example once more. This time, we add a computed signal as an intermediate node inside our reactive dependency graph.
Now, the effect’s dependency is the computed signal, which itself relies on the underlying counter signal…
@Component({
template: `<button (click)="update()">Update</update>`,
})
export class EffectExampleComponent {
counter = signal(0);
constructor() {
const isEven = computed(() => {
return this.counter() % 2 === 0;
});
effect(() => {
console.log('Effect runs with: ', isEven());
});
// logs "Effect runs with: true" when component is initialy rendered
}
update() {
this.counter.update((current) => current + 2); // notice + 2
}
}
As before, the initial value of true gets logged because the starting counter value of zero yields an even number, and the effect is flagged as dirty upon its creation…
But what unfolds when the user triggers the update button?
- the
countersignal dispatches a push alert to its consumer, theisEvencomputed signal, which in turn relays that alert to its own consumer, theeffect - the click also activates
zone.js-powered change detection, causing a re-execution of the effect (during therefreshViewinvocation) - when the effect kicks off, it does so because it had been flagged as dirty due to the incoming push notification
- the effect then polls its producer, the
isEvencomputed signal, and discovers that theisEvenvalue has actually stayed the same! - it terminates its run, avoids pulling the latest
isEvenvalue, and prints nothing to the console! - every subsequent click repeats this pattern (0 + 2 = 2, plus 2 = 4, and so on—all even, so the
isEvencomputed signal value never changes further)
Angular Signal Effect Cheat Sheet
- effects begin in a dirty state and execute at least once (assuming typical scenarios where the parent component undergoes change detection on creation)
- currently, effects are scheduled to run when Angular performs change detection and refreshes a component's view, ensuring they operate with the freshest values of referenced signals, even if multiple synchronous updates occurred earlier
- an executing effect polls its producer signals to confirm a value change before pulling and re-running; for base signals, the push notification always means a change, so the poll always succeeds, but for computeds, a push notification can still yield the same value, leading to a failed poll and no re-run
- effects support cleanup or cancellation of ongoing operations via the
onCleanupargument supplied to the effect function - the effect itself is automatically disposed of when its parent component or service is destroyed (
DestroyRef)
It's evident that Angular signals effects bring certain behaviors that may not be immediately intuitive. To make this less abstract, I've put together a StackBlitz example that demonstrates these traits in a live Angular app!
Angular Signals Components
Throughout this article, all examples relied on zone.js-driven change detection and its implications. If you've followed the Angular Signals RFC, you'll recall that Angular plans to introduce signal-based components using the signals: true flag (much like the standalone: true flag).
Opting into a signal-based component switches it to signal-driven change detection, eliminating zones, so change detection fires whenever a signal used in that component's template receives a push alert indicating a possible change.
This mechanism will likely rely on the effect itself (or something closely resembling the signal effect) and mirror the push -> poll -> pull sequence outlined above!
There are numerous outstanding resources to deepen your understanding of Angular signals effects, particularly this presentation by Angular core team member Pawel from NG-BE 2023, so be sure to take a look!
Angular Signals are awesome!
I trust you've gained insight into the push & pull dynamics of Angular signals and will now approach integrating signals into our Angular projects with ease and confidence!
This new knowledge will help clarify why signals behave as they do, covering everything from setting values to effects and laziness!
Feel free to reach out with any questions via the article comments or Twitter DMs @tomastrajan
Is the visual style of the code snippet to your liking? Dive into our freshly launched theme plugin
Skol - the definitive theme for your development environment
Bring the aurora borealis vibe into your editor. A lightweight yet striking dark theme that performs beautifully and soothes your eyes.
Craft smarter interfaces with Angular + AI
Angular + AI Video Course
This practical course walks you through embedding AI directly in your Angular applications, leveraging Hash Brown to craft responsive, intelligent user interfaces.
Progress step by step through streaming chat, tool invocation, generative UI components, structured data extraction, and more.
Looking for a concrete walkthrough of Angular Signal Forms structure, validation, and transition strategies?
The Angular Signal Forms Guide
With a model-first approach, you can build Angular forms that are fully typed, validated, and ready for production—all powered by signals.
The course covers schema-driven validation, form-state signals, custom controls, migrating from Reactive Forms, and clean API mapping patterns.
Enjoying this content? Want to master Angular's cutting-edge Signal Forms?
Angular Signal Forms: A Practical, Interactive Workshop
Angular's newly introduced Signal-Forms are unpacked across a dozen sequential chapters, blending conceptual explanations with practical exercises.
Gain proficiency in core form concepts, validation rules, bespoke controls, nested subforms, and approachable migration techniques.
Stay in the loop
with fresh articles
Join the Angular Experts Content Updates & News list, and we'll let you know the moment a new post lands on Angular, Ngrx, RxJs, or other exciting Frontend subjects!
Your email stays private—no sharing with third parties, and unsubscribing is a breeze whenever you'd like!
Share your thoughts & feedback
Feel free to raise any questions, offer your own insights, or contribute your viewpoint on the subject matter
Tomas Trajan
Google Developer Expert (GDE)
for Angular & Web Technologies
Through training and consulting, I assist developer teams in shipping successful Angular applications, with a strong focus on Architecture and State management using NgRx!
Working as an Angular trainer and consultant, I hold the title of Google Developer Expert for Angular & Web Technologies. My current mission is empowering enterprise teams worldwide through the implementation of core features and architecture, the introduction of best practices, knowledge sharing, and workflow optimization.
Tomas is dedicated to delivering outstanding value to both clients and the broader developer community. His efforts are backed by an impressive portfolio of widely-read industry publications, talks at global conferences and meetups, and contributions to open-source initiatives.
52
Blog posts
4.7M
Blog views
3.5K
Github stars
612
Trained developers
39
Given talks
8
Capacity to eat another cake
You might also like
Browse these additional posts from Angular Experts to go deeper into related subjects like Modern Angular or Signals !

Angular Signal Forms: Custom Controls Without ControlValueAccessor
Build reusable Angular custom controls with FormValueControl, model(), touch events, and schema-driven validation—without writing a ControlValueAccessor.

Kevin Kreuzer
@nivekcode
Aug 12, 2026
7 min read

Angular Signal Forms: The Missing Create/Edit Pattern
Learn a practical Angular Signal Forms pattern for create and edit flows, with route-based mode, edit data loading, linkedSignal prefilling, submit branching, and validation context.

Kevin Kreuzer
@nivekcode
Aug 1, 2026
6 min read

Angular Signal Forms Essentials
Understand the core concepts behind modern Angular Forms. Learn how to create Signal Forms, wire them up in templates, use built-in and custom validators, handle cross-field validation, submit forms, and more.

Kevin Kreuzer
@nivekcode
Feb 14, 2026
12 min read
Empower your team with our extensive experience
Angular Experts have spent many years consulting with enterprises and startups alike, leading workshops and tutorials, and maintaining rich open source resources. We take great pride in our experience in modern front-end and would be thrilled to help your business boom
