Replies: 75 comments 329 replies
-
For input decorators, why, not this syntax:
It defines an input signal; you do not need different keywords/functions. |
Beta Was this translation helpful? Give feedback.
-
I don't think I'll ever get used to this:
If options could always be the 2nd argument, instead of this:
It could be
The Actually, if you don't define an initialValue, what value will the template initially receive? Will it be Also, is there anything preventing me from wrapping |
Beta Was this translation helpful? Give feedback.
-
Food for thought regarding discussion point 3D. Given that passing options should not be frequent (in my experience, aliasing inputs is not used often, and I haven't seen any other option described here), an fluent API could be used
or even
Pros:
Cons:
|
Beta Was this translation helpful? Give feedback.
-
This is probably outside the scope of this RFC, but: given the new function APIs would also be analyzed at compilation time, could a similar (or identical) API be introduced for non-Signal-based Components? (Replacing decorators) |
Beta Was this translation helpful? Give feedback.
-
Are there other alternatives being considered for |
Beta Was this translation helpful? Give feedback.
-
Will @HostBinding work in pair with input? Example: |
Beta Was this translation helpful? Give feedback.
-
It's quite surprising to see that we'll need
It should be defined by a changeDetection strategy then, not by the
Looks like it only changes the lifecycle and change detection.
Please leave them readonly. It's a very important and right change.
Sorry for the strong word, but it's awful.
It is such a weak argument. You are breaking compatibility just for this? That's depressing... Yeah, it's the most frustrating sub-RFC, sorry that I can not stay positive - I still hope my opinion will somehow help... |
Beta Was this translation helpful? Give feedback.
-
Are there reasons |
Beta Was this translation helpful? Give feedback.
-
If inputs are read-only signals, how can we set their value programmatically
Would it be possible to inject DirectiveRef, which would wrap the directive instance and allow setting it's inputs? Speaking of directive composition, can a zone-based directive host a signal-based one, and vice-versa? And regarding queries, is the ElementRef still necessary? |
Beta Was this translation helpful? Give feedback.
-
What happens if lifecycle hooks are called outside the constructor? Will they throw an error if there is no injection context? Will the injection context be provided in methods when called as output or DOM event handlers? |
Beta Was this translation helpful? Give feedback.
-
What happens if a signal-based component implements old method lifecycle hooks (e.g. will there be a compilation error? |
Beta Was this translation helpful? Give feedback.
-
Concerning
I am a bit petrified 😅 How can we name this to let people know that Also, we might want to opt-out from all DOM updates and just focus on the component itself... how can we achieve this without |
Beta Was this translation helpful? Give feedback.
-
My gut reaction was that the initialValue thing was gross, as others have mentioned, but after playing with it for a bit I think it's fine, even preferable. 90% of the time that I'm dealing with objects I don't have a default/initial object anyway, and this syntax is cleaner in that common case, and it's cleaner for required primitive inputs with no initial value, too. I know the components team uses @Component.host over @HostThing, but I'm surprised that this RFC doesn't propose hostBinding/hostListener functions. I think it should at least be updated to clarify that the guidance is to use @Component.host instead, I had to double check the RFC to make sure I hadn't missed something. Finally, I'm confused by the timing of the rendering effects. They run after any DOM has been updated, not just the component's DOM? I think some examples would be helpful, I'm struggling to understand when I'd want to respond to other potentially unrelated components being rendered. |
Beta Was this translation helpful? Give feedback.
-
The rxjs RFC didn't answer this, so here goes. The way I use observables goes like this
If myinput is a signal input, how would that look? I suppose a default value would be required. |
Beta Was this translation helpful? Give feedback.
-
This is such a radically different way of writing components, why not just go full hog and make signal-based components as functional components? Is there any real benefit to classes at this point? Makes it easier to distinguish zone-based components from signal-based components too. export const MyComponent = defineComponent({
selector: 'my-component',
setup() {
const count = input(0)
return {
count
}
},
template: `
<div>{{ count() }}</div>
`
}) |
Beta Was this translation helpful? Give feedback.
-
Why does Angular do a re-check on all bindings? I would have expected that it only checks the binding of the changed signal. Regarding DX my first thought was to prohibit binding to non-signal properties. It would be clear and hence quick to learn. But maybe it could also be an option to provide a mutate call for the component where we can change the component's properties and have Angular pick up on it, similar to Signal.mutate. Just a thought.
Yes, because it's not backward compatible and hence could lead to a lot of confusion. Also, upgrading existing components with zone-based two-way binding to signals would lead to a breaking change to consumers. And it could lead to conflicts when having a model "property" and an output "propertyChange".
I agree with your reasoning. It will be a pain to upgrade but worth it for new components. |
Beta Was this translation helpful? Give feedback.
-
People are concerned to have multiple ways of doing the same thing. But this is unavoidable for a large project, such as Angular, to remain usable. For example, it took Java 3 interactions to implement a decent date and time API. In Java 1.0 there was |
Beta Was this translation helpful? Give feedback.
-
i think application life hooks should be same pattern like interface onInit,angular is classs code ,not function,i dont like function hook apis |
Beta Was this translation helpful? Give feedback.
-
I still cannot wrap my head around how signals run the new change detection mechanism to re render the template. |
Beta Was this translation helpful? Give feedback.
-
After downloading the recently released 16.0.0-rc.0 version, but I found out that |
Beta Was this translation helpful? Give feedback.
-
Signal-based inputs
isLoggedIn = input(false, { mutable: true });
@Component({
signals: true,
selector: 'some-checkbox',
template: `
<p>Checked: {{ checked() }}</p>
<button (click)="toggle()">Toggle</button>
`,
})
export class SomeCheckbox {
checked = input(false);
}
@Component({
signals: true,
selector: 'some-page',
template: `
<some-checkbox [checked]="isAdmin" />
`,
})
export class SomePage {
isAdmin = signal(false);
} |
Beta Was this translation helpful? Give feedback.
-
model() is enough, no more magic is needed. |
Beta Was this translation helpful? Give feedback.
-
Perhaps this has been discussed elsewhere already, but is there any way we can coerce the signal inputs? For example, currently we can do the following: import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';
...
@Input() set selected(value: boolean) {
this._selected = coerceBooleanProperty(value);
}
get selected(): boolean {
return this._selected;
}
private _selected = false;
static ngAcceptInputType_selected: BooleanInput; This enables us to support static values in our templates: <some-cmp selected="true" /> Are there any plans to provide support for this in signal-based components? Maybe the input function could incorporate an option? selected = input(false, { transform: coerceBooleanProperty }); |
Beta Was this translation helpful? Give feedback.
-
If Angular idea is to have function input and output. Does this open the possibility for developers to create they own input/output logic. The current system with decorators does support any form of wrapping or custom solution, due to ngtc checking against Angular/core import system. I'm not saying I want to do this or anything, but previously I did want to try custom solution/poc of proxy or signal in version 15 |
Beta Was this translation helpful? Give feedback.
-
New
|
Beta Was this translation helpful? Give feedback.
-
Something I've already missed with the change detection policy (we use OnPush for all components), and is even more boilerplate-y now for both standalone and signals: Why not having a project default in Not to mention that this would ease the path for new Angular developers, as the defaults for a newly generated project could be to use standalone and signals components. This way, new devs wouldn't have to understand why they need to explicitly set something for every component, to avoid the non-recommended alternative. |
Beta Was this translation helpful? Give feedback.
-
Can we use non-pure pipes in Signal components?Say we have a pipe which is impure, and relies on a Change Detector Ref to propagate updates. Can we still use this pipe in signal components? |
Beta Was this translation helpful? Give feedback.
-
@HyperLife1119 mentioned something interesting:
See the comment here: #49682 (reply in thread) @SignalComponent, @SignalDirectiveI think that could be a great idea because:
Possible downsides?
|
Beta Was this translation helpful? Give feedback.
-
Why would you need a |
Beta Was this translation helpful? Give feedback.
-
Sub-RFC 3: Signal-based Components
Changelog
April 10, 2023
effect
s and change detection, explaining the timing of effects relative to the change detection process.afterRender
& other functions describing them as application hooks, not component lifecycle hooks.ngOnInit
andngOnDestroy
only, and other hooks replaced by signal functionality.Introduction
This discussion covers the API of signal-based components.
Note: this document applies to both components and directives. However, repeating "components and directives" is rather cumbersome, so for brevity we just say "components" everywhere.
Signal-based components
Components are the primary building block of an Angular application. As such, they make a natural boundary for participation in the signal-based reactivity model.
You can mark a component as a signal-based component by setting
signals: true
in the component metadata:This setting controls a number of framework behaviors outlined in the rest of this document.
We'll refer to components with
signals: false
as zone-based components.You can use both signal-based components and zone-based components in a single application.
Signal-based components cannot extend zone-based components or the other way around.
_Note: we know setting both
signals: true
andstandalone: true
is kind of boilerplate-y. We're still thinking of ways to make this more concise.Using a signal in a component
Let's take a look at a counter component that uses signals:
You always call the signal getter to retrieve the signal's value.
Isn't calling a function in a template slow?
Angular developers have learned over the years to avoid calling functions inside templates because the function re-runs on every change detection. This idea no longer applies in a signal-based component because the expressions will only re-evaluate as a result of a signal dependency changing.
Could Angular automatically "unwrap" signal values?
You might be wondering if Angular instead could support binding to a signal directly, implicitly reading the signal's value. In the example above, that would change the binding to
count
to<p>{{count}}</p>
. This would be similar to the way Vue or Preact automatically unwrap thevalue
in templates.Angular cannot automatically unwrap signal values like this for a couple of reasons:
Here, Angular cannot statically know (at compile-time) that the template expression accesses a signal getter.
Further, explicitly calling the signal getter in the template makes reading values consistent inside and outside of templates.
We know that this is a departure from Angular today that may feel strange for some people, but we've found in our early user studies that developers internalize the concept and it becomes familiar relatively quickly.
Reading non-signal values in templates
Let's look at an example that mixes use of signal and non-signal values in a template:
This example includes both binding to a signal value (
count()
) and a plain value (name
). Let's say that thename
and the value ofcount
were both to change. You might expect that only the changes tocount()
would be reflected in the rendered DOM, but in this scenario Angular would update both values in the rendered DOM. This happens because Angular still re-checks every binding within the component when one or more signal dependencies change. Only a change to thecount
would trigger that change detection, however.This behavior can be surprising- in a signal-based component, you might expect that only bindings that read from signals should ever update.
In the signal-based component world, this behavior is a symptom of not embracing the central principle - dynamic state (particularly state referenced from a template) must be tracked within a signal.
We have some ideas on how to help guard against this behavior:
readonly
See "Granularity of change detection" for background on this behavior.
Discussion point 3A: We're actively looking for feedback on the developer experience of this behavior.
Computed properties
You can define computed properties similar to how you define writable signals. Let's look at an example of component that shows a Fahrenheit temperature from a Celsius source:
Computed properties can simplify your code and reduce unnecessary recomputation by replacing:
Computed properties are a longstanding feature request for Angular.
Signal-based inputs
In signal-based components, all component inputs are signals. Let's look at an example of declaring some signal-based inputs:
Here, the
input
function replaces the@Input
decorator as the means of declaring a component input.input
accepts two parameters:signal
, andinput
returns a read-onlySignal
. Reading the value from an input always returns the up-to-date bound value.To a consumer of the component, signal-based inputs are indistinguishable from non-signal inputs, using the same syntax for binding. Whether or not an input is signal-based is an implementation detail of the receiving component.
Model inputs
Signal-based components additionally have access to a new type of input, model inputs.
The
model
function defines a special kind of input that establishes a contract between parent component and child component. A model input gives you aWritableSignal
, which propagates its value back to the source. This lets you create two-way bindings without any additional requirements.When using two-way binding, the binding accepts a
WritableSignal
reference and not the unwrapped signal value. By passing the signal reference instead of the value, you're explicitly opting into the contract that the child component can write values into your signal. We see this pattern as additionally illustrating, by contrast, the value of explicitly invoking the signal getting for one-way bindings.One-way binding behavior
You can create one-way bindings on
model
inputs just like the standardinput
. Modifying theexample above:
In this situation, the
SomeCheckBox
component declares a model calledchecked
and can still change its values
, but the values it writes will not propagate back to the parent (SomePage
). If the bound value from the parent component (SomePage
) changes, it will overwrite the model's value. Themodel
value (checked
) will always reflect the most recently set value. This matches the current behavior of Angular inputs.Two-way-binding syntax
This RFC uses Angular's existing banana-in-a-box syntax,
[(propertyName)]
syntax for two-way bindings. This syntax originally came about as a shorthand for both a property binding and an event binding. For example, the following two are equivalent:However, in signal-based components, this original thinking doesn't exactly apply; the two-way binding does not literally map to an event handler. Instead, the value propagates back up when the component sets a value into the
model
property. Because of this, it might make sense to use an alternate syntax for two-way binding in signal-based components.Discussion point 3B: Should signal-based components introduce a new syntax for two-way bindings?
Inputs are read-only
In signal-based components, inputs are read-only inside the component that receives them. This is a departure from Angular's current approach, where inputs are mutable everywhere. We believe that making inputs read-only will lead to cleaner, easier-to-follow code. The input signal's value always reflects the most recent value bound into the component from outside.
When adapting existing Angular components to a signals world,
model
provides a way to preserve the original behavior.Discussion point 3C: We recognize that making inputs read-only is a significant change that may make adopting signals more difficult for existing projects. While we believe that this decision produces the best outcome, it may be too different. We want your feedback on how disruptive this change would be.
Input configuration
Inputs take an options object, which allows specifying the initial value of the input when not bound, the alias, whether the input is required, etc.
Many inputs have primitive values (
boolean
,number
,string
, etc). There is an alternate syntax available which uses the first argument for the initial value:These are equivalent to the longer forms which use the
initialValue
option:Discussion point 3D: We're interested in hearing opinions on the developer experience of this API.
Why no decorator?
See "Decorators in signal-based components" below.
input
andmodel
have special meaning to AngularSee "Special functions in signal-based APIs" below.
Signal-based queries
Angular queries include the APIs
ViewChild
,ContentChild
,ViewChildren
, andContentChildren
.In signal-based components, all queries produce signals. Let's look at an example of declaring some signal-based queries:
Queries work the same way as before in signal-based components with one exception: they always return a
Signal
. This applies to both singular (child
) and plural (children
) queries. This has the side effect of eliminating theQueryList
API in signal-based components.Why no decorator?
See "Decorators in signal-based components" below.
Query functions have special meaning to Angular
See "Special functions in signal-based APIs" below.
Output in signal-based components
Adopting signals as a reactivity primitive in Angular does not inherently affect component outputs. However, we believe that maintaining consistency between the API for inputs and outputs is important. Let's look at the proposed output API for signal-based components:
Aside from the change in declaration API, output behavior remains unchanged.
Why no decorator?
See "Decorators in signal-based components" below.
output
has special meaning to AngularSee "Special functions in signal-based APIs" below.
effect
s and Change Detectioneffect
s allow for the execution of side effects when dependent signals change. Although effects are usable throughout the application, their close coupling with the change detection operation has several implications on component & directive authoring.Effects wait for inputs to be set
When an effect is created during the construction of a component, that effect will not execute until that component's inputs are available. This guarantee means that effects can be written which depend on component inputs without having to guard against early execution.
Effects run during zone change detection
As Angular is traversing the component tree during change detection, any effects which are queued will be eagerly flushed.
Effects can become dirty during change detection if they observe a signal input which is bound in a zone component. That input isn't change detected until the parent zone component is processed, making the effect dirty.
It's important that the effect is flushed eagerly, as it might affect future rendering of that component. For example it might create or destroy views (such as a reactive form of
NgIf
might do).Risk of
ExpressionChangedAfterItHasBeenChecked
errorsBecause effects flush during change detection, they behave similarly to other lifecycle hooks (e.g.
ngOnInit
) with respect to application data flow. That is, using an effect which reads component inputs and further modifies application state may result inExpressionChangedAfterItHasBeenChecked
errors in other parts of the application, if the modified state is read in components which have already been change detected.Therefore, caution should be used when authoring effects which set other signals and change application state. This includes converting signals to Observables via
toObservable
, as this exposes the effect timing via the Observable.This issue only exists when interleaving signal and zone components. In a fully signal-based application, effects cannot become dirty during change detection, eliminating this risk.
Application rendering lifecycle
To supplement signal components, we're introducing three new application-level lifecycle hooks focused on running code after Angular performs any rendering operations. These are:
afterNextRender
afterNextRender
schedules a function to execute after the next rendering operation (that is, change detection cycle) is complete. This is useful whenever you want to read or write from the DOM manually.Delaying DOM reads until after the framework has finished writing to the DOM during rendering is essential to avoiding unnecessary reflows.
You can use
afterNextRender
in any injection context, including components, directives, or services.afterRender
afterRender
schedules a function to be executed after any time the framework performs DOM updates during rendering.You can use
afterRender
in any injection context, including components, directives, or services.afterRenderEffect
afterRenderEffect
is a special kind ofeffect
which, when triggered, executes withafterRender
timing.Component lifecycle and effects
Zone-based components support eight different lifecycle methods that let you hook into different stages of Angular's change detection. Because many of these methods are tightly coupled to Angular current change detection model, they don't make sense for signal-based components.
Signal-based components will retain the following lifecycle methods:
ngOnInit
ngOnDestroy
The remaining lifecycle methods will not be available in signal components. For all of these methods, signals provide new patterns to achieve the same use cases:
ngOnChanges
- is used to observe changes to inputs. As inputs are signal-based,computed
can be used to derive new values, oreffect
to react side-effectfully.ngDoCheck
- typically this hook was used to implement custom change detection.effect
is a likely (and more performant) replacement.ngAfterViewInit
is often used to perform some action after initial rendering.afterNextRender
can be used instead (and is more correct).ngAfterContentInit
,ngAfterViewChecked
andngAfterContentChecked
are often used to observe query results. Since queries are also signal-based and therefore reactive by default, signals can be used directly.Decorators in signal-based components
You might have noticed a pattern in the APIs presented in this RFC: signal-based components don't use any member decorators.
Decorators have long been a hallmark of Angular's authoring experience. For inputs and queries inside signal-based components, we believe that the proposed function-based APIs produce the best overall developer experience.
Let's use
input
to illustrate our thought process.Each input needs to create a
Signal
instance to contain its value at construction time. Imagine a theoretical API where this happens with decorators:We find this API unappealing because it introduces significantly more boilerplate for declaring inputs. You might ask "Can Angular automatically create the signal behind the scenes?" Let's imagine another such API:
We find this API unappealing because:
Signal
type.This leads us back to the proposed API:
We find this API appealing because:
strictPropertyInitialization
setting.Above we discussed
@Input
,@Output
, and all the query decorators. This leaves@HostBinding
and@HostListener
, which we further propose dropping in signal-based components. Becauseinput
,output
, and the query functions provide a much cleaner, more concise API, dropping@HostBinding
and@HostListener
results in a consistent authoring experience.Special functions in signal-based APIs
Today, Angular processes decorators like
@Input
at compile-time. Your run-time JavaScript bundle does not actually include any decorators. The new signal-based function APIs behave the same way- Angular will processinput
,output
,viewChild
, etc. at compile-time to statically understand the declarations.This means that you can't use these APIs as typical functions throughout your code. For example, you cannot create a custom
booleanFlag
that callsinput
:Instead, you can exclusively use these APIs as part of a component property initializer. Angular will report an error if these functions are used in any other context.
Why not
ChangeDetectionStrategy.Signals
?Longtime Angular developers might ask here, "Why not
ChangeDetectionStrategy.Signals
?" We considered this option, but decided on a separate API for two reasons:changeDetection
setting. Adding this setting to directives would be confusing, since directives participate in change detection based on the view in which they're contained. However, directives are affected by other aspects of participation in the signal reactivity system.Signal-based components completely drop the
changeDetection
setting.Will there be lint warnings or compiler checks for common anti-patterns with signals?
Yes! We're still exploring what kinds of checks we'll be able to implement, but some early ideas include:
readonly
(the value inside the signals should change, not the identity of the signal itself)computed
computation functionsBeta Was this translation helpful? Give feedback.
All reactions