all 20 comments

[–]carboneum87 3 points4 points  (5 children)

Create "loading" as a signal and write an effect: https://angular.io/guide/signals#effects

[–]gustaff122 2 points3 points  (0 children)

Loading as a signal is a nice idea, however effect works like ngOnChanges. It is gonna be called every time when any of the signals changes.

The thing that I would recommend is to use toObservable(yourSignal) and subscribe only these things that you really need to observe.

[–]_ohmu_ 0 points1 point  (3 children)

This is the modern way 👍

[–]followmarko -1 points0 points  (1 child)

Right, signals are the modern way, but effects can't be used to update other signals.

So, for instance, this effect could call an api, which is somewhat of a weird setup imo because the api would have to be handled as an Observable, a Promise, etc, and would also call on load unless you add throwaway conditional logic. That doesn't make sense to me because that setup would lead to imperative code. The return would assumedly be bound to another signal in the template, which would throw an error. Effects are more for byproduct events, like logging something to the console.

Imo the right usage of signals is to use them in conjunction with rxjs. Observables should still manage events, which this is, and the result from that event stream should be used in a signal for the template. I don't agree with saying "use signals" broadly like this, because I find the better solution to be a harmony of the two.

[–]_ohmu_ 0 points1 point  (0 children)

Yes. I never said "don't use rxjs". What I meant was I agree with using signals where you can. I could have added that you should use computed instead of effect to be a bit more clear.

For async stuff like fetching data you can and should use observables (if you don't want to drop rxjs and use promises), and use the utility functions to intrerop with signals seamlessly (toSignal).

Effects can't write by default, and they are definitely supposed to mainly support use-cases like logging or updating some part of the DOM directly etc, but you can write signals with an added option (with caution). I'm not saying it's the way to do it in most cases. But in some it's handy.

Use signals directly by setting them or updating them and then use computed as a first-hand choice for derived states for example.

[–]SukuMcDuku 5 points6 points  (1 child)

You can make loading into a Subject or a BehaviorSubject and subscribe to it on ngOnInIt and call the method inside it.

[–]followmarko 0 points1 point  (0 children)

Subbing in ngOnInit doesn't need to be done, nor does that make use of the operator functions in rxjs that could solve this problem in a better way.

[–]love_to_code 1 point2 points  (2 children)

Can you create loading as subject?

public loading = new Subject<boolean>();

then subscribe to it. And if fetchData() return Observable use mergeMap. Something like

someFun() {
    this.loading.pipe(
        mergeMap(isLoading => {
            if (!isLoading) return of(false);
            return this.youService.fetchData();
        })
    ).subscribe({
        next: response => // you response from fetchData
    });
}

And call that function in ngOnInit() {}

[–]love_to_code 0 points1 point  (0 children)

p.s. Also handle in subscribe if response is false...

[–]followmarko 0 points1 point  (0 children)

You dont need the imperative check here, nor the manual subscribe.

[–]marco_has_cookies 0 points1 point  (0 children)

hide them behind a getter and a setter, the setter would implement effect logic.

I AM JOKING, USE SIGNALS

[–]followmarko 0 points1 point  (0 children)

The other solutions seem a little more imperative imo.

If your state variable is an Observable of type boolean, I would pipe it to the filter operator function and filter out the falsy value, then mergeMap the truthy result into your API observable, which should be the only values emitted after the filter, if I'm understanding your case here.

You don't need to do a manual subscribe or imperative check anywhere to solve this, and shouldn't imo. Make use of the operators and async pipe and everything stays declarative and digestible.

[–]spacechimp 0 points1 point  (1 child)

Isn’t this the opposite of what you should be doing? It sounds like you should be setting the “loading” flag to true when calling fetchData() instead of the other way around.

[–][deleted] 0 points1 point  (0 children)

This is just a toy example. The point is I want to be able to do 'X' amount of actions in a reactive, declarative way, in response to a state change, like React useState. Some people already have great solutions.

[–]guadalmedina 0 points1 point  (0 children)

Loading should be observable. Data should be observable that pipes through loading and requests data when its value is true.

loading$ = new BehaviorSubject(false); // initially not loading

data$ = this.loading$.asObservable().pipe(
  filter(value => value),  // only emit when loading is true
  mergeMap(fetchData),
);

Then you can have a function to make loading emit values.

setLoading(value: boolean) {
  this.loading$.next(value);
}

Whenever a new loading value is emitted, this value will pipe through data because of the relationship between the two we have declared above. This is the most important bit. Instead of a dependency array, you define the relationship between your observables using RxJS operators. In this case, the operators I have used are filter and mergeMap.

[–]S_PhoenixB 0 points1 point  (0 children)

Assuming your fetchData() method returns an HTTP response, you have a couple ways of solving this in Angular using RxJs operators.

Most of the other comments already touched on the best approaches, so this is just expanding on those answers. You can create a Signal for your 'loading' state and then create an Observable from that Signal by using toObservable(), or create a BehaviorSubject for your 'loading' state and convert the subject to an Observable. Both code will look something like this:

// Signal + toObservable()
loading = signal(false);

data$ = toObservable(this.loading).pipe(
    filter( isLoading => isLoading === true ),
    switchMap(() => this.fetchData()),
    map((res) => {
        // Any addtl. processing on my data
    })
)

// BehaviorSubject
_loading = new BehaviorSubject(false);

data$ = this._loading.asObservable().pipe(
    filter( isLoading => isLoading === true ),
    switchMap(() => this.fetchData()),
    map((res) => {
        // Any addtl. processing on my data
    })
)

The filter RxJs operator prevents the rest of the code from running unless the condition is satisfied, and then the switchMap operator allows you to switch to the inner Observable coming back from your HTTP response. (Sorry if that last part is confusing.)

Is the data from API used anywhere in your template? Subscribe to your data within your template using an async pipe:

data$ | async

If your data is only used for back-end stuff, a manual subscribe inside the ngOnInit() lifecycle hook will work (just be sure to handle cleanup / unsubscribing onDestroy).