preamble
Angular 19 is expected to be released in mid-November, and the latest version is currently (2024-10-27) v19.0.0-next.11.
The changes in v19 are not small, there are a lot of new features, even the effect is breaking changes 🙄.
I guess the Angular team will be bragging as usual this time... Looking forward to it 🙄!
Although there are new features, but we should not expect too much, after all, Angular these years is to simplify the wind, most of the new features are just the upper layer of encapsulation, lower the threshold of the beginner only.
For the older users, still scoffing 😏
However, there is one thing to be happy about. With this release, we can confirm one thing -- Angular has not been abandoned by Google.
Therefore, you can learn with peace of mind.
This article will introduce the new features of v19 one by one, but will not cover all the features.
I will only talk about the topics I have taught, but not the ones I haven't (e.g., SSR, Unit Testing, Image Optimization), so if you're interested in this section, please refer to the official website.
Okay, without further ado, let's get started 🚀
Input with undefined initialValue
It's a very, very small improvement.
v18 We will report an error if we write it this way.
export class HelloWorldComponent { readonly age = input<number>(undefined, { alias: 'myAge' }); }
We must explicitly indicate the type of undefined to pass, like so
readonly age = input<number | undefined>(undefined, { alias: 'myAge' });
By v19 it won't be necessary.
The principle is simple: Angular adds an overloaded method to input...
Reference:Github – allow passing undefined without needing to include it in the type argument of input
Use "typeof" syntax in Template
It's a small improvement.
v18 In Template, this is an error.
@if (typeof value() === 'string') { <h1>is string value : {{ value() }}</h1> }
Angular does not recognize the "typeof" syntax.
We can only rely on components to do the type determination
export class AppComponent { readonly value = signal<string | number>('string value'); isString(value: string | number): value is string { return typeof value === 'string'; } }
App Template
@if (isString(value())) { <h1>is string value : {{ value() }}</h1> }
By v19 it won't be necessary.
@if (typeof value() === 'string') { <h1>is string value : {{ value() }}</h1> }
Just write it, and the compiler won't report any more errors because Angular already recognizes the "typeof" syntax 😊.
From v18.1@letIn addition to the typeof in v19, you can see that Angular's direction is to make Templates Razor (HTML + C#).
Why not JSX? Because JSX is JS + HTML, not HTML + JS, the concept is different, React level is much higher.
But in any case, it's always a good direction to make Templates more flexible, and it's up to the user to assign responsibilities rather than being tied down by a framework.
Reference:Github – add support for the typeof keyword in template expressions
provideAppInitializer
existAngular Lifecycle Hooks In the article, we learnedAPP_INITIALIZER。
It looks like this.
export const appConfig: ApplicationConfig = { providers: [ provideExperimentalZonelessChangeDetection(), { provide: APP_INITIALIZER, multi: true, // Remember to set it to true, otherwise it will overwrite the registration of other modules. useValue: () => { ('do something before bootstrap App subassemblies'); return (); }, }, ] };
Angular doesn't want us to define the provider directly like above, it wants us to wrap a layer of methods so that it looks functional.
So, after v19, it becomes like this.
export const appConfig: ApplicationConfig = { providers: [ provideExperimentalZonelessChangeDetection(), provideAppInitializer(() => { ('do something before bootstrap App subassemblies'); return (); }) ] };
Very nice 😊, the multi: true detail is encapsulated so we don't need to worry about forgetting to set true anymore.
provideAppInitializer'ssource code (computing)Like this.
Nothing special, it's really just a wrapper.
There is a small knowledge point: v18 If useValue is used, the inject function is not allowed inside the initializerFn, to use the inject function, you must use useFactory instead of useValue.
v19 Some changes have been made to this section. provideAppInitializer uses useValue, but the inject function can be used within initializerFn.
The reason for this is that it wraps a layer of injection context before executing, the source code for which is available in theapplication_init.ts
Reference:Github – add syntactic sugar for initializers
Use fetch as default HttpClient
existAngular HttpClient In the article, we learned that Angular has two ways to send http requests.
One of them is to useXMLHttpRequest (the default), and the other with theFetch。
v19 Changed the default to Fetch.
The biggest problem with Fetch is that itunsupportedUpload progress.
If the project requires it, we can configure it to use XMLHttpRequest via the withXhr function.
export const appConfig: ApplicationConfig = {
providers: [
provideExperimentalZonelessChangeDetection(),
provideHttpClient(withXhr())
]
};
Note: v19.0.0-next.11 is still XMLHttpRequest by default, and withXhr can't be used yet, maybe v19 official version will have it, or we have to wait for v20.
Reference:Github – Use the Fetch backend by default
New effect Execution Timing (breaking changes)
After v19.effect With the new execution timing, it's a no-brainer breaking changes.
After the upgrade, your project is likely to have some odd conditions that will leave you looking for a way out... 🤭
However, the effect is in preview, so the Angular team doesn't consider it a breadking change! Since the effect is in preview, the Angular team doesn't recognize it as breadking changes, and it's only your fault for listening to their rhetoric and being the first to get shot... 🤭
recalls that v18 effect execution timing
The v18 effect has an important concept called microtask.
Whenever we call effect, our callback function doesn't get executed right away; effect saves the callback first (the term is schedule).
Then wait for an async microtask (queueMicrotask) before it is executed (termed flush).
export const appConfig: ApplicationConfig = { providers: [ provideExperimentalZonelessChangeDetection(), provideAnimations(), { provide: APP_INITIALIZER, useFactory: () => { const firstName = signal('Derrick'); queueMicrotask(() => ('Run first 2')); //Run first. 2. effect(() => ('Back Run 3', firstName())); // After running 3, the App component has not been instantiated yet! ('Run first 1'); // Run first. 1. }, }, ], };
When the signal changes, the callback also waits for an async microtask before executing (flush).
In addition to the microtask concept, there are two types of effects.
One is called the root effect and the other is called the view effect.
As the name suggests, root refers to an effect that is called outside of the view, such as APP_INITIALIZER in the example above.
A view effect is an effect that is called from within the component (more rigorously: an effect depends on the Injector, and if the Injector can be injected into the ChangeDetectorRef, then it is a view effect).
The timing of the view effect is similar to that of the root effect.
export class AppComponent implements OnInit, AfterViewInit { readonly v1 = signal('v1'); readonly injector = inject(Injector); constructor() { queueMicrotask(() => ('microtask')); // Back Run 2 effect(() => ('constructor', this.v1())); // Running back 3 afterNextRender(() => { effect(() => ('afterNextRender', this.v1()), { injector: this.injector }); // Back Run 6 ('afterNextRender done'); // Run first. 1. }); } ngOnInit() { effect(() => ('ngOnInit', this.v1()), { injector: this.injector }); // Running back 4 } ngAfterViewInit() { effect(() => ('ngAfterViewInit', this.v1()), { injector: this.injector }); // Back Run 5 } }
The constructor phase calls the first effect, waits for an async microtask, and then executes the callback.
At this point, the entire lifecycle has been completed, and even afterNextRender has been executed.
On the surface, the view and root effect have the same execution timing, both waiting for the microtask, but in fact they have a slight difference, the callback of the view effect won't be scheduled immediately (the root effect will be), it will be delayed until after the refreshView is scheduled.
Why does it need to be pressed back? I'm not sure, and I don't know exactly under what circumstances this nuance would be manifested. But it doesn't hurt to not know, none of this matters anyway, v19 has a new execution timing... 🙄
v19 effect execution timing
Let's hold back the top layer of packaging and look directly at the bottom layer to see how the effect runs.
Dependence of effect
const v1 = signal('value');
effect(() => (v1()));
effect
It's a direct error... No surprise, effect depends on Injector. Just give it what it wants.
const v1 = signal('value'); const injector = ({ providers: [] }); // Create an empty Injector effect(() => (v1()), { injector }); // Hand over the Injector to the effect.
effect
Still an error... No surprise, we were given an empty Injector. The point is, we know that it depends on ChangeDetectionScheduler.
ChangeDetectionScheduler We're pretty familiar with it in theIvy rendering engine In the article we had gone through its source code.
Its core is the notify method, which is called in many places, such as after event dispatch, markForCheck, signal change, and so on.
After notify, it will setTimeout + tick and then refreshView.
Conclusion: After v19, the timing of effect execution is the same as that inChange Detection The mechanism is linked (v18 is not).
Okay, let's simulate a ChangeDetectionScheduler provide to it.
const injector = ({ providers: [ { provide: ɵChangeDetectionScheduler, useValue: { notify: () => ('notify'), runningTick: false } satisfies ɵChangeDetectionScheduler } ] });
effect
Still reporting errors, this time relying on EffectScheduler.
Let's go ahead and simulate a meet for it.
type SchedulableEffect = Parameters<ɵEffectScheduler['schedule']>[0]; const injector = ({ providers: [ { provide: ɵEffectScheduler, useValue: { schedulableEffects: [], schedule(schedulableEffect) { this.(schedulableEffect); // Save effect callback to favorites }, flush() { this.(effect => ()); // run is to execute the effect callback. } } satisfies ɵEffectScheduler & { schedulableEffects: SchedulableEffect[] } } ] });
EffectScheduler has 2 interfaces, a schedule method and a flush method, which we have already mentioned a little bit in the previous part.
At this point, calling effect will no longer result in an error.
Final Code
import { bootstrapApplication } from '@angular/platform-browser'; import { appConfig } from './app/'; import { AppComponent } from './app/'; import { effect, Injector, signal, ɵChangeDetectionScheduler, ɵEffectScheduler } from '@angular/core'; bootstrapApplication(AppComponent, appConfig) .catch((err) => (err)); const v1 = signal('value'); type SchedulableEffect = Parameters<ɵEffectScheduler['schedule']>[0]; const injector = ({ providers: [ { provide: ɵChangeDetectionScheduler, useValue: { notify: () => ('notify change detection machine'), runningTick: false } satisfies ɵChangeDetectionScheduler }, { provide: ɵEffectScheduler, useValue: { schedulableEffects: [], schedule(schedulableEffect) { ('schedule effect callback') this.(schedulableEffect); }, flush() { ('flush effect'); this.(effect => ()); } } satisfies ɵEffectScheduler & { schedulableEffects: SchedulableEffect[] } } ] }); effect(() => ('effect callback run', v1()), { injector }); const effectScheduler = (ɵEffectScheduler); queueMicrotask(() => ()); // Delay yourself. Flush yourself. Play with it.
effect
Conclusion: effect depends on Injector and Injector must be able to inject to instances of ɵChangeDetectionScheduler and ɵEffectScheduler abstract classes.
ChangeDetectionScheduler & EffectScheduler
Let's rationalize their relationship.
When we call effect, EffectScheduler will save the effect callback first, which is called schedule.
The notification Change Detection mechanism is then executed.
The relevant source code is at
Note: Here we are talking about the execution mechanism of the root effect, the view effect will be explained in the next part.
notify Change Detection will schedule a tick later. the source code is available inzoneless_scheduling_impl.ts (Reminder: the Change Detection mechanism has not changed in v19, only the timing of the effect execution has changed.)
scheduleCallbackWithRafRace internally performs setTimeout and requestAnimationFrame, whichever is triggered first.
Internally it executes (the famous tick method, which I won't go into too much detail about, for those of you who are not familiar with it, please review this article -- theIvy rendering engine)
The source code is in theapplication_ref.ts
The root effect is flushed before refreshView.
Okay, so this is the first time the root effect is executed.
Comparing the timing of the first execution of the v18 and 19 root effect
(v18)
export const appConfig: ApplicationConfig = { providers: [ provideExperimentalZonelessChangeDetection(), provideAnimations(), { provide: APP_INITIALIZER, multi: true, useFactory: () => { const injector = inject(Injector); return () => { const v1 = signal('v1'); effect(() => ('effect', v1()), { injector }); queueMicrotask(() => ('queueMicrotask')); }; }, }, ], };
(v19)
export const appConfig: ApplicationConfig = { providers: [ provideExperimentalZonelessChangeDetection(), provideAppInitializer(() => { const v1 = signal('v1'); effect(() => ('effect', v1())); queueMicrotask(() => ('queueMicrotask')); }) ] };
App Component (v18 & v19)
export class AppComponent implements OnInit { constructor() { ('App constructor'); } ngOnInit() { ('App ngOnInit'); } }
Effect (v18 & v19)
Obviously they are not in the same order... 😱
v18 is waiting for the microtask before executing the callback, so the callback is executed earlier than the App constructor.
The v19 effect notifies Change Detection early on, but Change Detection doesn't care because it's busy with thebootstrapApplication。
The bootstrapApplication will renderView first (instantiating the App component, at which point the App constructor executes), and then tick.
The tick flushes the root effect first (when the effect callback is executed) and then refreshes the View (when App ngOnInit is executed).
Okay, so this is the first time the root effect is executed.
Timing of the nth execution of the root effect
When the signal changes, the effect callback is rerun.
The relevant source code is at
This triggers consumerMarkedDirty, which then schedules the callback and notifies the Change Detection mechanism.
The Change Detection mechanism schedules the next tick, usually after setTimeout or requestAnimationFrame to see who is faster.
How can I tell if it's a view effect?
v18 is to see if it can be injected into ChangeDetectionRef, which is the view effect.
v19 is much the same.
The relevant source code is at
If an Injector can be injected into a ViewContext, it is a view effect.
If inject doesn't work on the ViewContext, it's a root effect.
The ViewContext looks like this, and the source code is in theview_context.ts
Here it uses the trick that __NG_ELEMENT_ID__ can only be injected by NodeInjector, not R3Injector. (For those of you who are not familiar with it, you can review this article -- theNodeInjecor)
Conclusion: As long as it can be injected into ViewContext, it must be NodeInjector, it must be under component, and it is view effect.
Timing of the first execution of the view effect
The relevant source code is at
There are 3 knowledge points:
-
view effect does not depend on EffectScheduler
This also means that it doesn't have schedule and flush, it has its own mechanism.
-
The ViewEffectNode (where the effect callback is also located) is saved to LView[EFFECTS 23]. (Note: LView comes from ViewContext)
For example, if we call an effect inside an App component, the view effect node created will be saved in [EFFECTS 23] of the App's parent LView (aka root LView).
The save action is similar to saving a callback and waiting for a time to execute it.
-
immediate implementation
In short, you mark LView as dirty, then notify Change Detection, then Change Detection will setTimeout + tick and refreshView.
The root effect executes the effect callback after the tick and before the refreshView.
The view effect executes the callback inside the refreshView, the source code for which is available in thechange_detection.ts
The view effect callback is executed after OnInit and before AfterContentInit. For those of you who are not familiar with lifecycle hooks, please review this article -- theLifecyle Hooks
Q: If we call effect in ngAfterContentInit, when will the first callback be executed?
A: The second round of refreshView. ngAfterContentInit has missed the first round of refreshView, but it doesn't matter because refreshView is rerun many times during a tick cycle.
The relevant source code is atchange_detection.ts
Let's test it out and see, the App component:
export class AppComponent implements OnInit, AfterContentInit, AfterViewInit { readonly v1 = signal('v1'); readonly injector = inject(Injector); constructor() { ('constructor'); // 1. will run after ngOnInit and before ngAfterContentInit (first round (of match, or election) refreshView) effect(() => ('constructor effect', this.v1())); } ngOnInit() { ('ngOnInit'); // 2. will run before ngAfterContentInit (first round (of match, or election) refreshView) effect(() => ('ngOnInit effect', this.v1()), { injector: this.injector }); } ngAfterContentInit() { ('ngAfterContentInit'); // will run after ngAfterViewInit (second round (of match, or election) refreshView (modal particle intensifying preceding clause)) effect(() => ('ngAfterContentInit effect', this.v1()), { injector: this.injector }); } ngAfterViewInit() { ('ngAfterViewInit'); } }
effect
The nth execution time of the view effect
signal Changes trigger consumerMarkedDirty,as a result mark LView dirty > notify Change Detection > setTimeout > tick > refreshView > effect callback,That's another round.。
summarize
The v18 effect runs the microtask mechanism.
In v19, the microtask is gone and linked to Change Detection.
root effect would put effect callback save (a file etc) (computing) (schedule) until (a time) EffectScheduler。
view effect would put effect callback Save to LView[EFFECT 23]。
The timing of the root effect is: notify > setTimeout > tick > andrun effect callback > refreshView > Onint > AfterContentInit > AfterViewInit > afterNextRender
The view effect is executed at the following times: notify > setTimeout > tick > refreshView > OnInit >.run effect callback > AfterContentInit > AfterViewInit > afterNextRender
Question: If there is no change in the signal inside the effect, but other external factors cause Change Detection to execute tick, will the . ...effect callback will be executed?
Answer: Of course not... . tick is just a full-field scan, whether effect will be executed or not, whether the LView template method will be executed or not, these still depend on whether they are dirty or not.
Reference:Github – change effect() execution timing & no-op allowSignalWrites
No more allowSignalWrites
In v18, if we go to set signal inside the effect callback, it will just report an error.
export class AppComponent { constructor() { const v1 = signal('v1'); const v2 = signal('v2'); effect(() => (v1())); // Error } }
We need to add an allowSignalWrites configuration.
effect(() => (v1()), { allowSignalWrites: true }); // no more error
v19 AllowSignalWrites is no longer needed because Angular doesn't report errors.
The reason why v18 reports an error is because Angular doesn't want us to modify other signals in the effect callback, not that we can't, just that we don't want to, so it reports an error, but then allows us to bypass it.
Don't use effects 🚫
This is a topic that the Angular team has been evangelizing lately.
YouTube – Don't Use Effects 🚫 and What To Do Instead 🌟 w/ Alex Rickabaugh, Angular Team
The Angular team's thinking is that the effect is used to synchronize with the outside world (out of reactive system).
For example, when the signal changes, you want to update the DOM, you want to update localstorage, these are typical out of reactive system.
But if you're trying to update another signal... It's a bit less of a windfall, like playing in a circle (inside reactive system) by yourself.
Bad feng shui is reflected in several places:
-
Possible infinite loop
As mentioned in the previous part, you can run the synchronizeOne method up to 10 times and refreshView up to 100 times in a tick cycle.
There will be this limitation is because I am afraid that the program will not be written well and go into an infinite loop, to avoid the excursion to run dead...
- Running multiple rounds of refreshView certainly doesn't save you any time or effort over running one round.
What if we really want to synchronize signals? computed is the way to go.
Of course computed has its limitations and may not be suitable for all scenarios.
In the YouTube video above, Alex Rickabaugh gives a very blind example of trying to replace effect with computed.
The final messed up writeup is signalValue()()... Double brackets🙄 (comment by JaiKrsh), and this writing also leads to memory leak🙄 (comment by a_lodygin).
Conclusion: Is it appropriate to modify the signal in the effect callback? I don't think the Angular team has decided yet, but the balance is -- it's good to avoid it, but it's not necessary, and things like double brackets and memory leaks are obviously necessary.
afterRenderEffect
The afterRenderEffect, as its name implies, is theafterRender + effect。
Note: It's afterRender + effect, not effect + afterRender Oh, and the main character is afterRender.
We think of it as an afterRender look-alike, and they have exactly the same characteristics:
-
SSR environments will not be executed.
-
The timing of the execution is tick > refreshView (update DOM) >run afterRender calblack > browser render
The only difference between the two is -- the afterRender callback is executed on every tick, while the afterRenderEffect is only executed when the signal it depends on changes.
export class AppComponent { constructor() { afterRender(() => ('render')); // Each tick executes a callback (e.g. a click event dispatch, which logs 'render'). const v1 = signal('v1'); // The callback is only executed when v1 is changed, click event dispatch is not, unless the value of v1 is changed after the click. // Of course, at least it will do it the first time, otherwise how would it know to listen for v1 changes. afterRenderEffect(() => ('effect', v1())); } }
Take a stroll through the source code
The source code is in theafter_render_effect.ts
If we look at it from the effect's point of view, / write / mixedReadWrite / read are equivalent to effect callbacks.
AfterRenderManager is equivalent to EffectScheduler for root effect or LView[EFFECT 23] for view effect, it is used to save the effect callback.
Whenever the signal on which the callback depends changes, it notifies the Change Detection mechanism.
summarize
afterRenderEffect is great for listening for signal changes and then synchronizing to the DOM.
It has a completely different timing than effect, which is exactly the same as afterRender.
Because of this new feature, effect is used in fewer scenarios.
No wonder the Angular team yells "Don't use effect", because they have a targeted alternative.
Reference:Github – introduce afterRenderEffect
linkedSignal
linkedSignal = signal + computed。
You can think of it as writable computed, or you can think of it as writable signal + effect ("synchronized" value).
const firstName = signal('Derrick'); const lastName = signal('Yam'); const fullName = linkedSignal(() =>firstName() + ' ' + lastName()); (fullName()); // 'Derrick Yam' ('Alex'); (fullName()); // 'Alex Yam'
It behaves exactly like computed.
Okay, here's the big one.
const firstName = signal('Derrick'); const lastName = signal('Yam'); const fullName = linkedSignal(() => firstName() + ' ' + lastName()); (fullName()); // 'Derrick Yam' // Directly set fullName ('new name'); (fullName()); // 'new name' ('Alex'); (fullName()); // 'Alex Yam'
linkedSignal can be assigned 😱 directly like writable signal.
When the signal that computed depends on changes, it switches back to the computed value.
We can't do the exact same thing in v18, but we can barely do signal + effect.
const fullName = signal(''); effect(() => (firstName() + ' ' + lastName()), { allowSignalWrites: true });
But effect is asynchronous, and there is no concept of computed lazy excute, so there is still a big difference in the final result.
Principle of linkedSignal
const fullName = linkedSignal(() =>firstName() + ' ' + lastName()); ('new name'); ('Alex'); (fullName()); // 'Alex Yam'
We start by assigning a value to fullName directly, and then we assign a value to the dependency (firstName) of fullName.
linkedSignal shows the computed result, correct.
Then try it again in reverse.
('Alex'); ('new name'); (fullName()); // 'new name'
linkedSignal shows the result of the signal set, correct.
Oops, that's clever. inedSignal seems to be able to sense the order in which firstName and fullName are assigned.
How does it do it? The source code is atlinked_signal.ts
The linkedSignal computed part is exactly the same as the computed mechanism.
When we call inkedSignal() to get the value, it will check the version of the dependent signal (Producer), and if the version is different from the one previously entered, the producer has changed, so we need to re-execute format / computation to get the new value.
The part of the inkedSignal set is different from the normal one, it has an extra step producerUpdateValueVersion().
We've looked at producerUpdateValueVersion'ssource code (computing)It does what it says above, checking the producer version > re-executing computation > and getting the new value.
const firstName = signal('Derrick'); const lastName = signal('Yam'); const fullName = linkedSignal(() => { ('run computation'); return firstName() + ' ' + lastName(); }); ('Alex'); // This triggers log 'run computation'
We didn't read the fullName value, we just set the fullName, but the computation was still executed.
This is the secret behind linkedSignal's ability to sense the order in which firstName and fullName are assigned.
summarize
Although linkedSignal is very much like writable computed, I personally think it's more of an alternative to the signal + effect "synchronization" value (don't use effect again +1).
Why else name it linkedSignal instead of writable computed?
In any case, whoever it looks like, it's a new, more flexible feature, nice, nice 👍.
Reference:Github – introduce the reactive linkedSignal
Resource API
Resource is kind of like an async version of linkedSignal.
It's good for situations where -- we want to listen for some signal changes, and then we want to do something asynchronous (like ajax), and come up with a value at the end.
Automatically send an ajax to update the value whenever the signal changes.
For such a simple requirement, the code would be very ugly 😩 if it was hard to implement it with effect without introducing RxJS.
That's why the Angular team introduced this Resource API, they wanted optional RxJS but the effect was poorly designed.
In the end, you can only introduce upper level encapsulated features like the Resource API to hide the dirty code and make newbies mistakenly think "wow... Writing code in Angular is so clean and awesome" 🙄.
Okay, let's look at examples
App Components
export class AppComponent { constructor() { // 1. this is the signal we're listening to const filter = signal('filter logic'); // 2. this is our ajax const getPeopleAsync = async (filter: string) => new Promise<string[]>( resolve => (() => resolve(['Derrick', 'Alex', 'Richard']), 5000) ) } }
We want to listen to the filter signal, whenever the filter changes, send ajax based on the filter to filter out the final people. (I'm not going to write the specific implementation code, we look at a shape, their own brain Ya)
The resource looks like this.
const peopleResource = resource({ request: filter, loader: async ({ request: filter, previous, abortSignal }) => { const people = await getPeopleAsync(filter); return people; } });
request is the signal we're listening for.
If we want to listen to more than one signal, we can wrap it up in computed, or we can just give it a function that acts as computed, like so
request: () => [signal1(), signal2(), signal3()],
loader: async ({ request : [s1, s2, s3], previous, abortSignal }) => {}
oader is a callback method that is called whenever the request changes.
Inside the loader, we use the latest filter value and send an ajax to get the final people. (Reminder: the return must be a Promise, RxJS Observable won't accept it).
In addition, there is an abortSignal in the loader parameter, which is used for theabort fetch Requested.
previous is the current state of the resource. Yes, resource has a state.
resource has 6 states
-
Idle
Initial state, where the resource value is undefined.
-
Error
The loader has failed, e.g. the ajax server is down, and the resource value is undefined.
-
Loading
The loader is loading data, e.g. ajax is not responding yet, and the resource value is undefined.
-
Reloading
The first time is called loading, the second time is called reloading (the request changes, the loader loads a second time), and the resource value is the value returned by the previous loader.
-
Resolved
loader succeeded,this time resource value be loader Returned value
-
Local
Resource is a linkedSignal, loader is its link, we can also directly set value for resource, this case is called local
The processing can be different for different state loaders, which is the intention of previous.
resource Commonly used attributes
const peopleResource = resource({ request: filter, loader: async ({ request: filter, previous, abortSignal }) => { const people = await getPeopleAsync(filter); return people; } }); (); // ['Derrick', 'Alex', 'Richard'] (); // true (); // undefined (); // false (); //
If it's at first loading then value is undefined, hasValue is false, isLoading is true, and so on.
In addition, resource can reload
();
Instead of waiting for the request to change, a manual reload will also trigger the loader ajax to get the new value.
There are also Resource-like linkedSignal, which can also be set and updated directly.
(['Jennifer', 'Stefanie']); (); // ['Jennifer', 'Stefanie'] (); //
Take a stroll through the source code
Resource is just an upper layer of encapsulation, it is the effect at the bottom, there is no big deal, we just stroll around.
The source code is in the
Here, you can already see its shape. reuqest and reload will cause computed to change, and the effect callback will definitely call computed inside, so whenever request or reload changes, the effect callback will be executed.
summarize
Resource is a little upper level wrapper, kind of like an async version of linkedSignal, mainly for -- listening to signals + async compute value.
It's currently in the Experimental phase, so I don't think it can be used in a real project yet.
From its implementation code, we can see that the effect'sinelegantWhile missing the convenience of RxJS.
I really hope the Angular team will find a cure soon and not let users continue to fold.
Reference:Github – Experimental Resource API
rxResource
rxResource is the RxJS version of Resource.
We provide a loader callback that returns an Observable, not a Promise.
Ouch, don't get me wrong.
It doesn't support the stream concept. It simply wraps a layer of resource calls underneath.
Use firstValueFrom to cut the Observable returned by the loader and convert it to a Promise, that's it... 🙄
summarize
This post briefly introduces some of the new features in Angular 19, as well as breakiing changes to effect execution timing.
I'll be back to add it when the official version launches, if there are any changes.
catalogs
Previous article.Angular 18+ Advanced Tutorial - Internationalization Internationalization i18n
Next TODO
To see the table of contents, go toAngular 18+ Advanced Tutorial - Table of Contents
Like it please click recommend 👍 and if you find the tutorial content to be out of context with the new version please comment to let me know. happy coding 😊 💻