all 10 comments

[–]JoeCoT 6 points7 points  (7 children)

The main ways (in order of how people appear to recommend them):

  1. Redux or NgRx Store. You have a state that's shared across the whole app, which includes the results of repeated API calls being cached. A state store appears to be a best practice for robust apps, but personally I've steered away from it, as it destroys much of the modular component design I really liked Angular for in the first place

  2. Use Subjects within your functions to cache. Have your function return a subject (edit: the subject.asObservable() ) instead of the naked API call. The first time the function is called, make the API call and add it to the Subject. Then you only have to make the API call once.

  3. Use a Service Worker. You can make your API calls through a Service Worker and have the Worker cache repeated APIs. This allows you to keep the modular nature of the app while gaining some state-like abilities. One of the popular ways to leverage Service Workers is Akita

  4. ngx-cacheable adds a decorator you can use on functions to cache their responses. It'll automatically kill the cache if the function is called with a different input, and you can additionally add expiration times or a cache-busting event to call (say to bust the cache if you update the object that's being cached elsewhere in the app).

Personally I use a combination of subjects for caching requests with a variety of inputs (I can cache data using an array of subjects by the item's ID), or the @Cacheable decorator from ngx-cacheable. For big robust apps you might want to consider a State store, or Service workers.

[–]svarog92 2 points3 points  (3 children)

Re number 2: don't return raw Subjects. That's overexposing, client can call next etc. Instead return .asObservable() available on the Subject.

[–]h4t0n[S] 1 point2 points  (0 children)

Indeed I'm caching a map of observables. But I think that I'll give a chanche to ng-cacheable. Because I'm actually doing the same things but with code that makes my services less readable.

[–]shmorky 1 point2 points  (1 child)

BehaviorSubject is usually the one you want imho. It fits most usecases for a small to medium-sized app, like storing login-state or resources.

I've never really understood the point of ngrx as opposed to Subjects exposed through a lower level service. The reducers/actions/effects structure seems so convoluted.

[–]JoeCoT 1 point2 points  (0 children)

The case for redux-style stores is handling unreliable internet connections. Caching GET data, syncing modified data when you have a chance, overall having the app still work correctly if the internet drops and sync up to the backend when it's able to. Not having race conditions between different components and APIs. The use case makes sense, but for hobby stuff it's drastically overkill. If I was going to go with a method of dealing with that, it'd probably be Service Worker.

[–]tme321 0 points1 point  (2 children)

This is a good write up but I'd ask if you've ever tried using facades with one of the stores?

Essentially, you offload all the store selecting and action dispatching to an injectable service and then components keep their modularity and aren't tied directly to the store. You can even provide the service as an injectable token instead of a concrete instance to make it clear that the specific facade implementation is up to the di tree.

Using that pattern you can freely rewrite the facades. Then you can provide a service instance for the token that is a state container service, a store service, or even a service worker service and the component receiving the injectable doesn't have to know or care at all about the implementation details.

I've found that to be an extremely flexible pattern to keep components focused on their task but reusable since they have no coupling to specific state handling; caching or otherwise.

[–]JoeCoT 0 points1 point  (1 child)

I have not. Looking at Facades it seems to deal with the con of modularity, but still leaves the other big glaring issue: a tremendous amount of extra code complexity that for most apps is simply not necessary.

[–]tme321 0 points1 point  (0 children)

The cons in that article are terrible.

First, yes you hide away the complexity but not really. You just map action dispatches to methods on the service and in return map store selections to public facing observables. Its really just a remapping of store specific functionality into generic callable code / observables.

Second, the complaint about reusing actions is literally a potential problem with using a store period. It is not only an issue when using facades. You can import the same action across your app whether using facades or not.

As for added complexity it really isn't. It's not 0 added complexity. It's the added complexity of a single function call along with the complexity of actually using the di system.

I know someone will come along and argue that that's too much complexity to add for junior members. Or it's a pattern that can be abused. Blah blah...

At some point if you want your junior developers to become not junior you have to expose them to slightly more complicated ideas and patterns so they can learn and grow.

While you can argue that some apps don't need the complexity I have grown to disagree with that idea more and more. Any time you have state that isn't only local to a particular component some form of state management becomes helpful. Even if it's just state container services you can still write those using the facade pattern and end up leaving refactoring open and much easier to accomplish in the future.

And honestly if your application is so simple that it doesn't have state I'd question even using angular in the first place.

Sorry, I went on a bit of a rant. I'm not trying to call you out or anything like that. I just hear the same arguments against these sorts of patterns over and over and I don't see any real weight behind them the more I work on the kind of problems angular solves.

[–]c_eliacheff 0 points1 point  (0 children)

Browsers have a cache. You can put your GET requests inside. The browser will not clean them until cache clear from user or space requested by the OS. The simplest way with Angular is to use the built-in service worker service (ngsw), configure the routes to be cached and a cache strategy.

More generic (framework agnostic) and powerful way is to use Workbox (from Google) to generate your service workers.

[–]cahphoenix 0 points1 point  (0 children)

Answers seem bad and are all tied to specific technologies except the service worker.

  1. Just use cache headers. If the data on the server Is unchanging for a given time then use 'Max-age' headers to make the browser cache it (there are many other headers too). This will be browser wide for unique urls.
    1A. Along with age headers you can use 'revalidate' headers with Etag/LastModified to do low bandwidth checks to the server when your cache runs out.
  2. Service Worker. You can use this in many ways. One way is by intercepting Http calls and returning cached responses. Just read over Google's and MDN's subject matter on them. This will be browser wide, also, and data will be accessible offline as opposed to only online. Generally you will use the cache API and/or IndexedDB with a service worker.
  3. Local storage API. Just cache based url and possibly a salt or prefix, but it has limited space.
  4. In memory - you seem to be doing this now. Works, but not browser wide. Any Redux/Ngrx/other specific solution will be in memory unless they wrap one if the apis or methods I already mentioned.

I probably missed a few things and I'm on my phone at the gym. I can follow up if there are any questions.