[NgRx]Is there any risk in registering the ComponentStore at the root level?

504 Views Asked by At

I saw the articles, they put the componentStore at the root level as a global store, which is convenient for development and can work normally.

enter image description here

However, in the NgRx documentation, @NgRx/Store is still used as an example if you want to manage global state.

enter image description here

In my opinion, it seems correct to regard componentStore as a global service, but it conflicts with the design concept of ComponentStore. I don't know if there are any objections. Do you have any opinion?

1

There are 1 best solutions below

5
Krenom On

I recommend not bothering to go down the ngrx route for anything, ever.

With Angular's architecture and its DI and singleton services and whatnot, the only thing you get out of ngrx is a headache and lot more complication in your code.

Why sub to your service and make a single request for new data when you could make 17 other objects for effects, and reducers, and whatever else it needs before... subbing to the service and requesting new data?

Whatever global service you want:

export class GlobalCacheService {
    private observers: {[key: string]: Observer<any>} = {};
    private observables: {[key: string]: Observable<any>} = {};

    public getObs<T>(key: string): Observable<T> {
        return this.observables[key];
    }

    public addObs<T>(key: string): void {
        if (this.observables[key]) return;
        this.observables[key] = new Observable<T>((x: Observer<T>) => {this.observers[key] = x;}).pipe(share());
    }

    public request<T>(fn: () => Observable<T>, key: string, returnRawResponse?: boolean): void {
        const sub = fn()
            .subscribe({
                next: (response: T) => {
                    sub.unsubscribe();
                    this.emit(key, response);
                },
                error: (err) => {
                    sub.unsubscribe();
                    this.emit(key, returnRawResponse ? err.error : undefined);
                }
            });
    }

    private emit<T>(key: string, data?: T): void {
        this.observers[key]?.next(data);
    }
}

This is actually taken from a base class I have in use somewhere, but I figure it would do the job for illustrating.

Usage:

private readonly cacheKey: string = 'someKeyThatShouldBeSomewhereSharedRatherThanJustInHere';

constructor(private readonly cache: GlobalCacheService) {
    cache.addObs<string>(this.cacheKey); // ensure it's initialised - probably should be done elsewhere.
}

public ngOnInit(): void {
    this.cache.getObs<string>(this.cacheKey)
        .subscribe((x: string) => this.onData(x));
}

private onData(x: string): void {
    console.log('got data from global cache: ', x);
}

Now, as I said, it's not really a cache because it's actually an abstract base service template thingy, but it had the extensible observable setup in it which is the crux of the matter.

Though probably BehaviorSubjects would be better for a cache because they will emit the last value to any new subscribers, whereas those plain old Observables will only emit when told to emit, so not great for a cache.

As it's a base service it contains a request wrapper for, for example, http requests, usage would be thus:

export class MyService extends ServiceBase {
    public static dataKey: string = 'dataKey';

    constructor(private readonly http: HttpClient) {
        this.addObs<string>(MyService.dataKey);
    }

    public getSomeData(): void {
        this.request(() => this.http.get<string>('someUrl'), this.someKey);
    }

}

Ostensibly, anything could be in that anon method, though.

and component usage, per above already:

export class SomeComponent implements OnInit {
    constructor(private readonly service: MyService) {}

    public ngOnInit(): void {
        this.someSub = this.service.getObs<string>(MyService.dataKey)
            .subscribe((x: string) => onDataReceived(x));

        // Assuming this component is responsible for getting/refreshing the data and the other eleventeen components listening don't, I dunno
        this.service.getSomeData();
    }

    private onDataRecieved(x: string): void {
        console.log('got data: ', x);
    }
}

So yeah, that's a lot of rambling for someone that wants to just crawl into bed, and it looks worse than it is because we're looking at all the base stuff.

Once the one or two files are in place, you're looking at one or two lines every time you want your service or component to tap in to that cache: an init call (unless its done somewhere at app setup (likely recommended), and a subscription... plus the occasional request call whenever a component/service wants to get fresh data for everyone.

Just remember that ultimately, the store pattern type stuff just means a single place that everything will go, to listen for and/or cause updates.

Welcome to observables in a [singleton] service in Angular. A basic concept involving a lot less effort (especially when done at a very basic level).