Piotr Sobuś
Piotr Sobuś

Reputation: 322

PWA: Exclude caching in some part of application

In an Angular application, I have an URL endpoint that is being cached like so:

// ngsw-config.json
"dataGroups": [{
      "name": "api-performance",
      "urls": [
        "https://my-api.com/v1/languages",
      ],
      "cacheConfig": {
        "strategy": "performance",
        "maxSize": 300,
        "maxAge": "1d"
      }
    }
  ]

It works perfectly in offline scenarios when a client goes through a survey process. But in admin panel, when I try to update the language information, indeed - it does update the record in database, but when I try to refresh the data, it doesn't send the request to our endpoint, but to stored cache in browser.

This is what I tried:

getLanguages(shouldCache: boolean): Promise<any> {
    if (shouldCache) {
      return this.httpClient.get('https://my-api.com/v1/languages').toPromise();
    } else {
      const headers = new HttpHeaders({
        'Cache-Control': 'no-cache, no-store, must-revalidate, post-check=0, pre-check=0',
        'Pragma': 'no-cache',
        'Expires': '0'
      });
      return this.httpClient.get('https://my-api.com/v1/languages', { headers: headers }).toPromise();
    }
  }

Unfortunately, it doesn't work. I thought about updating the cache also, but I don't know how to do it.

Does anyone have an idea how to solve this problem?

Upvotes: 3

Views: 3218

Answers (2)

Chris Love
Chris Love

Reputation: 3893

First, you need to understand the service worker layer is decoupled from both the front-end and your server. It sits in the middle and intercepts all network requests so you can decide how they will be handled.

The service worker fetch event handler has a single argument, event.

You can 'parse' this object for information about the request, event.request and decide how things should be handled.

For example you most likely do not want to cache POST, PUT or DELETE requests, so just pass those along to the network by calling fetch. Test for just GET requests, to make this easy.

self.addEventListener("fetch", event => {

  event.respondWith(

    if(event.request.mode === "GET") {

      //compare URL against rules table
      //perform caching strategy for this URL

    } else {

      return fetch(event);

    }
  );
});

If you may have a caching strategy to apply to the URL then you need to see what strategy you may want to apply. FWIW, I have around 25 different strategies I may employ, so I will try to keep this simplified.

This is an example of my method to match the URL with the caching strategy:

function testRequestRule( request, rules ) {

  for ( let i = 0; i < rules.length; i++ ) {

      if ( rules[ i ].route && rules[ i ].route.test( request.url ) ) {
          return rules[ i ];
      } else if ( rules[ i ].destination &&
          rules[ i ].destination === request.destination ) {
          return rules[ i ];
      }

  }

}

This is what a rule object might look like:

let routeRules = [ {
    "route": /img\/products\//,
    "strategy": "cacheFallingBackToNetworkCache",
    "options": {
        cacheName: prodPhotos,
        fallback: offlineProductPhoto
    }
},....]

Then I will execute the desired strategy on the request:

    if ( rule.strategy ) {

        switch ( rule.strategy ) {

            case "cacheFallingBackToNetwork":

                return responseManager.cacheFallingBackToNetworkCache(
                    event.request, rule.cacheName || cacheName,
                    rule.options.fallback );

            case "fetchAndRenderResponseCache":

                return responseManager.fetchAndRenderResponseCache( {
                        request: event.request,
                        pageURL: rule.options.pageURL,
                        template: rule.options.template,
                        api: rule.options.api,
                        cacheName: rule.cacheName || cacheName
                    } )
                    .then( response => {

                        invalidationManager.cacheCleanUp( rule.cacheName || cacheName );

                        return response;

                    } );

            case "cacheOnly":

                return responseManager.cacheOnly( event.request, rule.cacheName || cacheName )
                    .then( response => {

                        invalidationManager.cacheCleanUp( rule.cacheName || cacheName );

                        return response;

                    } );

            case "networkOnly":

                return responseManager.networkOnly( event.request );

            case "custom":

                return rule.options.handler( event, rule );

            default:

                return responseManager
                    .cacheFallingBackToNetworkCache( event.request,
                        rule.cacheName || cacheName,
                        rule.options.fallback )
                    .then( response => {

                        invalidationManager.cacheCleanUp( rule.cacheName || cacheName );

                        if ( response ) {
                            return response;
                        } else {
                            return simpleFetch( event );
                        }

                    } )
                    .catch( error => {
                        console.error( "fetch error: ", error );
                        console.error( "url: ", event.request.url );
                    } );

        }

    } else {

        return simpleFetch( event );

    }

In short, you can control how every single network request is handled, this is the beauty of the service worker. You just have to create the logic to handle this and there are no magic bullets to making a good service worker. You will have to write code yourself and of course test it. :)

Upvotes: 0

Walter Luszczyk
Walter Luszczyk

Reputation: 1522

You are using performance mode with 1 day maxAge, which always gives value from cache if available. You'll see data change after one day.

Instead you can use freshness mode or diminish maxAge in performance mode.

Yours manual done request doesn't apply, because URL https://my-api.com/v1/languages is cached in service worker. Either cache manipulation in request won't work, because request cache and service worker cache are distinct cache layers.

From Angular docs:

The Angular service worker can use either of two caching strategies for data resources.

performance, the default, optimizes for responses that are as fast as possible. If a resource exists in the cache, the cached version is used, and no network request is made. This allows for some staleness, depending on the maxAge, in exchange for better performance. This is suitable for resources that don't change often; for example, user avatar images.

freshness optimizes for currency of data, preferentially fetching requested data from the network. Only if the network times out, according to timeout, does the request fall back to the cache. This is useful for resources that change frequently; for example, account balances.

Upvotes: 2

Related Questions