1<!DOCTYPE html><html lang="en" data-critters-container><head><base href="/"> 2 <link rel="preload" href="/assets/fonts/lato-latin-400.woff2" as="font" type="font/woff2" crossorigin="anonymous"> 3 <link rel="preload" href="/assets/fonts/sen-latin-400.woff2" as="font" type="font/woff2" crossorigin="anonymous"> 4 <link rel="preload" href="/assets/fonts/lato-latin-700.woff2" as="font" type="font/woff2" crossorigin="anonymous"> 5 <link rel="alternate" type="application/rss+xml" title="RSS Feed for allfront.io" href="https://allfront.io/rss.xml"> 6 <link rel="preload" as="image" href="/assets/images/branding/all-front-logo.svg"> 7 <meta name="google-site-verification" content="q0Q7wqR1xaZj8C2WGLTqaQ1ubq32dJVuFX0GCseUVjU"> 8 9 <meta charset="utf-8"> 10 <meta content="IE=edge" http-equiv="X-UA-Compatible"> 11 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 12 <meta content="yes" name="apple-mobile-web-app-capable"> 13 <meta content="yes" name="apple-mobile-web-app-status-bar-style"> 14 <meta content="#6862b6" name="theme-color"> 15 <meta content="yes" name="apple-mobile-web-app-capable"> 16 <title>How to take your Angular apps offline | All Front</title> 17 <meta content="Discover everything you need to know about Service Workers and how to add them to enable offline capabilities for your Angular apps." name="description" property="og:description"> 18 <meta content="Angular,Business,Service Workers" name="keywords"> 19 <link href="/assets/icons/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180"> 20 <link color="#604b8c" href="/assets/icons/safari-pinned-tab.svg" as="mask-icon"> 21 <link href="/assets/icons/favicon.ico" rel="shortcut icon" fetchpriority="low"> 22 <meta content="#ffffff" name="msapplication-TileColor"> 23 <meta content="/assets/favicons/browserconfig.xml" name="msapplication-config"> 24 <link href="favicon.ico" rel="icon" type="image/x-icon"> 25 <link href="manifest.webmanifest" rel="manifest"> 26 <style>@font-face{font-family:Sen;font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sen-latin-ext-400.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Sen;font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sen-latin-400.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Sen;font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sen-latin-ext-700.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Sen;font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sen-latin-700.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Sen;font-style:normal;font-weight:800;font-display:swap;src:url(/assets/fonts/sen-latin-ext-800.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Sen;font-style:normal;font-weight:800;font-display:swap;src:url(/assets/fonts/sen-latin-800.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}*{padding:0;margin:0}body{font-family:Sen,sans-serif;width:100vw}html{overflow-y:scroll;overflow-x:hidden}@media (min-width: 481px) and (max-width: 992px){html{-webkit-overflow-scrolling:touch;scroll-behavior:smooth}}.content-parent{width:100%}@supports (-moz-appearance: none){*{filter:none!important}}@media (max-width: 768px){}html{background-color:#fff}.smoovy-container{perspective:calc(5000px - 100vw);animation-fill-mode:forwards;perspective-origin:50% 50%;opacity:1}@media (min-width: 993px){.smoovy-container{position:fixed;left:0;top:0;width:100%;height:100%}}@media (max-width: 991px){.smoovy-container{position:relative!important}}@media (min-width: 992px) and (max-width: 1529px){.smoovy-container{perspective:6500px}}@media (min-width: 1530px) and (max-width: 2047px){.smoovy-container{perspective:5000px}}@media (min-width: 2048px){.smoovy-container{perspective:calc(7000px - 100v
26w)}}.smoovy-wrapper{transform-style:preserve-3d}body{overflow:hidden}app-root{display:block;transform-style:preserve-3d}</style><style>@font-face{font-family:Lato;font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/lato-latin-ext-400.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Lato;font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/lato-latin-400.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Lato;font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/lato-latin-ext-700.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Lato;font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/lato-latin-700.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Lato;font-style:normal;font-weight:900;font-display:swap;src:url(/assets/fonts/lato-latin-ext-900.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Lato;font-style:normal;font-weight:900;font-display:swap;src:url(/assets/fonts/lato-latin-900.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Montserrat;font-style:normal;font-weight:500;font-display:swap;src:url(/assets/fonts/montserrat-latin-ext-500.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Montserrat;font-style:normal;font-weight:500;font-display:swap;src:url(/assets/fonts/montserrat-latin-500.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Sen;font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sen-latin-ext-400.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Sen;font-style:normal;font-weight:400;font-display:swap;src:url(/assets/fonts/sen-latin-400.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Sen;font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sen-latin-ext-700.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Sen;font-style:normal;font-weight:700;font-display:swap;src:url(/assets/fonts/sen-latin-700.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Sen;font-style:normal;font-weight:800;font-display:swap;src:url(/assets/fonts/sen-latin-ext-800.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Sen;font-style:normal;font-weight:800;font-display:swap;src:url(/assets/fonts/sen-latin-800.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}*{padding:0;margin:0}body{font-family:Sen,sans-serif;width:100vw}html{overflow-y:scroll;overflow-x:hidden}@media (min-width: 481px) and (max-width: 992px){html{-webkit-overflow-scrolling:touch;scroll-behavior:smooth}}h3,h4,app-blog-post h1,h2,h1{font-family:Sen,sans-serif}h1,h2,h3,h4{line-height:1.2}h1{color:#3b3952;letter-spacing:2px}@media (max-width: 480px){h1{font-size:32px}}@media (min-width: 480px) and (max-width: 768px){h1{font-size:48px}}@media (min-width: 768px) and (max-width: 1530px){h1{font-size:48px}}@media (min-width: 1530px) and (max-width: 3840px){h1{font-size:64px}}h2{color:#3b3952}@media (max-width: 480px){h2{font-size:32px}}@media (min-width: 480px) and (max-width: 768px){h2{font-size:32px}}@media (min-width: 768px) and (max-width: 1530px){h2{font-size:32px}}@media (min-width: 1530px) and (max-width: 3840px){h2{font-size:48px}}h3,h4,app-blog-post h1{color:#3b3952}@media (max-width: 480px){h3,h4,app-blog-post h1{font-size:20px}}@media (min-width: 480px) and (max-width: 768px){h3,h4,app-blog-post h1{font-size:20px}}@media (min-width: 768px) and (max-width: 1530px){h3,h4,app-blog-post h1{font-size:32px}}@media (min-width: 1530px) and (max-width: 3840px){h3,h4,app-blog-post h1{font-size:32px}}p,li,a,span,h5{color:#3b3952;font-size:20px;font-family:Lato,sans-serif;line-height:1.5}
26code{color:#3b3952;font-size:16px;line-height:1.5}@media (min-width: 1530px){section{display:flex;align-items:center;justify-content:center}section:not(.no-expand){min-height:max(100vh,672px)}}@media (min-width: 993px){section{padding-top:192px;padding-bottom:192px}section.no-top{padding-bottom:192px;padding-top:0}}@media (max-width: 992px){section{padding:16vw 0}section.no-top{padding-bottom:16vw;padding-top:0}}.content-parent{width:100%}@supports (-moz-appearance: none){*{filter:none!important}}@media (max-width: 768px){}button{font-family:Montserrat,sans-serif;padding:18px 24px;border-radius:0;text-align:center;font-size:20px;font-weight:500;border:2px solid #6862b6}button.outline{background-color:transparent;color:#6862b6}button:hover{cursor:pointer}button:focus{outline:none}button:disabled{cursor:not-allowed;opacity:.8}@media (max-width: 992px){.header-padding-top{padding-top:72px}}.content{box-sizing:border-box}@media (max-width: 768px){.content{margin:0 auto;width:90vw}}@media (min-width: 769px) and (max-width: 991px){.content{margin:0 auto;width:90vw;max-width:1530px}}@media (min-width: 992px) and (max-width: 1529px){.content{margin:0 auto;width:90vw;max-width:1530px}}@media (min-width: 1530px) and (max-width: 2047px){.content{margin:0 auto;width:90vw;max-width:1530px}}@media (min-width: 2048px) and (max-width: 3839px){.content{margin:0 auto;width:90vw;max-width:1530px}}@media (min-width: 3840px){.content{margin:0 auto;width:90vw;max-width:2048px}}.token.punctuation{color:#c5c8c6}.token.property,.token.keyword{color:#96cbfe}.token.class-name{color:#ffffb6}.token.boolean{color:#9c9}.token.number{color:#ff73fd}.token.string{color:#a8ff60}.token.operator{color:#ededed}.token.function{color:#dad085}html{background-color:#fff}.smoovy-container{perspective:calc(5000px - 100vw);animation-fill-mode:forwards;perspective-origin:50% 50%;opacity:1}@media (min-width: 993px){.smoovy-container{position:fixed;left:0;top:0;width:100%;height:100%}}@media (max-width: 991px){.smoovy-container{position:relative!important}}@media (min-width: 992px) and (max-width: 1529px){.smoovy-container{perspective:6500px}}@media (min-width: 1530px) and (max-width: 2047px){.smoovy-container{perspective:5000px}}@media (min-width: 2048px){.smoovy-container{perspective:calc(7000px - 100vw)}}.smoovy-wrapper{transform-style:preserve-3d}body{overflow:hidden}button{cursor:pointer}h2{margin-bottom:24px}img{width:100%}a{text-decoration:none;color:#6862b6;font-weight:700}.centered{text-align:center}ul{list-style-position:inside}ul{padding-left:12px}@media (min-width: 992px){p{max-width:650px}}@media (min-width: 1530px){p{max-width:40vw}}.ellipsis-2-lines{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.rich-text{display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:start}.rich-text>*{width:100%}.rich-text a{word-break:break-word}.rich-text h1{margin-bottom:6px;font-size:32px}@media (max-width: 480px){.rich-text h1{font-size:20px}}@media (min-width: 480px) and (max-width: 768px){.rich-text h1{font-size:20px}}.rich-text h6{text-align:center;margin-bottom:24px;font-style:italic;font-size:20px;font-weight:400;font-family:Lato,sans-serif}.rich-text img{margin:12px auto;height:auto;box-shadow:12px 12px 24px #3d3b5326}.rich-text h4{margin-top:36px;margin-bottom:0!important}.rich-text p{max-width:100%;margin-bottom:24px}.rich-text pre{display:flex;margin:24px 0}.rich-text pre code{padding:24px;margin:0 auto;white-space:pre-wrap}.rich-text code,.rich-text pre{background-color:#161b22;word-break:break-word;color:#fff;font-size:20px}.rich-text blockquote{margin:24px 6px}.rich-text ul{padding-left:0;margin-bottom:24px}.rich-text blockquote{border-left:3px solid #bcbbcf}app-root,.route-wrapper{display:block;transform-style:preserve-3d}app-root section,app-root .content{transform:translateZ(1px)}</style><link rel="stylesheet" href="styles.92ef09053ae06b5d.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles.92ef09053ae06b5d.css"></noscript><style ng-app-id="ng">@media (max-width: 992px){.header-padding-top[_ngcontent-ng-c1778523502]{padding-top:72px}}@media (min-width: 993px){.header-padding-top.forced-above-tablet[_ngcontent-ng-c1778523502]{padding-top:96px}}.border-radius[_ngcontent-ng-c1778523502]{border-radius:10px}.transition[_ngcontent-ng-c1778523502], .transition-all[_ngcontent-ng-c1778523502] *[_ngcontent-ng-c1778523502]{transition:all .4s ease}p[_ngcontent-ng-c1778523502]{margin-bottom:24px}.blog-post-content[_ngcontent-ng-c1778523502], .related-blogs[_ngcontent-ng-c1778523502]{max-width:720px;margin:auto}.meta[_ngcontent-ng-c1778523502]{display:flex;align-items:center;border-top:2px solid #f4f6f8;margin-top:12px;padding-top:24px}
26.meta[_ngcontent-ng-c1778523502] .publication-details[_ngcontent-ng-c1778523502]{margin-left:12px;height:-moz-fit-content;height:fit-content;margin-top:-12px}.meta[_ngcontent-ng-c1778523502] .publication-details[_ngcontent-ng-c1778523502] p[_ngcontent-ng-c1778523502]{font-size:12px}.meta[_ngcontent-ng-c1778523502] img[_ngcontent-ng-c1778523502]{max-height:72px;width:auto;border-radius:50%}.blog-post-content[_ngcontent-ng-c1778523502]{display:block}.blog-post-content[_ngcontent-ng-c1778523502] .markdown[_ngcontent-ng-c1778523502]{display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:start}.blog-post-content[_ngcontent-ng-c1778523502] .markdown[_ngcontent-ng-c1778523502] table{width:100%;border-collapse:collapse;color:#3b3952;box-shadow:#3b39521a 0 5px 15px}.blog-post-content[_ngcontent-ng-c1778523502] .markdown[_ngcontent-ng-c1778523502] table th{font-family:Sen,sans-serif;padding:12px;text-align:left}.blog-post-content[_ngcontent-ng-c1778523502] .markdown[_ngcontent-ng-c1778523502] table thead{font-weight:600}.blog-post-content[_ngcontent-ng-c1778523502] .markdown[_ngcontent-ng-c1778523502] table thead th{border-bottom:2px solid #3b3952}.blog-post-content[_ngcontent-ng-c1778523502] .markdown[_ngcontent-ng-c1778523502] table tbody th, .blog-post-content[_ngcontent-ng-c1778523502] .markdown[_ngcontent-ng-c1778523502] table tbody td{border-bottom:1px solid #eeedf7}.blog-post-content[_ngcontent-ng-c1778523502] .markdown[_ngcontent-ng-c1778523502] table tbody tr:hover{box-shadow:#3b39521a 0 5px 15px}.blog-post-content[_ngcontent-ng-c1778523502] .markdown[_ngcontent-ng-c1778523502] table tbody tr td{font-family:Lato,sans-serif;padding:12px;text-align:left;word-wrap:break-word}.blog-post-content[_ngcontent-ng-c1778523502] .markdown[_ngcontent-ng-c1778523502] .table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (min-width: 993px){.blog-post-content[_ngcontent-ng-c1778523502] .markdown[_ngcontent-ng-c1778523502] .table-responsive::-webkit-scrollbar{width:0;height:0}}.blog-post-content[_ngcontent-ng-c1778523502] .markdown[_ngcontent-ng-c1778523502] {all:inherit}.blog-post-content[_ngcontent-ng-c1778523502] .markdown[_ngcontent-ng-c1778523502] .blog-post-content-header{margin-bottom:6px;font-size:32px}@media (max-width: 480px){.blog-post-content[_ngcontent-ng-c1778523502] .markdown[_ngcontent-ng-c1778523502] .blog-post-content-header{font-size:20px}}@media (min-width: 480px) and (max-width: 768px){.blog-post-content[_ngcontent-ng-c1778523502] .markdown[_ngcontent-ng-c1778523502] .blog-post-content-header{font-size:20px}}.blog-post-content[_ngcontent-ng-c1778523502] .blog-desc[_ngcontent-ng-c1778523502]{margin-bottom:24px}.blog-post-content[_ngcontent-ng-c1778523502] .blog-img[_ngcontent-ng-c1778523502]{margin:12px auto;height:auto}.blog-post-content[_ngcontent-ng-c1778523502] .blog-img-caption[_ngcontent-ng-c1778523502]{text-align:center;margin-bottom:24px;font-style:italic}.related-blogs[_ngcontent-ng-c1778523502]{display:flex;flex-direction:column;align-items:center}@media (min-width: 769px){.related-blogs[_ngcontent-ng-c1778523502] .related{grid-template-columns:repeat(1,minmax(250px,1fr))}}@media (min-width: 3841px){.related-blogs[_ngcontent-ng-c1778523502] .related-blog .parallax-container{min-height:120%;min-width:120%}}@media (max-width: 768px){.related-blogs[_ngcontent-ng-c1778523502] .related-blog .parallax-container .parallax-img{width:auto}}.utils[_ngcontent-ng-c1778523502]{overflow:hidden;margin:36px 0 48px;border-top:2px solid #f4f6f8;padding-top:48px}.utils-inner[_ngcontent-ng-c1778523502]{margin:-24px -12px 0}@media (max-width: 767px){.utils-inner[_ngcontent-ng-c1778523502]{margin:-10px -5px 0}}.utils[_ngcontent-ng-c1778523502] h4[_ngcontent-ng-c1778523502]{margin-bottom:24px;font-size:28px}</style><style ng-app-id="ng">.border-radius[_ngcontent-ng-c434331449]{border-radius:10px}.transition[_ngcontent-ng-c434331449], .transition-all[_ngcontent-ng-c434331449] *[_ngcontent-ng-c434331449]{transition:all .4s ease}.display-none[_ngcontent-ng-c434331449]{display:none!important}.title[_ngcontent-ng-c434331449]{display:flex;flex-wrap:wrap;margin:0}.title.md[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449], .title.lg[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{padding:0 6px}.title.xl[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{padding:0 8px}.title[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{font-family:Sen,sans-serif;color:#3b3952;display:inline-block;line-height:normal;padding:0 3px}@media (max-width: 768px){.title[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{padding:0 4px}}.title[_ngcontent-ng-c434331449] .capitalized[_ngcontent-ng-c434331449]{text-transform:capitalize}.title.default[_ngcontent-ng-c434331449]{color:#3b3952}@media (min-width: 768px){.title.underline.lg[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]:after, .title.underlineBig.lg[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]:after{margin:-18px -8px 0!important}}@media (max-width: 768px){.title.underline.md[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]:after, .title.underlineBig.md[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]:after{height:8px;margin:-10px -6px 0}}.title.underline[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]:after{content:"";height:12px;display:block;background-color:#dad7fa;margin:-14px -6px 0}.title.underlineBig[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]:after{content:"";height:12px;display:block;background-color:#dad7fa;margin:-18px -6px 0}@media (max-width: 992px){.title.underlineBig[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]:after{margin:-14px -6px 0}}.title.blockPrimary[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{color:#fff;background-color:#6862b6;padding:3px 6px}.title.blockSecondary[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{color:#3b3952;background-color:#dad7fa;padding:3px 6px}.title.colored[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{color:#6862b6}.center[_ngcontent-ng-c434331449]{justify-content:center}.left[_ngcontent-ng-c434331449]{margin-left:-6px;justify-content:flex-start}
26.right[_ngcontent-ng-c434331449]{margin-right:-6px;justify-content:flex-end}@media (max-width: 992px){.mobile-center[_ngcontent-ng-c434331449]{justify-content:center}}@media (max-width: 992px){.mobile-left[_ngcontent-ng-c434331449]{margin-left:-6px;justify-content:flex-start}}@media (max-width: 992px){.mobile-right[_ngcontent-ng-c434331449]{margin-right:-6px;justify-content:flex-end}}.sm[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{font-size:20px}@media (max-width: 992px){.sm[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{font-size:16px}}.sm[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{font-size:20px;line-height:normal}@media (max-width: 992px){.sm[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{font-size:16px}}.md[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{font-size:32px}@media (max-width: 992px){.md[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{font-size:20px}}.md[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{font-size:20px;line-height:normal}.lg[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{font-size:48px}@media (max-width: 992px){.lg[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{font-size:32px}}@media (min-width: 993px){.lg[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{padding:3px 8px!important}}.lg[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{font-size:48px;line-height:normal}@media (max-width: 992px){.lg[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{font-size:32px}}.xl[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{font-size:64px}@media (max-width: 992px){.xl[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{font-size:32px}}.xl[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{font-size:48px;line-height:normal}@media (max-width: 992px){.xl[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{font-size:32px}}.uppercase[_ngcontent-ng-c434331449] span[_ngcontent-ng-c434331449]{text-transform:uppercase}.bottom-space-sm[_ngcontent-ng-c434331449]{margin-bottom:24px}.bottom-space-md[_ngcontent-ng-c434331449]{margin-bottom:48px}.bottom-space-lg[_ngcontent-ng-c434331449]{margin-bottom:96px}.bottom-space-xl[_ngcontent-ng-c434331449]{margin-bottom:192px}</style><style ng-app-id="ng">.border-radius[_ngcontent-ng-c3825890317]{border-radius:10px}.transition[_ngcontent-ng-c3825890317], .transition-all[_ngcontent-ng-c3825890317] *[_ngcontent-ng-c3825890317]{transition:all .4s ease}.blog-grid[_ngcontent-ng-c3825890317]{display:grid;grid-gap:48px;margin-top:48px;width:100%}@media (min-width: 993px){.blog-grid[_ngcontent-ng-c3825890317]{grid-template-columns:repeat(auto-fit,minmax(250px,1fr))}}@media (max-width: 992px){.blog-grid[_ngcontent-ng-c3825890317]{grid-template-columns:repeat(1,minmax(250px,1fr))}}@media (max-width: 479px){.blog-grid[_ngcontent-ng-c3825890317]{grid-template-columns:repeat(1,minmax(120px,1fr))}}.list-blogs[_ngcontent-ng-c3825890317]{grid-template-columns:none;grid-gap:24px}</style><style ng-app-id="ng">.border-radius[_ngcontent-ng-c3111289922]{border-radius:10px}.transition[_ngcontent-ng-c3111289922], .transition-all[_ngcontent-ng-c3111289922] *[_ngcontent-ng-c3111289922]{transition:all .4s ease}[_nghost-ng-c3111289922]{position:relative}footer[_ngcontent-ng-c3111289922]{background:linear-gradient(0deg,RGBA(59,57,82,1) 0%,RGBA(48,46,70,1) 100%)}footer[_ngcontent-ng-c3111289922] .darker-bg[_ngcontent-ng-c3111289922]{background-color:RGBA(48,46,70,1)}footer[_ngcontent-ng-c3111289922] a[_ngcontent-ng-c3111289922]{font-weight:400}footer[_ngcontent-ng-c3111289922] h3[_ngcontent-ng-c3111289922]{font-weight:700;font-family:Lato,sans-serif;font-size:20px;padding-bottom:12px}
26footer[_ngcontent-ng-c3111289922] h4[_ngcontent-ng-c3111289922]{font-weight:700;font-family:Lato,sans-serif;font-size:20px;padding-bottom:24px}footer[_ngcontent-ng-c3111289922] a[_ngcontent-ng-c3111289922], footer[_ngcontent-ng-c3111289922] p[_ngcontent-ng-c3111289922], footer[_ngcontent-ng-c3111289922] h3[_ngcontent-ng-c3111289922], footer[_ngcontent-ng-c3111289922] h4[_ngcontent-ng-c3111289922]{color:#fff}footer[_ngcontent-ng-c3111289922] nav[_ngcontent-ng-c3111289922]{display:flex!important;flex-wrap:wrap;padding:96px 0 48px}@media (max-width: 991px){footer[_ngcontent-ng-c3111289922] nav[_ngcontent-ng-c3111289922]{padding-bottom:24px}footer[_ngcontent-ng-c3111289922] nav[_ngcontent-ng-c3111289922] .footer-col[_ngcontent-ng-c3111289922]:not(:first-child){display:none}footer[_ngcontent-ng-c3111289922] nav.expandedMobileMenu[_ngcontent-ng-c3111289922] .footer-col[_ngcontent-ng-c3111289922]:not(:first-child){display:flex}footer[_ngcontent-ng-c3111289922] nav.expandedMobileMenu[_ngcontent-ng-c3111289922] .footer-btn[_ngcontent-ng-c3111289922]:after{transform:rotate(180deg)}}footer[_ngcontent-ng-c3111289922] .inline-icon[_ngcontent-ng-c3111289922]{color:#fff}footer[_ngcontent-ng-c3111289922] .footer-col[_ngcontent-ng-c3111289922]{display:flex;flex-direction:column;align-items:flex-start;width:25%}@media (max-width: 1280px){footer[_ngcontent-ng-c3111289922] .footer-col[_ngcontent-ng-c3111289922]{width:33.3333333333%}footer[_ngcontent-ng-c3111289922] .footer-col.blog-col[_ngcontent-ng-c3111289922]{width:100%;margin-top:48px}footer[_ngcontent-ng-c3111289922] .footer-col.blog-col[_ngcontent-ng-c3111289922] app-small-feed[_ngcontent-ng-c3111289922] af-small-feed[_ngcontent-ng-c3111289922]{width:100%}footer[_ngcontent-ng-c3111289922] .footer-col.blog-col[_ngcontent-ng-c3111289922] app-small-feed[_ngcontent-ng-c3111289922] af-small-feed[_ngcontent-ng-c3111289922] .feed-card{width:100%}footer[_ngcontent-ng-c3111289922] .footer-col.blog-col[_ngcontent-ng-c3111289922] app-small-feed[_ngcontent-ng-c3111289922] af-small-feed[_ngcontent-ng-c3111289922] .feed-card a{height:100%}footer[_ngcontent-ng-c3111289922] .footer-col.blog-col[_ngcontent-ng-c3111289922] app-small-feed[_ngcontent-ng-c3111289922] af-small-feed[_ngcontent-ng-c3111289922] .feed-card img{object-position:center}}@media (max-width: 991px){footer[_ngcontent-ng-c3111289922] .footer-col[_ngcontent-ng-c3111289922]{width:100%;align-items:center;text-align:center}footer[_ngcontent-ng-c3111289922] .footer-col[_ngcontent-ng-c3111289922] + .footer-col[_ngcontent-ng-c3111289922]{margin-top:96px}footer[_ngcontent-ng-c3111289922] .footer-col.blog-col[_ngcontent-ng-c3111289922]{text-align:left}footer[_ngcontent-ng-c3111289922] .footer-col[_ngcontent-ng-c3111289922] h3[_ngcontent-ng-c3111289922]{width:100%}}footer[_ngcontent-ng-c3111289922] .footer-col[_ngcontent-ng-c3111289922] .link[_ngcontent-ng-c3111289922]{margin:12px 0}footer[_ngcontent-ng-c3111289922] .footer-col[_ngcontent-ng-c3111289922] .link[_ngcontent-ng-c3111289922]:last-of-type{margin-bottom:0}footer[_ngcontent-ng-c3111289922] .footer-col[_ngcontent-ng-c3111289922] a.underline[_ngcontent-ng-c3111289922]{border-bottom-color:#f4f6f8;padding-bottom:0}footer[_ngcontent-ng-c3111289922] .social[_ngcontent-ng-c3111289922]{display:flex!important}@media (max-width: 480px){footer[_ngcontent-ng-c3111289922] .social[_ngcontent-ng-c3111289922]{max-width:144px}}@media (min-width: 993px){footer[_ngcontent-ng-c3111289922] .social[_ngcontent-ng-c3111289922]{align-items:flex-start!important;justify-content:center!important}}footer[_ngcontent-ng-c3111289922] .social[_ngcontent-ng-c3111289922] .social-content[_ngcontent-ng-c3111289922]{max-width:142px}footer[_ngcontent-ng-c3111289922] .social[_ngcontent-ng-c3111289922] .social-content[_ngcontent-ng-c3111289922] .based-in[_ngcontent-ng-c3111289922]{margin-bottom:24px;font-size:12px}@media (max-width: 480px){footer[_ngcontent-ng-c3111289922] .social[_ngcontent-ng-c3111289922] .social-content[_ngcontent-ng-c3111289922] .based-in[_ngcontent-ng-c3111289922]{text-align:center}}footer[_ngcontent-ng-c3111289922] .social[_ngcontent-ng-c3111289922] .social-content[_ngcontent-ng-c3111289922] .follow-icons[_ngcontent-ng-c3111289922]{dis
26play:flex!important;flex-direction:row!important;align-items:center!important;justify-content:space-between!important}footer[_ngcontent-ng-c3111289922] .social[_ngcontent-ng-c3111289922] .social-content[_ngcontent-ng-c3111289922] .follow-icons[_ngcontent-ng-c3111289922] .social-icon svg{width:24px}footer[_ngcontent-ng-c3111289922] .contact-section[_ngcontent-ng-c3111289922] .contact-btn[_ngcontent-ng-c3111289922]{color:#fff;border-color:#fff;width:100%}@media (max-width: 480px){footer[_ngcontent-ng-c3111289922] .contact-section[_ngcontent-ng-c3111289922]{text-align:center;width:100%}}footer[_ngcontent-ng-c3111289922] .contact-section[_ngcontent-ng-c3111289922] .contact-row[_ngcontent-ng-c3111289922]{display:flex!important;align-items:center!important}@media (max-width: 480px){footer[_ngcontent-ng-c3111289922] .contact-section[_ngcontent-ng-c3111289922] .contact-row[_ngcontent-ng-c3111289922]{justify-content:center!important}}footer[_ngcontent-ng-c3111289922] .contact-section[_ngcontent-ng-c3111289922] .contact-row[_ngcontent-ng-c3111289922] .contact-icon[_ngcontent-ng-c3111289922]{display:flex!important;margin-right:12px;width:24px}footer[_ngcontent-ng-c3111289922] .contact-section[_ngcontent-ng-c3111289922] .mobile-links[_ngcontent-ng-c3111289922]{display:none!important;margin-bottom:24px}@media (max-width: 480px){footer[_ngcontent-ng-c3111289922] .contact-section[_ngcontent-ng-c3111289922] .mobile-links.visible-mobile[_ngcontent-ng-c3111289922]{display:block!important}}footer[_ngcontent-ng-c3111289922] .legal-section[_ngcontent-ng-c3111289922]{display:flex!important;flex-direction:row!important;align-items:center!important;justify-content:space-between!important;font-size:12px}footer[_ngcontent-ng-c3111289922] .legal-section[_ngcontent-ng-c3111289922] p[_ngcontent-ng-c3111289922], footer[_ngcontent-ng-c3111289922] .legal-section[_ngcontent-ng-c3111289922] .links[_ngcontent-ng-c3111289922] a[_ngcontent-ng-c3111289922]{font-size:16px}@media (min-width: 993px){footer[_ngcontent-ng-c3111289922] .legal-section[_ngcontent-ng-c3111289922]{height:96px}}@media (max-width: 992px){footer[_ngcontent-ng-c3111289922] .legal-section[_ngcontent-ng-c3111289922]{display:flex!important;align-items:center!important;justify-content:center!important;flex-direction:column!important;padding:2vw 0}}footer[_ngcontent-ng-c3111289922] .legal-section[_ngcontent-ng-c3111289922] .links[_ngcontent-ng-c3111289922]{display:flex!important;flex-direction:row!important;align-items:center!important;justify-content:space-between!important}footer[_ngcontent-ng-c3111289922] .legal-section[_ngcontent-ng-c3111289922] .links[_ngcontent-ng-c3111289922] [_ngcontent-ng-c3111289922]:last-child{text-align:right}@media (min-width: 993px){footer[_ngcontent-ng-c3111289922] .legal-section[_ngcontent-ng-c3111289922] .links[_ngcontent-ng-c3111289922]{width:25%}}@media (max-width: 992px){footer[_ngcontent-ng-c3111289922] .legal-section[_ngcontent-ng-c3111289922] .links[_ngcontent-ng-c3111289922]{display:flex!important;align-items:center!important;justify-content:center!important;flex-direction:column!important}}.logo-container[_ngcontent-ng-c3111289922] img[_ngcontent-ng-c3111289922]{position:absolute;z-index:-5;width:0;height:0;min-width:auto;opacity:0}
26.footer-bar[_ngcontent-ng-c3111289922]{display:flex;justify-content:space-between;align-items:center;padding:48px 0}@media (max-width: 991px){.footer-bar[_ngcontent-ng-c3111289922]{flex-direction:column;justify-content:flex-start;padding-bottom:96px}}.logo-section[_ngcontent-ng-c3111289922]{max-width:140px}@media (max-width: 991px){.logo-section[_ngcontent-ng-c3111289922]{display:none}}.logo-section[_ngcontent-ng-c3111289922] .based-in[_ngcontent-ng-c3111289922]{font-family:Sen,sans-serif;font-size:16px}.nav-section[_ngcontent-ng-c3111289922] ul[_ngcontent-ng-c3111289922]{display:flex;align-items:center;margin:-12px;padding-left:0;list-style:none}@media (max-width: 991px){.nav-section[_ngcontent-ng-c3111289922] ul[_ngcontent-ng-c3111289922]{display:block;margin:0;text-align:center}}.nav-section[_ngcontent-ng-c3111289922] ul[_ngcontent-ng-c3111289922] li[_ngcontent-ng-c3111289922]{padding:0 12px}@media (max-width: 991px){.nav-section[_ngcontent-ng-c3111289922] ul[_ngcontent-ng-c3111289922] li[_ngcontent-ng-c3111289922]{padding:0}.nav-section[_ngcontent-ng-c3111289922] ul[_ngcontent-ng-c3111289922] li[_ngcontent-ng-c3111289922] + li[_ngcontent-ng-c3111289922]{margin-top:24px}}.nav-section[_ngcontent-ng-c3111289922] ul[_ngcontent-ng-c3111289922] a[_ngcontent-ng-c3111289922], .nav-section[_ngcontent-ng-c3111289922] ul[_ngcontent-ng-c3111289922] p[_ngcontent-ng-c3111289922]{display:inline-block;font-family:Sen,sans-serif;font-size:16px}.nav-section[_ngcontent-ng-c3111289922] ul[_ngcontent-ng-c3111289922] a[_ngcontent-ng-c3111289922]{border-bottom:1px solid transparent}.nav-section[_ngcontent-ng-c3111289922] ul[_ngcontent-ng-c3111289922] a[_ngcontent-ng-c3111289922]:hover{border-bottom-color:#f4f6f8}.socials-section[_ngcontent-ng-c3111289922]{flex:0 1 142px}@media (max-width: 991px){.socials-section[_ngcontent-ng-c3111289922]{flex:auto;max-width:250px;width:100%;order:-1;margin-bottom:96px}}.socials-section[_ngcontent-ng-c3111289922] .follow-icons[_ngcontent-ng-c3111289922]{display:flex!important;flex-direction:row!important;align-items:center!important;justify-content:space-between!important;width:100%}.socials-section[_ngcontent-ng-c3111289922] .follow-icons[_ngcontent-ng-c3111289922] .social-icon svg{width:24px}@media (max-width: 991px){.socials-section[_ngcontent-ng-c3111289922] .follow-icons[_ngcontent-ng-c3111289922] .social-icon svg{width:40px}}.links-header[_ngcontent-ng-c3111289922]{order:-1;padding-bottom:12px}.show-first[_ngcontent-ng-c3111289922]{order:-1}.contacts[_ngcontent-ng-c3111289922]{padding-left:0;list-style:none}.contacts[_ngcontent-ng-c3111289922] li[_ngcontent-ng-c3111289922] + li[_ngcontent-ng-c3111289922]{margin-top:24px}.contacts[_ngcontent-ng-c3111289922] a[_ngcontent-ng-c3111289922], .contacts[_ngcontent-ng-c3111289922] p[_ngcontent-ng-c3111289922]{display:flex;align-items:center;font-family:Sen,sans-serif;cursor:pointer}.contacts[_ngcontent-ng-c3111289922] a[_ngcontent-ng-c3111289922]:hover span[_ngcontent-ng-c3111289922], .contacts[_ngcontent-ng-c3111289922] p[_ngcontent-ng-c3111289922]:hover span[_ngcontent-ng-c3111289922]{border-bottom-color:#f4f6f8}.contacts[_ngcontent-ng-c3111289922] a[_ngcontent-ng-c3111289922] span[_ngcontent-ng-c3111289922], .contacts[_ngcontent-ng-c3111289922] p[_ngcontent-ng-c3111289922] span[_ngcontent-ng-c3111289922]{border-bottom:1px solid transparent;color:#fff}.contact-icon[_ngcontent-ng-c3111289922]{display:flex;justify-content:center;align-items:center;width:24px;margin-right:24px}.footer-links[_ngcontent-ng-c3111289922] a[_ngcontent-ng-c3111289922]{border-bottom:1px solid transparent}.footer-links[_ngcontent-ng-c3111289922] a[_ngcontent-ng-c3111289922]:hover{border-bottom-color:#f4f6f8}.footer-btn[_ngcontent-ng-c3111289922]{width:100%;margin-top:48px;border:2px solid #302e46;background:none;font-family:Sen,sans-serif;color:#fff}.footer-btn[_ngcontent-ng-c3111289922]:after{content:"";display:inline-block;width:0;height:0;margin-left:12px;border-style:solid;border-width:10px 10px 0 10px;border-color:#ffffff transparent transparent transparent}@media (min-width: 992px){.footer-btn[_ngcontent-ng-c3111289922]{dis
26play:none}}</style><style ng-app-id="ng">.border-radius[_ngcontent-ng-c172377450]{border-radius:10px}.transition[_ngcontent-ng-c172377450], .transition-all[_ngcontent-ng-c172377450] *[_ngcontent-ng-c172377450]{transition:all .4s ease}.small-feed-wrapper[_ngcontent-ng-c172377450]{display:grid;grid-template-columns:repeat(1,1fr);grid-auto-rows:1fr;gap:24px}</style><style ng-app-id="ng">.border-radius[_ngcontent-ng-c1605307905]{border-radius:10px}.transition[_ngcontent-ng-c1605307905], .transition-all[_ngcontent-ng-c1605307905] *[_ngcontent-ng-c1605307905]{transition:all .4s ease}.feed-card[_ngcontent-ng-c1605307905]:hover .feed-card-content[_ngcontent-ng-c1605307905]{background:#302e46}.feed-card[_ngcontent-ng-c1605307905] a[_ngcontent-ng-c1605307905]{display:flex;width:100%;border:2px solid #302e46;box-sizing:border-box;height:100%}.feed-card[_ngcontent-ng-c1605307905] img[_ngcontent-ng-c1605307905]{flex-shrink:0;width:85px;height:100%;object-fit:cover;object-position:-50px;filter:grayscale(.8)}.feed-card[_ngcontent-ng-c1605307905] .feed-card-content[_ngcontent-ng-c1605307905]{flex-grow:1;padding:12px 10px 12px 20px;box-sizing:border-box;transition:background .3s ease;display:flex;align-items:center;justify-content:flex-start}.feed-card[_ngcontent-ng-c1605307905] .feed-card-content[_ngcontent-ng-c1605307905] p[_ngcontent-ng-c1605307905]{max-width:none;font-family:Sen,sans-serif;font-weight:400;color:#fff}</style><style ng-app-id="ng">.border-radius[_ngcontent-ng-c967979157]{border-radius:10px}.transition[_ngcontent-ng-c967979157], .transition-all[_ngcontent-ng-c967979157] *[_ngcontent-ng-c967979157]{transition:all .4s ease}@media (max-width: 992px){.header-padding-top[_ngcontent-ng-c967979157]{padding-top:72px}}@media (min-width: 993px){.header-padding-top.forced-above-tablet[_ngcontent-ng-c967979157]{padding-top:96px}}*[_ngcontent-ng-c967979157]{padding:0;margin:0}body[_ngcontent-ng-c967979157]{font-family:Sen,sans-serif;width:100vw}html[_ngcontent-ng-c967979157]{overflow-y:scroll;overflow-x:hidden}@media (min-width: 481px) and (max-width: 992px){html[_ngcontent-ng-c967979157]{-webkit-overflow-scrolling:touch;scroll-behavior:smooth}}.root-header[_ngcontent-ng-c967979157], h3[_ngcontent-ng-c967979157], h4[_ngcontent-ng-c967979157], app-blog-post[_ngcontent-ng-c967979157] h1[_ngcontent-ng-c967979157], h2[_ngcontent-ng-c967979157], h1[_ngcontent-ng-c967979157]{font-family:Sen,sans-serif}h1[_ngcontent-ng-c967979157], h2[_ngcontent-ng-c967979157], h3[_ngcontent-ng-c967979157], h4[_ngcontent-ng-c967979157]{line-height:1.2}h1[_ngcontent-ng-c967979157]{color:#3b3952;letter-spacing:2px}@media (max-width: 480px){h1[_ngcontent-ng-c967979157]{font-size:32px}}@media (min-width: 480px) and (max-width: 768px){h1[_ngcontent-ng-c967979157]{font-size:48px}}@media (min-width: 768px) and (max-width: 1530px){h1[_ngcontent-ng-c967979157]{font-size:48px}}@media (min-width: 1530px) and (max-width: 3840px){h1[_ngcontent-ng-c967979157]{font-size:64px}}h2[_ngcontent-ng-c967979157]{color:#3b3952}@media (max-width: 480px){h2[_ngcontent-ng-c967979157]{font-size:32px}}@media (min-width: 480px) and (max-width: 768px){h2[_ngcontent-ng-c967979157]{font-size:32px}}@media (min-width: 768px) and (max-width: 1530px){h2[_ngcontent-ng-c967979157]{font-size:32px}}@media (min-width: 1530px) and (max-width: 3840px){h2[_ngcontent-ng-c967979157]{font-size:48px}}h3[_ngcontent-ng-c967979157], h4[_ngcontent-ng-c967979157], app-blog-post[_ngcontent-ng-c967979157] h1[_ngcontent-ng-c967979157]{color:#3b3952}@media (max-width: 480px){h3[_ngcontent-ng-c967979157], h4[_ngcontent-ng-c967979157], app-blog-post[_ngcontent-ng-c967979157] h1[_ngcontent-ng-c967979157]{font-size:20px}}@media (min-width: 480px) and (max-width: 768px){h3[_ngcontent-ng-c967979157], h4[_ngcontent-ng-c967979157], app-blog-post[_ngcontent-ng-c967979157] h1[_ngcontent-ng-c967979157]{font-size:20px}}@media (min-width: 768px) and (max-width: 1530px){h3[_ngcontent-ng-c967979157], h4[_ngcontent-ng-c967979157], app-blog-post[_ngcontent-ng-c967979157] h1[_ngcontent-ng-c967979157]{font-size:32px}}@media (min-width: 1530px) and (max-width: 3840px){h3[_ngcontent-ng-c967979157], h4[_ng
26content-ng-c967979157], app-blog-post[_ngcontent-ng-c967979157] h1[_ngcontent-ng-c967979157]{font-size:32px}}p[_ngcontent-ng-c967979157], li[_ngcontent-ng-c967979157], a[_ngcontent-ng-c967979157], span[_ngcontent-ng-c967979157], h5[_ngcontent-ng-c967979157]{color:#3b3952;font-size:20px;font-family:Lato,sans-serif;line-height:1.5}code[_ngcontent-ng-c967979157]{color:#3b3952;font-size:16px;line-height:1.5}@media (min-width: 1530px){section[_ngcontent-ng-c967979157]{display:flex;align-items:center;justify-content:center}section[_ngcontent-ng-c967979157]:not(.no-expand){min-height:max(100vh,672px)}}section.expand-all[_ngcontent-ng-c967979157]{min-height:max(100vh,672px)}@media (min-width: 993px){section[_ngcontent-ng-c967979157]{padding-top:192px;padding-bottom:192px}section.no-bottom.no-top[_ngcontent-ng-c967979157]{padding-bottom:0!important;padding-top:0!important}section.no-top[_ngcontent-ng-c967979157]{padding-bottom:192px;padding-top:0}section.no-bottom[_ngcontent-ng-c967979157]{padding-bottom:0;padding-top:192px}}@media (max-width: 992px){section[_ngcontent-ng-c967979157]{padding:16vw 0}section.no-bottom.no-top[_ngcontent-ng-c967979157]{padding-bottom:0;padding-top:0}section.no-top[_ngcontent-ng-c967979157]{padding-bottom:16vw;padding-top:0}section.no-bottom[_ngcontent-ng-c967979157]{padding-bottom:0;padding-top:16vw}}.content-parent[_ngcontent-ng-c967979157]{width:100%}@media (max-width: 992px){.reverse-items-mobile[_ngcontent-ng-c967979157]{flex-direction:column-reverse}}header[_ngcontent-ng-c967979157]{transform-style:preserve-3d;display:flex!important;align-items:center!important;justify-content:center!important;position:fixed;width:100%;z-index:9999}@media (min-width: 480px) and (max-width: 991px){header[_ngcontent-ng-c967979157] a[_ngcontent-ng-c967979157], header[_ngcontent-ng-c967979157] button[_ngcontent-ng-c967979157]{font-size:12px}}@media (min-width: 993px){header[_ngcontent-ng-c967979157] a[_ngcontent-ng-c967979157], header[_ngcontent-ng-c967979157] button[_ngcontent-ng-c967979157]{font-size:16.8px}}header[_ngcontent-ng-c967979157] .background[_ngcontent-ng-c967979157]{pointer-events:none;transform-style:preserve-3d;position:absolute;top:0;left:0;height:100%;width:100%;z-index:-1;transition:opacity ease .5s;box-shadow:0 6px 12px #3b395208;background-color:#fff;opacity:.01}@media (max-width: 991px){header[_ngcontent-ng-c967979157] .background.scrolled[_ngcontent-ng-c967979157]{opacity:.99}}@media (min-width: 992px){header[_ngcontent-ng-c967979157] .background.scrolled[_ngcontent-ng-c967979157]{opacity:.99}header[_ngcontent-ng-c967979157] .background.stick-top[_ngcontent-ng-c967979157]{opacity:.01}}@media (max-width: 768px){header[_ngcontent-ng-c967979157]{height:48px}}@media (min-width: 769px){header[_ngcontent-ng-c967979157]{height:96px}}header[_ngcontent-ng-c967979157] .inner[_ngcontent-ng-c967979157]{display:flex!important;flex-direction:row!important;align-items:center!important;justify-content:space-between!important}@media (max-width: 991px){header[_ngcontent-ng-c967979157] .inner[_ngcontent-ng-c967979157]{height:48px}}@media (max-width: 768px){header[_ngcontent-ng-c967979157]{padding:12px 0}}header[_ngcontent-ng-c967979157] .inner[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] a[_ngcontent-ng-c967979157]{color:#3b3952}header[_ngcontent-ng-c967979157] .inner[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] a.active[_ngcontent-ng-c967979157]{color:#6862b6}header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157]{-webkit-user-select:none;user-select:none;position:relative}@media (max-width: 1199px){header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157]{display:flex;flex-direction:column;overflow:auto;padding:96px 0;margin-bottom:-96px}}header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] button[_ngcontent-ng-c967979157]{margin:12px 0 12px 12px;padding:12px 24px}@media (max-width: 1199px){header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] button[_ngcontent-ng-c967979157]{margin:0;border:none;padding:12px;width:100%;color:#000000b0}}header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] a[_ngcontent-ng-c967979157], header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] a[_ngcontent-ng-c967979157]:hover{float:left;text-decoration:none;padding:12px;z-index:1;font-weight:700;line-height:50px;height:100%;letter-spacing:1px;font-family:Sen,sans-serif;display:inline-block;position:relative;text-align:center;cursor:pointer}@media (max-width: 1199px){header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] a[_ngcontent-ng-c967979157], header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] a[_ngcontent-ng-c967979157]:hover{text-align:center;display:block;padding:6px 12px;margin:6px auto}header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] a.active[_ngcontent-ng-c967979157], header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] a[_ngcontent-ng-c967979157]:hover.active{border-bottom:4px solid #6862b6}}@media (min-width: 1201px){header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] a[_ngcontent-ng-c967979157], header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] a[_ngcontent-ng-c967979157]:hover{display:inline-block}}header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] .animation[_ngcontent-ng-c967979157]{position:absolute;height:2px;top:57.6px;transition:.5s ease 0s}header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] a[_ngcontent-ng-c967979157]:nth-child(1).active ~ .animation[_ngcontent-ng-c967979157]{width:calc(var(--header-width1) - (2 * 12px));left:12px;background-color:#6862b6}header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] a[_ngcontent-ng-c967979157]:nth-child(2).active ~ .animation[_ngcontent-ng-c967979157]{width:calc(var(--header-width2) - (2 * 12px));left:calc(12px + var(--header-width1));background-color:#6862b6}header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] a[_ngcontent-ng-c967979157]:nth-child(3).active ~ .animation[_ngcontent-ng-c967979157]{width:calc(var(--header-width3) - (2 * 12px));left:calc(12px + var(--header-width1) + var(--header-width2));background-color:#6862b6}header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] a[_ngcontent-ng-c967979157]:nth-child(4).active ~ .animation[_ngcontent-ng-c967979157]{width:calc(var(--header-width4) - (2 * 12px));left:calc(12px + var(--header-width1) + var(--header-width2) + var(--header-width3));background-color:#6862b6}header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] a[_ngcontent-ng-c967979157]:nth-child(5).active ~ .animation[_ngcontent-ng-c967979157]{width:calc(var(--header-width5) - (2 * 12px));left:calc(12px + var(--header-width1) + var(--header-width2) + var(--header-width3) + var(--header-width4));background-color:#6862b6}header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] a[_ngcontent-ng-c967979157]:nth-child(6).active ~ .animation[_ngcontent-ng-c967979157]{width:calc(var(--header-width6) - (2 * 12px));left:calc(12px + var(--header-width1) + var(--header-width2) + var(--header-width3) + var(--header-width4) + var(--header-width5));background-color:#6862b6}header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] a[_ngcontent-ng-c967979157]:nth-child(7).active ~ .animation[_ngcontent-ng-c967979157]{width:calc(var(--header-width7) - (2 * 12px));left:calc(12px + var(--header-width1) + var(--header-width2) + var(--header-width3) + var(--header-width4) + var(--header-width5) + var(--header-width6));background-color:#6862b6}@media (max-width: 1199px){header[_ngcontent-ng-c967979157] nav[_ngcontent-ng-c967979157] .animation[_ngcontent-ng-c967979157]{display:none}}@media (max-width: 1199px){header[_ngcontent-ng-c967979157] .menu-button[_ngcontent-ng-c967979157]{display:block;display:flex!important;align-items:center!important;justify-content:center!important}}@media (min-width: 1200px){header[_ngcontent-ng-c967979157] .menu-button[_ngcontent-ng-c967979157]{display:none}}header[_ngcontent-ng-c967979157] .menu-button[_ngcontent-ng-c967979157] img[_ngcontent-ng-c967979157]{width:40px}@media (max-width: 1199px){header[_ngcontent-ng-c967979157] .menu-content[_ngcontent-ng-c967979157]{position:fixed;overflow:hidden;background-color:#fff;width:0;height:100vh;
26top:0;left:0}}@media (min-width: 1201px){header[_ngcontent-ng-c967979157] .menu-content[_ngcontent-ng-c967979157]{display:block}}header[_ngcontent-ng-c967979157] .menu-content[_ngcontent-ng-c967979157] .phone-flex-column[_ngcontent-ng-c967979157] button.contact[_ngcontent-ng-c967979157]{font-family:Sen,sans-serif;font-weight:700;letter-spacing:1px}@media (max-width: 1199px){header[_ngcontent-ng-c967979157] .menu-content[_ngcontent-ng-c967979157] .phone-flex-column[_ngcontent-ng-c967979157]{display:flex;flex-flow:column;height:100%}header[_ngcontent-ng-c967979157] .menu-content[_ngcontent-ng-c967979157] .phone-flex-column.nav-elements[_ngcontent-ng-c967979157]{justify-items:center;justify-content:center;margin-top:-96px}}header[_ngcontent-ng-c967979157] .menu-content[_ngcontent-ng-c967979157] .close-menu[_ngcontent-ng-c967979157]{width:30px;margin-left:auto;margin-right:5%;margin-top:18px;z-index:10}@media (min-width: 1200px){header[_ngcontent-ng-c967979157] .menu-content[_ngcontent-ng-c967979157] .close-menu[_ngcontent-ng-c967979157]{display:none}}@media (max-width: 1199px){header[_ngcontent-ng-c967979157] .menu-content.slide-menu[_ngcontent-ng-c967979157]{width:100vw}header[_ngcontent-ng-c967979157] .menu-content.slide-menu[_ngcontent-ng-c967979157] a[_ngcontent-ng-c967979157], header[_ngcontent-ng-c967979157] .menu-content.slide-menu[_ngcontent-ng-c967979157] button[_ngcontent-ng-c967979157]{font-size:32px}}.logo[_ngcontent-ng-c967979157]{min-width:96px;min-height:50px;height:100%;-webkit-mask:url(/assets/images/branding/all-front-logo.svg) no-repeat center;mask:url(/assets/images/branding/all-front-logo.svg) no-repeat center;background-color:#3b3952}.logo[_ngcontent-ng-c967979157] img[_ngcontent-ng-c967979157]{width:0;height:0;min-width:auto;opacity:0}
26</style><meta property="og:type" content="website"><meta property="og:title" content="How to take your Angular apps offline | All Front"><meta property="twitter:title" content="How to take your Angular apps offline | All Front"><meta name="url" property="og:url" content="ssr"><meta name="twitter:description" content="Discover everything you need to know about Service Workers and how to add them to enable offline capabilities for your Angular apps."><meta name="og:image" property="og:image" content="https://cdn.sanity.io/images/68sc9ce8/production/08433bd56482e454af1e6f6f751071502f3b5ab9-728x419.png?w=1200&fit=max"><meta name="twitter:card" content="summary_large_image"><meta name="twitter:image" content="https://cdn.sanity.io/images/68sc9ce8/production/08433bd56482e454af1e6f6f751071502f3b5ab9-728x419.png?w=1200&fit=max"><meta name="author" content="Gabriel Stellini">
26<script type="application/ld+json" id="json-ld">{"@context":"https://schema.org","@type":"BlogPosting","mainEntityOfPage":{"@type":"WebPage","@id":"https://allfront.io/blog/how-to-take-your-angular-apps-offline"},"headline":"How to take your Angular apps offline","image":{"@type":"ImageObject","url":"https://cdn.sanity.io/images/68sc9ce8/production/08433bd56482e454af1e6f6f751071502f3b5ab9-728x419.png?w=1200&fit=max","height":400,"width":400},"datePublished":"2020-10-18T11:01:00.000Z","dateModified":"2020-10-18T11:01:00.000Z","author":{"@type":"Person","name":"Gabriel Stellini","image":"https://cdn.sanity.io/images/68sc9ce8/production/fa58038f1eb37081db809350ceb6c48eedeb0945-200x200.jpg","url":"https://www.linkedin.com/in/gabriel-stellini/"},"publisher":{"@type":"Organization","name":"All Front","logo":{"@type":"ImageObject","url":"https://allfront.io/assets/images/branding/all-front-logo-purple.svg","width":200,"height":200}},"description":"Discover everything you need to know about Service Workers and how to add them to enable offline capabilities for your Angular apps."}</script>
26<style ng-app-id="ng">.border-radius[_ngcontent-ng-c217727897]{border-radius:10px}.transition[_ngcontent-ng-c217727897], .transition-all[_ngcontent-ng-c217727897] *[_ngcontent-ng-c217727897]{transition:all .4s ease}.btn-filter[_ngcontent-ng-c217727897]{display:none;margin:0}@media (max-width: 480px){.btn-filter[_ngcontent-ng-c217727897]{display:flex}}[_nghost-ng-c217727897]{display:flex;margin:24px 12px 0}.tab-button[_ngcontent-ng-c217727897]{cursor:pointer;text-decoration:underline;display:inline-flex;justify-content:center;align-items:center;min-height:48px;border:2px solid #f4f6f8;border-radius:24px;padding:3px 27px;background:#f4f6f8;box-sizing:border-box;font-family:Lato,sans-serif;font-size:20px;font-weight:700;color:#3b3952;transition:border-color .3s ease,background .3s ease}.tab-button[_ngcontent-ng-c217727897]:hover{border-color:#000;background:#ffffff;text-decoration:none}@media (max-width: 992px){.tab-button[_ngcontent-ng-c217727897]{text-align:center;width:100%}}.tab-button.active[_ngcontent-ng-c217727897]{border-color:#000;background:#ffffff;text-decoration:none}</style><style ng-app-id="ng">.border-radius[_ngcontent-ng-c2736361305]{border-radius:10px}.transition[_ngcontent-ng-c2736361305], .transition-all[_ngcontent-ng-c2736361305] *[_ngcontent-ng-c2736361305]{transition:all .4s ease}.blog-card[_ngcontent-ng-c2736361305]{font-weight:initial!important;height:100%;display:flex;border:1px solid #f4f6f8}.blog-card[_ngcontent-ng-c2736361305] .card-content[_ngcontent-ng-c2736361305]{padding:24px;display:flex;flex-direction:column;flex:1;background-color:#f4f6f8}.blog-card[_ngcontent-ng-c2736361305] .card-content[_ngcontent-ng-c2736361305] .title[_ngcontent-ng-c2736361305]{margin-bottom:12px}.blog-card[_ngcontent-ng-c2736361305] .card-content[_ngcontent-ng-c2736361305] .description[_ngcontent-ng-c2736361305]{display:flex;flex-direction:column;justify-content:flex-end;flex:1}.blog-card[_ngcontent-ng-c2736361305]:not(.horizontal){flex-direction:column}@media (min-width: 769px){.blog-card[_ngcontent-ng-c2736361305]:not(.horizontal) .card-content[_ngcontent-ng-c2736361305] .title[_ngcontent-ng-c2736361305]{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;text-overflow:ellipsis}}.blog-card.horizontal[_ngcontent-ng-c2736361305]{border-width:2px}.blog-card.horizontal[_ngcontent-ng-c2736361305] .card-content[_ngcontent-ng-c2736361305]{background:#ffffff}.blog-card.horizontal[_ngcontent-ng-c2736361305] .img-header[_ngcontent-ng-c2736361305]{position:relative;overflow:hidden;height:calc(100% + 4px);margin:-2px 0 -2px -2px}.blog-card.horizontal[_ngcontent-ng-c2736361305] .img-header[_ngcontent-ng-c2736361305] > *[_ngcontent-ng-c2736361305]{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:140%;margin-left:-45px}.blog-card.horizontal[_ngcontent-ng-c2736361305] .publish-date[_ngcontent-ng-c2736361305]{margin-top:6px;font-size:20px}@media (max-width: 480px){.blog-card.horizontal[_ngcontent-ng-c2736361305]{flex-direction:column}}@media (min-width: 481px){.blog-card.horizontal[_ngcontent-ng-c2736361305] .img-header[_ngcontent-ng-c2736361305]{width:288px}}.blog-card[_ngcontent-ng-c2736361305] .img-header[_ngcontent-ng-c2736361305]{overflow:hidden;height:288px;margin:-1px -1px 0}.blog-card[_ngcontent-ng-c2736361305] .img-header[_ngcontent-ng-c2736361305] af-parallax[_ngcontent-ng-c2736361305]{display:flex;place-content:center center;height:100%}.blog-card[_ngcontent-ng-c2736361305] .img-header[_ngcontent-ng-c2736361305] af-parallax[_ngcontent-ng-c2736361305] .parallax-container{align-self:center;display:flex;place-content:center center}.blog-card[_ngcontent-ng-c2736361305] .img-header[_ngcontent-ng-c2736361305] af-parallax[_ngcontent-ng-c2736361305] picture[_ngcontent-ng-c2736361305]{display:flex}@media (min-width: 1530px){.blog-card[_ngcontent-ng-c2736361305] .img-header[_ngcontent-ng-c2736361305] af-parallax[_ngcontent-ng-c2736361305] picture[_ngcontent-ng-c2736361305]{width:100%}}.blog-card[_ngcontent-ng-c2736361305] .img-header[_ngcontent-ng-c2736361305] af-parallax[_ngcontent-ng-c2736361305] img[_ngcontent-ng-c2736361305]{height:auto}@media (max-width: 768px){.blog-card[_ngcontent-ng-c2736361305] .img-header[_ngcontent-ng-c2736361305] af-parallax[_ngcontent-ng-c2736361305] img[_ngcontent-ng-c2736361305]{min-height:298px}}@media (max-width: 480px){.blog-card[_ngcontent-ng-c2736361305] .img-header[_ngcontent-ng-c2736361305] af-parallax[_ngcontent-ng-c2736361305] img[_ngcontent-ng-c2736361305]{transform:translate(-15%)}}.blog-card[_ngcontent-ng-c2736361305] p[_ngcontent-ng-c2736361305]{max-width:100%}.blog-card[_ngcontent-ng-c2736361305]:hover{cursor:pointer}.blog-card[_ngcontent-ng-c2736361305]:focus{outline:none}.publish-date[_ngcontent-ng-c2736361305]{margin-right:12px;color:#827ea6}.actions[_ngcontent-ng-c2736361305]{display:flex;justify-content:space-between;align-items:center;margin-top:24px}.actions.button[_ngcontent-ng-c2736361305]{margin-top:12px}
26.card-button[_ngcontent-ng-c2736361305]{width:100%}</style><style ng-app-id="ng">[_nghost-ng-c1992147294]{transform-style:preserve-3d}[_nghost-ng-c1992147294] *[_ngcontent-ng-c1992147294]{transform-style:inherit;transform:rotate(.01deg)}.background-wrapper[_ngcontent-ng-c1992147294]{height:200vh;width:200vw;z-index:-1;position:absolute;top:-50vh;left:-50vw;pointer-events:none}.parallax-container[_ngcontent-ng-c1992147294]{transition:all .5s ease-out}</style>
26<script data-cfasync="false" nonce="f398f973-5d02-40b9-b6ee-2aeef55a6c0e">try{(function(w,d){!function(kP,kQ,kR,kS){if(kP.zaraz)console.error("zaraz is loaded twice");else{kP[kR]=kP[kR]||{};kP[kR].executed=[];kP.zaraz={deferred:[],listeners:[]};kP.zaraz._v="20";kP.zaraz._n="f398f973-5d02-40b9-b6ee-2aeef55a6c0e";kP.zaraz.q=[];kP.zaraz._f=function(kT){return async function(){var kU=Array.prototype.slice.call(arguments);kP.zaraz.q.push({m:kT,a:kU})}};for(const kV of["track","set","debug"])kP.zaraz[kV]=kP.zaraz._f(kV);kP.zaraz.init=()=>{var kW=kQ.getElementsByTagName(kS)[0],kX=kQ.createElement(kS),kY=kQ.getElementsByTagName("title")[0];kY&&(kP[kR].t=kQ.getElementsByTagName("title")[0].text);kP[kR].x=Math.random();kP[kR].w=kP.screen.width;kP[kR].h=kP.screen.height;kP[kR].j=kP.innerHeight;kP[kR].e=kP.innerWidth;kP[kR].l=kP.location.href;kP[kR].r=kQ.referrer;kP[kR].k=kP.screen.colorDepth;kP[kR].n=kQ.characterSet;kP[kR].o=(new Date).getTimezoneOffset();if(kP.dataLayer)for(const kZ of Object.entries(Object.entries(dataLayer).reduce((k$,la)=>({...k$[1],...la[1]}),{})))zaraz.set(kZ[0],kZ[1],{scope:"page"});kP[kR].q=[];for(;kP.zaraz.q.length;){const lb=kP.zaraz.q.shift();kP[kR].q.push(lb)}kX.defer=!0;for(const lc of[localStorage,sessionStorage])Object.keys(lc||{}).filter(le=>le.startsWith("_zaraz_")).forEach(ld=>{try{kP[kR]["z_"+ld.slice(7)]=JSON.parse(lc.getItem(ld))}catch{kP[kR]["z_"+ld.slice(7)]=lc.getItem(ld)}});kX.referrerPolicy="origin";kX.src="/bundle.s.js?z="+btoa(encodeURIComponent(JSON.stringify(kP[kR])));kW.parentNode.insertBefore(kX,kW)};["complete","interactive"].includes(kQ.readyState)?zaraz.init():kP.addEventListener("DOMContentLoaded",zaraz.init)}}(w,d,"zarazData","script");window.zaraz._p=async kH=>new Promise(kI=>{if(kH){kH.e&&kH.e.forEach(kJ=>{try{const kK=d.querySelector("script[nonce]"),kL=kK?.nonce||kK?.getAttribute("nonce"),kM=d.createElement("script");kL&&(kM.nonce=kL);kM.innerHTML=kJ;kM.onload=()=>{d.head.removeChild(kM)};d.head.appendChild(kM)}catch(kN){console.error(`Error executing script: ${kJ}\n`,kN)}});Promise.allSettled((kH.f||[]).map(kO=>fetch(kO[0],kO[1])))}kI()});zaraz._p({"e":["(function(w,d){})(window,document)"]});})(window,document)}catch(e){throw fetch("/api/t"),e;};</script>
26</head> 27 <body><!--nghm--> 28 <app-header _nghost-ng-c967979157 ng-version="16.2.0" ngh="14" ng-server-context="ssg|hydration"><header _ngcontent-ng-c967979157 class="content-parent ng-star-inserted"><div _ngcontent-ng-c967979157 class="inner content"><a _ngcontent-ng-c967979157 aria-label="main" routerlink="/" class="main" href="/"><div _ngcontent-ng-c967979157 class="logo"><img _ngcontent-ng-c967979157 src="assets/images/branding/all-front-logo.svg" alt="All Front Logo" loading="eager" class="logo"></div></a><div _ngcontent-ng-c967979157 class="burger"><div _ngcontent-ng-c967979157 actionname="Burger opened" destinationroute="Same route" class="menu-button"><img _ngcontent-ng-c967979157 loading="eager" src="assets/icons/menu_icon.svg" alt="menu_icon" width="40" height="40"></div><div _ngcontent-ng-c967979157 class="menu-content"><div _ngcontent-ng-c967979157 class="phone-flex-column"><div _ngcontent-ng-c967979157 actionname="Burger closed" destinationroute="Same route" class="close-menu"><img _ngcontent-ng-c967979157 loading="eager" src="assets/icons/close_icon.svg" alt="close icon" width="30" height="30" fetchpriority="low"></div><div _ngcontent-ng-c967979157 class="phone-flex-column nav-elements"><nav _ngcontent-ng-c967979157 class="ng-star-inserted"><a _ngcontent-ng-c967979157 actionname="Header Link" routerlinkactive="active" href="/" class="ng-star-inserted"> Home </a><a _ngcontent-ng-c967979157 actionname="Header Link" routerlinkactive="active" href="/quick-start/angular/" class="ng-star-inserted"> Angular Developers </a><a _ngcontent-ng-c967979157 actionname="Header Link" routerlinkactive="active" href="/about-us/" class="ng-star-inserted"> About us </a><a _ngcontent-ng-c967979157 actionname="Header Link" routerlinkactive="active" href="/services/" class="ng-star-inserted"> Services </a><a _ngcontent-ng-c967979157 actionname="Header Link" routerlinkactive="active" href="/cases/" class="ng-star-inserted"> Case Studies </a><a _ngcontent-ng-c967979157 actionname="Header Link" routerlinkactive="active" href="/blog/" class="ng-star-inserted"> Blog </a><!----><button _ngcontent-ng-c967979157 routerlink="/contact" actionname="Header Button" class="outline contact" tabindex="0"> Contact Us </button><div _ngcontent-ng-c967979157 class="animation"></div></nav><!----></div></div></div></div></div><div _ngcontent-ng-c967979157 class="background"></div></header><!----></app-header> 29 <div class="content-parent sticky-content"> 30 <app-sticky-outlet></app-sticky-outlet> 31 </div> 32 33 <div class="smoovy-container"> 34 <div class="smoovy-wrapper" style="overflow: scroll; height: 100vh; perspective: 5000px;"> 35 <app-root _nghost-ng-c1206372886 class="ng-tns-c1206372886-0" ng-version="16.2.0" ngh="12|13" ng-server-context="ssg|hydration"><div _ngcontent-ng-c1206372886 class="ng-tns-c1206372886-0 ng-trigger ng-trigger-triggerName route-wrapper preserve3d"><router-outlet _ngcontent-ng-c1206372886 class="ng-tns-c1206372886-0"></router-outlet><app-blog-post _nghost-ng-c1778523502 class="ng-star-inserted" ngh="11"><section _ngcontent-ng-c1778523502 class="landing content-parent"><div _ngcontent-ng-c1778523502 class="header-padding-top"></div><div _ngcontent-ng-c1778523502 class="content"><div _ngcontent-ng-c1778523502 class="blog-post-content ng-star-inserted"><div _ngcontent-ng-c1778523502 class="blog-post-content rich-text"><af-title _ngcontent-ng-c1778523502 align="left" textsize="md" textstyle="default" headingtype="h1" class="blog-post-content-header" _nghost-ng-c434331449 ngh="0"><h1 _ngcontent-ng-c434331449 class="title md left default ng-star-inserted"><span _ngcontent-ng-c434331449 class="ng-star-inserted"> How </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> to </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> take </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> your </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> Angular </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> apps </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> offline </span><!----></h1><!----><!----><!----></af-title><p _ngcontent-ng-c1778523502 class="blog-desc">Everything you need to know about Service Workers</p><app-sanity-image _ngcontent-ng-c1778523502 loading="eager" _nghost-ng-c1861816981 ngh="1"><img _ngcontent-ng-c1861816981 sizes="(max-width: 400px) 400px, 36 (max-width: 800px) 1000px, 37 (max-width: 1200px) 1500px, 38 2000px" style="aspect-ratio: 1.7374701670644392; height: auto; display: block;" srcset="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB1klEQVQokW2Q2WrbUBgG9d59g9K73peuoQttKZgYSgIpdUPixrEdx1viXV4lL5IsHa3WlCMrwbF7YK4OfMP8Sltzac9cWjOX+6lLcyJoTAT1saA2ElRHgtuhQ0V1uFEdygOHUt+m2Le57tkUejZXXZu/nXWC0p97SHq6R1fz6OwJ7iZiKxkL6juCRDIUBxJlvPKRjJY+w6WPuvAY7Er0Q0lNtcjXJ1xUR5S7BrWRQzWVKLoVINHMgJkZMDUCJsZ/JItUMvcot2Z8yZzx9vMxv4stmmOHu/RciuGErJyQpR2ykKxD5utUYj2VDOYuDdUgV2zx4WuW10ffyRUatGdOUiNLFNuLWEvcCMuNMEWEIQ4lA90hV1H5+POaT9kLTs5LFG7b3KsLhgv3sUbxgg1uivA3OH6ElNg7Et30+HPT58W7E569/Mb7zDn17hT
38N9J6USJQwipEEUYwfbhJ2JZYIafQ1jrJ5nr865s2PHMWGim66mCJk/2TKJo5J2MREKQ8SOa5qBqeXdTK/SuQrHdTZCksESYmzV2KJCCWOY/aRgjiGtfAp1AecXtZoqxrCC5OShxpvp0aeS7IdhGRgy3ZUvunC4uyqSbHRR7h+8v9YkZZsBdtTSf4BRdI4aeiiCggAAAAASUVORK5CYII="></app-sanity-image><p _ngcontent-ng-c1778523502 class="blog-img-caption">Unplug your app from the internet!</p><div _ngcontent-ng-c1778523502 class="markdown"><p>Weâve all been there â you are filling up a form, click submit, and oops â something went wrong with your connection. Now you need to refresh the page and fill everything up again because the app broke ð¤</p><p>The reality of this is that intermittent connections are prevalent worldwideâ as of 2017, <a href="https://www.ispreview.co.uk/index.php/2017/08/uswitch-study-claims-43-million-uk-adults-blighted-poor-broadband.html" rel="nofollow" target="_blank">43 million brits have reported unreliable copper connections</a> . In Germany, a decision to stick with copper over fibre-optic connections in the 1980s has left the internet infrastructure there to be one of the more unreliable ones in the EU <a href="https://www.euractiv.com/section/broadband/news/german-german-dispute-over-fibre-optic-funding-to-be-decided-in-brussels/" rel="nofollow" target="_blank">(at least until optic-fibre is implemented in 2025)</a> .</p><p>As you probably already know, thereâs a way to improve experiences for offline users. Letâs take a simple todo list as an example â ideally, offline users can still take new notes which get synced once you get back online.</p><blockquote>TL;DR: If you donât want an explanation, you can view the <a href="https://github.com/DEMO-4/offline-todo-list" rel="nofollow" target="_blank">github link directly</a> . Explanations are grouped by branch.</blockquote><h4 id="what-s-next">Whatâs next?</h4><p>To take your apps offline, you first need to add a service worker. At its core, a service worker is a Javascript file that intercepts and caches requests. The cool thing about it is that it runs separate from the main thread, so it can get in touch with a server while the app is not running. You can use this to add push notifications to your app, for example.</p><h4 id="where-s-the-catch">Whereâs the catch?</h4><p>Since service workers are designed to be run completely asynchronously from the main app, there are a few things you need to keep in mind:</p><ul><li><strong>No access to local storage</strong> â the official docs by google state that this is <a href="https://developers.google.com/web/ilt/pwa/introduction-to-service-worker#:~:text=A%20service%20worker%20is%20a,cache%2C%20and%20delivering%20push%20messages." rel="nofollow" target="_blank">due to its asynchronous design</a> . You can use something like <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API" rel="nofollow" target="_blank">indexedDB</a> are instead.</li><li><strong>Service workers only run over HTTPS</strong> . Weâre mentioning this only because the default <a href="https://allfront.io/quick-start/angular/ " rel="dofollow" target="_blank">Angular development</a> environment runs over HTTP.</li><li>Service workers can become idle and <strong>can also restart</strong> when the browser deems fit. This means that you need to <strong>persist your state.</strong></li><li><strong>No access to the DOM</strong> due to separation of concerns in the design</li></ul><h4 id="the-good-news">ð° The good news</h4><p>One of the many advantages of converting your app to a PWA is that you also auto-magically <a href="https://allfront.io/blog/creating-a-pwa-with-angular/" rel="dofollow" target="_blank">add a service worker during the installation</a> !</p><p>While converting your app into a PWA may seem counter-intuitive, keep in mind that PWAs are installable â so your app works while itâs offline.</p><p>And more good news! Angularâs implementation of service workers already caches your requests. You have these nifty features right out of the box:</p><ul><li>Cached files are <strong>tied to your app</strong> version â so if you update your app on the server, the clientâs app wonât suddenly break.</li><li><strong>New versions</strong> of the app are <strong>downloaded in the background</strong> . This means that your site viewers will view the latest version of their app after revisiting the page. You can also hook onto refresh events â which can be a powerful way to handle app updates</li><li>Service workers <strong>only download changed resources</strong> and try to conserve bandwidth when possible</li></ul><h4 id="forms-are-hard">ð¶ Forms are hard</h4><p>
38Hereâs where things get spicy. Service workers primarily cache incoming requests, not outgoing requests. This means that each app will need to build its own implementation of form syncing. Letâs jump right in!</p><p>You donât have time to waste, <a href="https://github.com/gabrielstellini/serviceworker-example/tree/feature/basic-list" rel="nofollow" target="_blank">so letâs start off with all the boring bits taken care of</a> . The linked demo includes a simple json backend server (json-server), with a pwa todo list.</p><p>Please note that due to the deployment limitations on stackblitz, this demo needs to be run locally. This is due to a multitude of reasons â the normal âng-serveâ does not work with service workers, and a backend needs to be available on a separate port. To view the service worker, you need to run the provided â <strong>start:service-worker</strong> â command.</p><p>It should also be noted that hot reload does not work when youâre using the â <strong>start:service-worker</strong> â command</p><blockquote>If you have any questions or issues any of the steps, please refer to the Q&A at the bottom of the article.</blockquote><p>If everything is working as expected, you should be able to see the service worker in the development tools, and your app should have an installation prompt (the small plus icon on the right of the URL bar in the case of chrome)</p><img [object object] loading="lazy" src="https://cdn.sanity.io/images/68sc9ce8/production/d8d0a60ca204785148089b4752fc6679451a3dcc-3508x1828.png?w=1200&fit=max"><p><strong>Whatâs next?</strong></p><p>The first step is to detect whether the user is online or offline. The window object has some nifty events which we can quickly attach to an <a href="https://medium.com/all-front/what-is-rxjs-8a46220d7f6d" rel="nofollow" target="_blank">rxjs subject which you can read more on here</a> .</p><p></p><pre class="javascript"><code><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span> 39<span class="token keyword">import</span> <span class="token punctuation">{</span>BehaviorSubject<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'rxjs'</span><span class="token punctuation">;</span> 40 41@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">{</span> 42 <span class="token literal-property property">providedIn</span><span class="token operator">:</span> <span class="token string">'root'</span> 43<span class="token punctuation">}</span><span class="token punctuation">)</span> 44<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">OnlineService</span> <span class="token punctuation">{</span> 45 isOnline$ <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BehaviorSubject</span><span class="token operator"><</span>boolean<span class="token operator">></span><span class="token punctuation">(</span>window<span class="token punctuation">.</span>navigator<span class="token punctuation">.</span>onLine<span class="token punctuation">)</span><span class="token punctuation">;</span> 46 47 <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 48 <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">listenToOnlineStatus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 49 <span class="token punctuation">}</span> 50 51 <span class="token function">listenToOnlineStatus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span> 52 window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'online'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>isOnline$<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 53 window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'offline'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>isOnline$<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token boolean">
53false</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 54 <span class="token punctuation">}</span> 55<span class="token punctuation">}</span></code></pre><p>The next step is to initialize the indexDB. It is important that you do NOT store your data in variables, as your app may restart at any point in time.</p><p>There are several wrappers we can use to wrap the browserâs native indexDB in Angular; For this example, we will be using <a href="https://www.npmjs.com/package/ngx-indexed-db" rel="nofollow" target="_blank">ngx-indexed-db</a> .</p><p></p><pre class="javascript"><code><span class="token operator">...</span> 56<span class="token keyword">const</span> <span class="token literal-property property">dbConfig</span><span class="token operator">:</span> DBConfig <span class="token operator">=</span> <span class="token punctuation">{</span> 57 <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'MyDb'</span><span class="token punctuation">,</span> 58 <span class="token literal-property property">version</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> 59 <span class="token literal-property property">objectStoresMeta</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">{</span> 60 <span class="token literal-property property">store</span><span class="token operator">:</span> <span class="token string">'tasks'</span><span class="token punctuation">,</span> 61 <span class="token literal-property property">storeConfig</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">keyPath</span><span class="token operator">:</span> <span class="token string">'id'</span><span class="token punctuation">,</span> <span class="token literal-property property">autoIncrement</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> 62 <span class="token literal-property property">storeSchema</span><span class="token operator">:</span> <span class="token punctuation">[</span> 63 <span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'name'</span><span class="token punctuation">,</span> <span class="token literal-property property">keypath</span><span class="token operator">:</span> <span class="token string">'name'</span><span class="token punctuation">,</span> <span class="token literal-property property">options</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">unique</span><span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> 64 <span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'isComplete'</span><span class="token punctuation">,</span> <span class="token literal-property property">keypath</span><span class="token operator">:</span> <span class="token string">'isComplete'</span><span class="token punctuation">,</span> <span class="token literal-property property">options</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">unique</span><span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> 65 <span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'isOnServer'</span><span class="token punctuation">,</span> <span class="token literal-property property">keypath</span><span class="token operator">:</span> <span class="token string">'isOnServer'</span><span class="token punctuation">,</span> <span class="token literal-property property">options</span><span class="token operator">:</span> <span class="token punctuation">{</span><span class="token literal-property property">unique</span><span class="token operator">:</span> <span class="token boolean">
65false</span><span class="token punctuation">}</span><span class="token punctuation">}</span> 66 <span class="token punctuation">]</span> 67 <span class="token punctuation">}</span><span class="token punctuation">]</span> 68<span class="token punctuation">}</span><span class="token punctuation">;</span> 69 70@<span class="token function">NgModule</span><span class="token punctuation">(</span><span class="token punctuation">{</span> 71 <span class="token literal-property property">declarations</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token operator">...</span><span class="token punctuation">]</span><span class="token punctuation">,</span> 72 <span class="token literal-property property">imports</span><span class="token operator">:</span> <span class="token punctuation">[</span> 73 <span class="token operator">...</span> 74 NgxIndexedDBModule<span class="token punctuation">.</span><span class="token function">forRoot</span><span class="token punctuation">(</span>dbConfig<span class="token punctuation">)</span><span class="token punctuation">,</span> 75 ServiceWorkerModule<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span><span class="token string">'ngsw-worker.js'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">enabled</span><span class="token operator">:</span> environment<span class="token punctuation">.</span>production <span class="token punctuation">}</span><span class="token punctuation">)</span> 76 <span class="token punctuation">]</span><span class="token punctuation">,</span> 77 <span class="token literal-property property">providers</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> 78 <span class="token literal-property property">bootstrap</span><span class="token operator">:</span> <span class="token punctuation">[</span>AppComponent<span class="token punctuation">]</span> 79<span class="token punctuation">}</span><span class="token punctuation">)</span> 80<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">AppModule</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span></code></pre><p>We now need to rework our task service to sync when online and use the dbService when youâre offline. To keep the demo simple, we:</p><ul><li>Disable the delete and editing buttons when the user is offline â this means that I donât need to keep track of records to delete or updates when syncing</li><li>One service is being used to handle API communication and offline handling â by keeping the logic together, it is easier to understand (but not as maintainable)</li></ul><p>You can see what the final code looks like by checking out <a href="https://github.com/DEMO-4/offline-todo-list/blob/feature/add-offline/src/app/services/tasks.service.ts" rel="nofollow" target="_blank">our git repo</a> .</p><h4 id="key-takeaways">Key takeaways</h4><p>Making your app work offline is <em>expensive</em>. Adding offline functionality required almost 3x as much code in the service (without accounting for tests).</p><p>Hereâs a quick summary of what needs to be done:</p><ul><li>Make use of a database on the front end and sync with your remote data</li><li>Handle data migrations for new upgrades on your browser database without losing the old sync data</li><li>Keep your backend APIs backwards-compatible for possible syncs on older app versions</li><li>Handling conflicts: what happens if two offline users edit the same record? Thereâs two main approaches â either avoiding this situation entirely by making sure that shared data canât be modified offline, or merging records.<
80/li><li>Handling of Angular app versioning â while you can allow angular to update your app ânaturallyâ in the background, you may want to just push users to the latest version directly</li><li>Error handling of incomplete syncing</li><li>Reworking of all your services to switch between 2 sources of truth</li><li>Integration works better if you are using angular-redux or ngrx.</li></ul><p>This may look like a lot of work â because it is. While adding a service worker and converting your app to a PWA is an easier task with strong performance benefits, making your app work offline requires getting your hands dirtier and modifying critical parts of your appâs architecture.</p><p>You will also need significant backend rework, and possible optimization of APIs to handle the very large syncing requests.</p><h4 id="q-amp-a">Q&A</h4><p>Whatâs the â<em>start:service-workerâ</em> command doing?</p><p>â¢ï¸ The command adds your device to a global botnet which is using brute force attacks to hack nuclear codes.</p><p>Just kidding â The command concurrently:</p><ul><li>builds the production version of the project in the dist folder</li><li>Deploys the built code on port 8080 using http-server â since we canât use ng-serve, we need a new way to view the deployed files</li><li>Runs a simple json backend âthe backend needs to be separate from the app for this demo to be realistic</li></ul><p>My changes arenât showing up, HELP!</p><p>Keep in mind that the angular service workers prioritize app speed and stability over showing you the latest changes. What this means to you is that the service worker downloads the latest version of your app when you open it, but does not switch to it immediately. You need to refresh/hard-refresh your page to show the latest app version.</p><p>If using the latest version of the app is absolutely necessary, you can <a href="https://angular.io/guide/service-worker-communications" rel="nofollow" target="_blank">listen to the service worker events</a> and manually call window.reload().</p><p></p><pre class="javascript"><code>@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span> 81<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">LogUpdateService</span> <span class="token punctuation">{</span> 82 83 <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">updates</span><span class="token operator">:</span> SwUpdate</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 84 updates<span class="token punctuation">.</span>available<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token parameter">event</span> <span class="token operator">=></span> <span class="token punctuation">{</span> 85 console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'current version is'</span><span class="token punctuation">,</span> event<span class="token punctuation">.</span>current<span class="token punctuation">)</span><span class="token punctuation">;</span> 86 console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'available version is'</span><span class="token punctuation">,</span> event<span class="token punctuation">.</span>available<span class="token punctuation">)</span><span class="token punctuation">;</span> 87 <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 88 updates<span class="token punctuation">.</span>activated<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token parameter">event</span> <span class="token operator">=></span> <span class="token punctuation">{</span> 89 console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'old version was'</span><span class="token punctuation">,</span> event<span class="token punctuation">.</span>previous<span class="token punctuation">)</span><span class="token punctuation">;</span> 90 console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'new version is'</span><span class="token punctuation">,</span> event<span class="token punctuation">.</span>current<span class="token punctuation">)</span><span class="token punctuation">;</span> 91 <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 92 <span class="token punctuation">}</span> 93<span class="token punctuation">}</span></code></pre><p>Any other development tips?</p><p>1. You should keep in mind that when using developer tools, the service worker is kept running in the background and never restarts.</p><p>2. If you want to remove the service worker entirely, remove the <strong><em>ngsw.json</em></strong> file. When the Angular service worker canât find this file, it deactivates itself as a fail-safe.</p><p>3. When switching your app versions on the frontend, you might want to migrate your data. You can read more about migration handling in the <a href="https://www.npmjs.com/package/ngx-indexed-db" rel="nofollow" target="_blank">official ngx-indexed-db quickstart guide</a></p><p>
934. It is recommended to keep a versioning system in your API â this way you can segregate older APIs from newer ones and make it easier to deprecate the old APIs.</p><p>5. Consider adding some form of logging in your app to track the app versions out there in the wild. This will make future decisions on API deprecations easier.</p><p><strong>Are pages really being cached? How can I confirm this?</strong></p><p>Files cached by the service worker can be viewed in the âcache storageâ section. As can be seen in the screenshot below, the main and the index files are being stored in this specific cache. The files stored here depend largely on the contents in your <strong>ngsw-config</strong> file. You can read more about configuring it in <a href="https://angular.io/guide/service-worker-config" rel="nofollow" target="_blank">the official docs</a> .</p><p></p><img [object object] loading="lazy" src="https://cdn.sanity.io/images/68sc9ce8/production/c157dfc482c5376efab6ededac008279044abbfa-2940x1954.png?w=1200&fit=max"><p>If you hit refresh, you can also see that your app should load much faster but the todo app is small in itself, so you can fix this by slowing down your connection speed manually with the chrome dev tools:</p><p></p><img [object object] loading="lazy" src="https://cdn.sanity.io/images/68sc9ce8/production/946f1d8fbcd7d78ce244ca5571309583b6ba4888-3239x2351.png?w=1200&fit=max"><h6><em>The connection was slowed down to âSlow 3Gâ. The tasks API request took 2s, but the main bundle was loaded in 10ms due to the service worker caching!</em></h6><p></p></div></div><div _ngcontent-ng-c1778523502 class="utils ng-star-inserted"><h4 _ngcontent-ng-c1778523502>Tags</h4><div _ngcontent-ng-c1778523502 class="utils-inner"><app-tags _ngcontent-ng-c1778523502 _nghost-ng-c859226629 ngh="2"><app-tag _ngcontent-ng-c859226629 actionname="Learn more" routerlink="/blog" _nghost-ng-c217727897 tabindex="0" class="ng-star-inserted" ngh="1"><div _ngcontent-ng-c217727897 class="tab-button">Angular</div></app-tag><app-tag _ngcontent-ng-c859226629 actionname="Learn more" routerlink="/blog" _nghost-ng-c217727897 tabindex="0" class="ng-star-inserted" ngh="1"><div _ngcontent-ng-c217727897 class="tab-button">Business</div></app-tag><app-tag _ngcontent-ng-c859226629 actionname="Learn more" routerlink="/blog" _nghost-ng-c217727897 tabindex="0" class="ng-star-inserted" ngh="1"><div _ngcontent-ng-c217727897 class="tab-button">Service Workers</div></app-tag><!----></app-tags></div></div><!----><div _ngcontent-ng-c1778523502 class="meta"><img _ngcontent-ng-c1778523502 loading="lazy" width="72px" height="72px" alt="menu_icon" src="https://cdn.sanity.io/images/68sc9ce8/production/fa58038f1eb37081db809350ceb6c48eedeb0945-200x200.jpg"><div _ngcontent-ng-c1778523502 class="publication-details"><h5 _ngcontent-ng-c1778523502>Gabriel Stellini</h5><p _ngcontent-ng-c1778523502>18th October 2020</p></div></div><div _ngcontent-ng-c1778523502 id="replybox"></div></div><!----></div></section><section _ngcontent-ng-c1778523502 class="content-parent no-expand no-top"><div _ngcontent-ng-c1778523502 class="content related-blogs"><af-title _ngcontent-ng-c1778523502 headingtype="h2" align="center" textsize="lg" textstyle="default" text="Have a look at some other blogs" class="centered" _nghost-ng-c434331449 ngh="3"><!----><h2 _ngcontent-ng-c434331449 class="title lg center default ng-star-inserted"><span _ngcontent-ng-c434331449 class="ng-star-inserted"> Have </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> a </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> look </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> at </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> some </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> other </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> blogs </span><!----></h2><!----><!----></af-title><app-blog-cards _ngcontent-ng-c1778523502 gridclass="related" cardclass="related-blog" _nghost-ng-c3825890317 ngh="8"><div _ngcontent-ng-c3825890317 class="blog-grid related"><app-blog-card _ngcontent-ng-c3825890317 _nghost-ng-c2736361305 class="related-blog ng-star-inserted" ngh="6"><div _ngcontent-ng-c2736361305 class="blog-card ng-star-inserted" tabindex="0"><div _ngcontent-ng-c2736361305 class="img-header"><af-parallax _ngcontent-ng-c2736361305 _nghost-ng-c1992147294 ngh="4"><div _ngcontent-ng-c1992147294 class="parallax-container" style="transform: translate3d(0px, 0px, 0px);"><picture _ngcontent-ng-c2736361305><source _ngcontent-ng-c2736361305 type="image/webp" srcset="https://cdn.sanity.io/images/68sc9ce8/production/7f8ec92d4
931a89ca3211791b8a043f1efd4ea1fdd-727x420.png" class="ng-star-inserted"><!----><img _ngcontent-ng-c2736361305 class="parallax-img" src="https://cdn.sanity.io/images/68sc9ce8/production/7f8ec92d41a89ca3211791b8a043f1efd4ea1fdd-727x420.png" alt="proof of concept vs prototype vs mvp" loading="lazy"></picture><div _ngcontent-ng-c1992147294 class="background-wrapper" style="background: transparent;"></div></div></af-parallax></div><div _ngcontent-ng-c2736361305 class="card-content"><div _ngcontent-ng-c2736361305 class="title"><af-title _ngcontent-ng-c2736361305 headingtype="h2" align="left" textsize="md" textstyle="default" _nghost-ng-c434331449 ngh="5"><!----><h2 _ngcontent-ng-c434331449 class="title md left default ng-star-inserted"><span _ngcontent-ng-c434331449 class="ng-star-inserted"> Proof </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> of </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> Concept </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> vs </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> Prototype </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> vs </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> MVP: </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> Differences </span><!----></h2><!----><!----></af-title></div><div _ngcontent-ng-c2736361305 class="description"><p _ngcontent-ng-c2736361305 class="ng-star-inserted">What's the difference between a Proof of Concept, a Prototype and an MVP? When to use which? Check out our article for a full comparison.</p><!----><div _ngcontent-ng-c2736361305 class="actions"><p _ngcontent-ng-c2736361305 class="publish-date ng-star-inserted"> 5th February 2025 </p><!----><!----></div></div></div></div><!----></app-blog-card><app-blog-card _ngcontent-ng-c3825890317 _nghost-ng-c2736361305 class="related-blog ng-star-inserted" ngh="6"><div _ngcontent-ng-c2736361305 class="blog-card ng-star-inserted" tabindex="0"><div _ngcontent-ng-c2736361305 class="img-header"><af-parallax _ngcontent-ng-c2736361305 _nghost-ng-c1992147294 ngh="4"><div _ngcontent-ng-c1992147294 class="parallax-container" style="transform: translate3d(0px, 0px, 0px);"><picture _ngcontent-ng-c2736361305><source _ngcontent-ng-c2736361305 type="image/webp" srcset="https://cdn.sanity.io/images/68sc9ce8/production/3914d167ea02822a5bb1a2e0ce34349da6dbb5f2-740x419.png" class="ng-star-inserted"><!----><img _ngcontent-ng-c2736361305 class="parallax-img" src="https://cdn.sanity.io/images/68sc9ce8/production/3914d167ea02822a5bb1a2e0ce34349da6dbb5f2-740x419.png" alt="mocking-in-angular-faking-a-graphql-backend" loading="lazy"></picture><div _ngcontent-ng-c1992147294 class="background-wrapper" style="background: transparent;"></div></div></af-parallax></div><div _ngcontent-ng-c2736361305 class="card-content"><div _ngcontent-ng-c2736361305 class="title"><af-title _ngcontent-ng-c2736361305 headingtype="h2" align="left" textsize="md" textstyle="default" _nghost-ng-c434331449 ngh="3"><!----><h2 _ngcontent-ng-c434331449 class="title md left default ng-star-inserted"><span _ngcontent-ng-c434331449 class="ng-star-inserted"> Mocking </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> in </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> Angular: </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> Faking </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> a </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> GraphQL </span><span _ngcontent-ng-c434331449 class="ng-star-inserted">
93 backend </span><!----></h2><!----><!----></af-title></div><div _ngcontent-ng-c2736361305 class="description"><p _ngcontent-ng-c2736361305 class="ng-star-inserted">Test in isolation by mocking - when is it useful and how do we mock a GraphQL backend. Read on and take a deeper dive into intercepting GQL requests.</p><!----><div _ngcontent-ng-c2736361305 class="actions"><p _ngcontent-ng-c2736361305 class="publish-date ng-star-inserted"> 20th November 2019 </p><!----><!----></div></div></div></div><!----></app-blog-card><app-blog-card _ngcontent-ng-c3825890317 _nghost-ng-c2736361305 class="related-blog ng-star-inserted" ngh="6"><div _ngcontent-ng-c2736361305 class="blog-card ng-star-inserted" tabindex="0"><div _ngcontent-ng-c2736361305 class="img-header"><af-parallax _ngcontent-ng-c2736361305 _nghost-ng-c1992147294 ngh="4"><div _ngcontent-ng-c1992147294 class="parallax-container" style="transform: translate3d(0px, 0px, 0px);"><picture _ngcontent-ng-c2736361305><source _ngcontent-ng-c2736361305 type="image/webp" srcset="https://cdn.sanity.io/images/68sc9ce8/production/a0192438464fea4f4b43ddc4de0180957fe61b8b-727x420.png" class="ng-star-inserted"><!----><img _ngcontent-ng-c2736361305 class="parallax-img" src="https://cdn.sanity.io/images/68sc9ce8/production/a0192438464fea4f4b43ddc4de0180957fe61b8b-727x420.png" alt="what-comes-after-an-mvp" loading="lazy"></picture><div _ngcontent-ng-c1992147294 class="background-wrapper" style="background: transparent;"></div></div></af-parallax></div><div _ngcontent-ng-c2736361305 class="card-content"><div _ngcontent-ng-c2736361305 class="title"><af-title _ngcontent-ng-c2736361305 headingtype="h2" align="left" textsize="md" textstyle="default" _nghost-ng-c434331449 ngh="7"><!----><h2 _ngcontent-ng-c434331449 class="title md left default ng-star-inserted"><span _ngcontent-ng-c434331449 class="ng-star-inserted"> From </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> Viable </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> to </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> Marketable: </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> What </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> comes </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> after </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> an </span><span _ngcontent-ng-c434331449 class="ng-star-inserted"> MVP? </span><!----></h2><!----><!----></af-title></div><div _ngcontent-ng-c2736361305 class="description"><p _ngcontent-ng-c2736361305 class="ng-star-inserted">If you've launched your MVP and are wondering "What next?â, this is for you. Learn how to measure and scale your MVP, and what comes after an MVP.</p><!----><div _ngcontent-ng-c2736361305 class="actions"><p _ngcontent-ng-c2736361305 class="publish-date ng-star-inserted"> 5th October 2023 </p><!----><!----></div></div></div></div><!----></app-blog-card><!----></div></app-blog-cards></div></section><app-footer _ngcontent-ng-c1778523502 _nghost-ng-c3111289922 ngh="10"><footer _ngcontent-ng-c3111289922 class="content-parent"><div _ngcontent-ng-c3111289922 class="content"><div _ngcontent-ng-c3111289922 class="footer-content"><nav _ngcontent-ng-c3111289922 class><div _ngcontent-ng-c3111289922 class="footer-col"><h4 _ngcontent-ng-c3111289922>Get in Touch</h4><ul _ngcontent-ng-c3111289922 class="contacts"><li _ngcontent-ng-c3111289922><a _ngcontent-ng-c3111289922 href="mailto:[email protected]"><i _ngcontent-ng-c3111289922 class="contact-icon"><svg _ngcontent-ng-c3111289922="" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><defs _ngcontent-ng-c3111289922=""><style _ngcontent-ng-c3111289922=""> .a-envelope-white { fill: none; } .b-envelope-white { fill: #fff; } </style></defs><path _ngcontent-ng-c3111289922="" d="M0,0H24V24H0Z" class="a-envelope-white"/><path _ngcontent-ng-c3111289922="" d="M20,4H4A2,2,0,0,0,2.01,6L2,18a2.006,2.006,0,0,0,2,2H20a2.006,2.006,0,0,0,2-2V6A2.006,2.006,0,0,0,20,4Zm0,4-8,5L4,8V6l8,5,8-5Z" class="b-envelope-white"/></svg></i><span _ngcontent-ng-c3111289922>[email protected]</span></a></li><li _ngcontent-ng-c3111289922>
93<a _ngcontent-ng-c3111289922 routerlink="/contact" routerlinkactive="underline" actionname="Footer Link" href="/contact/"><i _ngcontent-ng-c3111289922 class="contact-icon"><svg _ngcontent-ng-c3111289922="" xmlns="http://www.w3.org/2000/svg" width="21" height="18" viewBox="0 0 21 18"><defs _ngcontent-ng-c3111289922=""><style _ngcontent-ng-c3111289922=""> .a-msg-white { fill: #fff; } </style></defs><path _ngcontent-ng-c3111289922="" d="M2.01,21,23,12,2.01,3,2,10l15,2L2,14Z" transform="translate(-2 -3)" class="a-msg-white"/></svg></i><span _ngcontent-ng-c3111289922>Send us a message</span></a></li><li _ngcontent-ng-c3111289922 class="ng-star-inserted"><a _ngcontent-ng-c3111289922 routerlink="/meeting/development" routerlinkactive="underline" actionname="Footer Link" href="/meeting/development/"><i _ngcontent-ng-c3111289922 class="contact-icon"><svg _ngcontent-ng-c3111289922="" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><defs _ngcontent-ng-c3111289922=""><style _ngcontent-ng-c3111289922=""> .a-meeting-white { fill: none; } .b-meeting-white { fill: #fff; } </style></defs><path _ngcontent-ng-c3111289922="" d="M0,0H24V24H0Z" class="a-meeting-white"/><path _ngcontent-ng-c3111289922="" d="M17,10.5V7a1,1,0,0,0-1-1H4A1,1,0,0,0,3,7V17a1,1,0,0,0,1,1H16a1,1,0,0,0,1-1V13.5l4,4V6.5Z" class="b-meeting-white"/></svg></i><span _ngcontent-ng-c3111289922>Set up a meeting</span></a></li><!----></ul></div><div _ngcontent-ng-c3111289922 class="footer-col footer-links"><h3 _ngcontent-ng-c3111289922>Menu</h3><div _ngcontent-ng-c3111289922 class="link ng-star-inserted"><a _ngcontent-ng-c3111289922 routerlinkactive="underline" actionname="Footer Link" href="/" class="ng-star-inserted"> Home </a><!----><!----></div><div _ngcontent-ng-c3111289922 class="link ng-star-inserted"><a _ngcontent-ng-c3111289922 routerlinkactive="underline" actionname="Footer Link" href="/about-us/" class="ng-star-inserted"> About us </a><!----><!----></div><div _ngcontent-ng-c3111289922 class="link ng-star-inserted"><a _ngcontent-ng-c3111289922 routerlinkactive="underline" actionname="Footer Link" href="/services/" class="ng-star-inserted"> Services </a><!----><!----></div><div _ngcontent-ng-c3111289922 class="link ng-star-inserted"><a _ngcontent-ng-c3111289922 routerlinkactive="underline" actionname="Footer Link" href="/cases/" class="ng-star-inserted"> Case studies </a><!----><!----></div><div _ngcontent-ng-c3111289922 class="link ng-star-inserted"><a _ngcontent-ng-c3111289922 routerlinkactive="underline" actionname="Footer Link" href="/blog/" class="ng-star-inserted"> Blog </a><!----><!----></div><div _ngcontent-ng-c3111289922 class="link ng-star-inserted"><a _ngcontent-ng-c3111289922 routerlinkactive="underline" actionname="Footer Link" href="/careers/" class="ng-star-inserted"> Careers </a><!----><!----></div><!----></div><div _ngcontent-ng-c3111289922 class="footer-col footer-links"><h3 _ngcontent-ng-c3111289922 class="links-header">Services</h3><div _ngcontent-ng-c3111289922 class="link ng-star-inserted"><a _ngcontent-ng-c3111289922 routerlinkactive="underline" actionname="Footer Link" href="/quick-start/angular/" class="ng-star-inserted"> Hire Angular Developers </a><!----><!----></div><div _ngcontent-ng-c3111289922 class="link ng-star-inserted"><a _ngcontent-ng-c3111289922 routerlinkactive="underline" actionname="Footer Link" href="/services/web-development/" class="ng-star-inserted"> Web App Development </a><!----><!----></div><div _ngcontent-ng-c3111289922 class="link ng-star-inserted"><a _ngcontent-ng-c3111289922 routerlinkactive="underline" actionname="Footer Link" href="/services/design/" class="ng-star-inserted"> UX & UI Product Design </a><!----><!----></div><div _ngcontent-ng-c3111289922 class="link ng-star-inserted"><a _ngcontent-ng-c3111289922 routerlinkactive="underline" actionname="Footer Link" href="/fintech/" class="ng-star-inserted"> Fintech </a><!----><!----></div><div _ngcontent-ng-c3111289922 class="link ng-star-inserted"><a _ngcontent-ng-c3111289922 routerlinkactive="underline" actionname="Footer Link" href="/travel/" class="ng-star-inserted"> Travel </a><!----><!----></div><div _ngcontent-ng-c3111289922 class="link ng-star-inserted"><a _ngcontent-ng-c3111289922 routerlinkactive="underline" actionname="Footer Link" href="/pitch/" class="ng-star-inserted"> Pitch </a><!----><!----></div><!----></div><div _ngcontent-ng-c3111289922 class="footer-col blog-col"><h4 _ngcontent-ng-c3111289922>Blog</h4><app-small-feed _ngcontent-ng-c3111289922 _nghost-ng-c172377450 ngh="1"><af-small-feed _ngcontent-ng-c172377450 class="small-feed-wrapper" _nghost-ng-c1605307905 ngh="9"><div _ngcontent-ng-c1605307905 class="feed-card ng-star-inserted"><a _ngcontent-ng-c1605307905 href="/blog/proof-of-concept-vs-prototype-vs-mvp/"><picture _ngcontent-ng-c1605307905><source _ngcontent-ng-c1605307905 type="image/webp" srcset="https://cdn.sanity.io/images/68sc9ce8/production/7f8ec92d4
931a89ca3211791b8a043f1efd4ea1fdd-727x420.png"><img _ngcontent-ng-c1605307905 loading="lazy" srcset="https://cdn.sanity.io/images/68sc9ce8/production/7f8ec92d41a89ca3211791b8a043f1efd4ea1fdd-727x420.png" alt="Proof of Concept vs Prototype vs MVP: Differences"></picture><div _ngcontent-ng-c1605307905 class="feed-card-content"><p _ngcontent-ng-c1605307905 class="ellipsis-2-lines">Proof of Concept vs Prototype vs MVP: Differences</p></div></a></div><div _ngcontent-ng-c1605307905 class="feed-card ng-star-inserted"><a _ngcontent-ng-c1605307905 href="/blog/web-app-vs-website/"><picture _ngcontent-ng-c1605307905><source _ngcontent-ng-c1605307905 type="image/webp" srcset="https://cdn.sanity.io/images/68sc9ce8/production/4dd492e4e51dbf40a833ab391b7e620c4d371773-727x420.png"><img _ngcontent-ng-c1605307905 loading="lazy" srcset="https://cdn.sanity.io/images/68sc9ce8/production/4dd492e4e51dbf40a833ab391b7e620c4d371773-727x420.png" alt="Web App vs Website Explained: Full Comparison"></picture><div _ngcontent-ng-c1605307905 class="feed-card-content"><p _ngcontent-ng-c1605307905 class="ellipsis-2-lines">Web App vs Website Explained: Full Comparison</p></div></a></div><div _ngcontent-ng-c1605307905 class="feed-card ng-star-inserted"><a _ngcontent-ng-c1605307905 href="/blog/experience-design/"><picture _ngcontent-ng-c1605307905><source _ngcontent-ng-c1605307905 type="image/webp" srcset="https://cdn.sanity.io/images/68sc9ce8/production/fcf178a59aa731afb823beff01c8eabf34e89b4b-727x420.png"><img _ngcontent-ng-c1605307905 loading="lazy" srcset="https://cdn.sanity.io/images/68sc9ce8/production/fcf178a59aa731afb823beff01c8eabf34e89b4b-727x420.png" alt="What is Experience Design, and Why It Matters"></picture><div _ngcontent-ng-c1605307905 class="feed-card-content"><p _ngcontent-ng-c1605307905 class="ellipsis-2-lines">What is Experience Design, and Why It Matters</p></div></a></div><!----></af-small-feed></app-small-feed></div><button _ngcontent-ng-c3111289922 class="footer-btn"> Show menu </button></nav></div></div><div _ngcontent-ng-c3111289922 class="darker-bg"><div _ngcontent-ng-c3111289922 class="content"><div _ngcontent-ng-c3111289922 class="footer-bar"><div _ngcontent-ng-c3111289922 class="logo-section"><div _ngcontent-ng-c3111289922 class="logo-container"><div _ngcontent-ng-c3111289922 inlinesvg="assets/images/branding/all-front-logo.svg" class="logo inline-icon"></div><!----><img _ngcontent-ng-c3111289922 src="assets/images/branding/all-front-logo.svg" alt="Financial Software Development - All Front" class="logo" loading="lazy"></div><p _ngcontent-ng-c3111289922 class="based-in">Based in Malta</p></div><div _ngcontent-ng-c3111289922 class="nav-section"><ul _ngcontent-ng-c3111289922><li _ngcontent-ng-c3111289922><a _ngcontent-ng-c3111289922 routerlink="/legal/terms" routerlinkactive="underline" actionname="Footer Link" rel="nofollow" href="/legal/terms/"> Terms and Conditions </a></li><li _ngcontent-ng-c3111289922><a _ngcontent-ng-c3111289922 routerlink="/legal/cookies-policy" routerlinkactive="underline" actionname="Footer Link" rel="nofollow" href="/legal/cookies-policy/"> Cookies Policy </a></li><li _ngcontent-ng-c3111289922><p _ngcontent-ng-c3111289922>All Rights Reserved, 2025.</p></li></ul></div><div _ngcontent-ng-c3111289922 class="socials-section"><div _ngcontent-ng-c3111289922 class="follow-icons"><a _ngcontent-ng-c3111289922 href="https://twitter.com/Allfront1" target="_blank" rel="noreferrer" actionname="External link under envelope" destinationroute="Link to Twitter"><div _ngcontent-ng-c3111289922 class="social-icon inline-icon"><svg _ngcontent-ng-c3111289922="" xmlns="http://www.w3.org/2000/svg" width="31.994" height="26" viewBox="0 0 31.994 26"><path _ngcontent-ng-c3111289922="" id="iconmonstr-twitter-1" d="M31.993,5.326a13.1,13.1,0,0,1-3.77,1.033A6.575,6.575,0,0,0,31.11,2.728a13.149,13.149,0,0,1-4.168,1.593,6.568,6.568,0,0,0-11.186,5.985A18.634,18.634,0,0,1,2.228,3.449a6.572,6.572,0,0,0,2.03,8.764,6.536,6.536,0,0,1-2.971-.821A6.569,6.569,0,0,0,6.551,17.91a6.579,6.579,0,0,1-2.965.112A6.569,6.569,0,0,0,9.718,22.58,13.2,13.2,0,0,1,0,25.3a18.582,18.582,0,0,0,10.062,2.949c12.187,0,19.072-10.293,18.656-19.524a13.364,13.364,0,0,0,3.275-3.4Z" transform="translate(0 -2.248)" fill="#fff"/></svg></div></a><a _ngcontent-ng-c3111289922 href="https://medium.com/all-front" target="_blank" rel="noreferrer" actionname="External link under envelope" destinationroute="Link to Medium"><div _ngcontent-ng-c3111289922 class="social-icon inline-icon"><svg _ngcontent-ng-c3111289922="" height="38.788" viewBox="0 0 46.961 38.788" width="46.961" xmlns="http://www.w3.org/2000/svg"><g _ngcontent-ng-c3111289922=""><title _ngcontent-ng-c3111289922="">Medium</title><path _ngcontent-ng-c3111289922="" fill="currentColor" d="M43.408,38.225l3.553,3.43v.114H28.939v-.114l3.618-3.43a1.39,1.39,0,0,0,.482-1.317V14.978a23.087,23.087,0,0,1,.16-3.251L21.045,42.208H20.9L8.312,14.166c-.274-.7-.354-.748-.531-1.219V31.332a6.451,6.451,0,0,0,.6,3.593l5.064,6.73v.114H0v-.114l5.064-6.746a6.468,6.468,0,0,0,.594-3.576V11.012a3.964,3.964,0,0,0-.836-2.78l-3.585-4.7V3.42H14.1L24.871,27.171,34.357,3.42H46.945v.114L43.408,7.517a1.406,1.406,0,0,0-.547,1.366V36.908a1.307,1.307,0,0,0,.547,1.317Z" transform="translate(0 -3.42)"/></g></svg></div></a><a _ngcontent-ng-c3111289922 href="https://dribbble.com/allfront/" target="_blank" rel="noreferrer" actionname="External link under envelope" destinationroute="Link to Dribble"><div _ngcontent-ng-c3111289922 class="social-icon inline-icon"><svg _ngcontent-ng-c3111289922="" height="39.141" viewBox="0 0 39.141 39.141" width="39.141" xmlns="http://www.w3.org/2000/svg"><g _ngcontent-ng-c3111289922=""><title _ngcontent-ng-c3111289922="">Dribble</title><path _ngcontent-ng-c3111289922="" fill="currentColor" d="M0,19.57A19.171,19.171,0,0,1,2.622,9.746,19.432,19.432,0,0,1,9.746,2.622,19.171,19.171,0,0,1,19.57,0a19.171,19.171,0,0,1,9.824,2.622,19.431,19.431,0,0,1,7.124,7.124,19.171,19.171,0,0,1,2.622,9.824,19.171,19.171,0,0,1-2.622,9.824,19.431,19.431,0,0,1-7.124,7.124,19.171,19.171,0,0,1-9.824,2.622,19.171,19.171,0,0,1-9.824-2.622,19.432,19.432,0,0,1-7.124-7.124A19.171,19.171,0,0,1,0,19.57Zm3.249,0a15.781,15.781,0,0,0,4.11,10.764,22.4,22.4,0,0,1,5.949-7.006,19.458,19.458,0,0,1,7.946-4.188q-.587-1.37-1.135-2.466a47.557,47.557,0,0,1-14.56,2.153q-1.526,0-2.27-.039,0,.157-.02.391T3.249,19.57Zm.509-4.031q.861.078,2.544.078a42.956,42.956,0,0,0,12.408-1.761,39.632,39.632,0,0,0-6.536-8.807,15.889,15.889,0,0,0-5.3,4.345A16.711,16.711,0,0,0,3.758,15.539ZM9.589,32.448A15.994,15.994,0,0,0,25.324,34.8,57.473,57.473,0,0,0,22.271,21.84a16.693,16.693,0,0,0-7.261,3.953A22.2,22.2,0,0,0,9.589,32.448ZM15.578,3.8a41.594,41.594,0,0,1,6.38,8.885,19.493,19.493,0,0,0,8.024-5.675A15.866,15.866,0,0,0,19.57,3.249,15.024,15.024,0,0,0,15.578,3.8Zm7.789,11.664q.587,1.252,1.331,3.17,2.9-.274,6.3-.274,2.427,0,4.814.117A15.714,15.714,0,0,0,31.978,9Q29.434,12.8,23.367,15.461Zm2.309,5.91a56.179,56.179,0,0,1,2.7,11.9,16.331,16.331,0,0,0,5.049-5.127,15.809,15.809,0,0,0,2.348-6.771q-2.857-.2-5.206-.2Q28.416,21.175,25.676,21.371Z"/></g></svg></div></a><a _ngcontent-ng-c3111289922 href="https://www.linkedin.com/company/all-front/" target="_blank" rel="noreferrer" actionname="External link under envelope" destinationroute="Link to LinkedIn"><div _ngcontent-ng-c3111289922 class="social-icon inline-icon"><svg _ngcontent-ng-c3111289922="" height="38.932" viewBox="0 0 40.741 38.932" width="40.741" xmlns="http://www.w3.org/2000/svg"><g _ngcontent-ng-c3111289922=""><title _ngcontent-ng-c3111289922="">LinkedIn</title><path _ngcontent-ng-c3111289922="" fill="white" d="M0,9.522A4.235,4.235,0,0,1,1.376,6.278,5.049,5.049,0,0,1,4.955,5a4.9,4.9,0,0,1,3.5,1.258A4.436,4.436,0,0,1,9.831,9.64a4.141,4.141,0,0,1-1.337,3.146,5.059,5.059,0,0,1-3.618,1.3H4.837a4.813,4.813,0,0,1-3.5-1.3A4.358,4.358,0,0,1,0,9.522Zm.511,34.41V17.663h8.73V43.932Zm13.567,0h8.73V29.264a5.119,5.119,0,0,1,1.986-4.385,4.273,4.273,0,0,1,2.812-.924q4.4,0,4.4,5.938V43.932h8.73V28.87q0-5.82-2.753-8.829a9.39,9.39,0,0,0-7.275-3.008,8.924,8.924,0,0,0-7.9,4.365v.079h-.039l.039-.079V17.663h-8.73q.079,1.258.079,7.826T14.078,43.932Z" transform="translate(0 -5)"/></g></svg></div></a></div></div></div></div></div></footer></app-footer></app-blog-post><!----></div></app-root><!----> 94 </div> 95 </div> 96
96<script type="text/javascript"> 97 // if (window.innerWidth < 768) { 98 setTimeout(() => { 99 console.log("Centering appScale element"); 100 try { 101 document.querySelectorAll("app-scaler").forEach((element) => { 102 const { contentWidth, contentHeight } = JSON.parse( 103 element.getAttribute("data-hydrate-inputs") 104 ); 105 106 // Order is intentional. Doing this after getBoundingClientRect will cause the element to be resized to the wrong size. 107 element.firstChild.style["visibility"] = `visible`; 108 element.firstChild.style["display"] = `inline-block`; 109 const parentRect = 110 element.parentElement.parentElement.getBoundingClientRect(); 111 112 const scale = Math.min( 113 parentRect.width / contentWidth, 114 parentRect.height / contentHeight 115 ); 116 console.log( 117 parentRect.width, 118 contentWidth, 119 parentRect.height, 120 contentHeight 121 ); 122 element.firstChild.style["transform"] = `scale(${scale})`; 123 // console.log("Centered appScale element", scale); 124 }); 125 } catch (e) { 126 console.log("Failed to center laptop"); 127 } 128 }, 0); 129 // } 130 </script>
130 131
131<script src="runtime.caefa6bea1d9a43e.js" type="module"></script>
131<script src="polyfills.a01a67b21c056f62.js" type="module"></script>
131<script src="main.571cb48115ee7079.js" type="module"></script>
131 132
133<script id="ng-state" type="application/json">{"tag-summary":[{"title":"Angular"},{"title":"PWA"},{"title":"Android"},{"title":"SEO"},{"title":"React"},{"title":"Frontend"},{"title":"Business"}],"reviews-summary":[{"author":"Kim Callender","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/224a9856f3f1a3570c2d4f55fdde7d6af6337387-100x100.jpg","link":null,"position":"CEO, SoloTrvlr","review":"David and team are as close as you can get to an in-house development team. They have consistently gone above and beyond to deliver a quality product that is both technically sound, and puts our user needs front and center. I truly could not ask for a better development partner in building out our product!","stars":5},{"author":"Roger Francis","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/0f8226a6dccf920f2c44819f62bbfc1e09de1a45-100x100.jpg","link":null,"position":"CTO, Moblox","review":"The most impressive aspect of the All Front team is their ability to become highly productive very quickly with a complex set of business requirements.","stars":5},{"author":"Philipp Schuler","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/d812bdc388288a706592f0cc6a4a4df68bdd6a75-100x100.jpg","link":null,"position":"Project Manager, Fusonic","review":"Theyâve identified with our goals and now see our success as their own.","stars":5},{"author":"Nate Wright","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/9ba2d2b6b0c9978fed3cc4d793745fdf74b16668-48x48.jpg","link":"https://clutch.co/profile/all-front#review-1809292","position":"Chief Strategist, B2B Bandits","review":"Working with All Front revealed the power of interactive prototypes for cross-team communication and coordination","stars":5},{"author":"Will Mease","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/3d5c2ab756f3dacb7e77ead4c4d3e0d673aaa5b8-70x70.webp","link":"https://clutch.co/profile/all-front#review-1804271","position":"Founder, Weave Social","review":"Having All Front on board helps us unlock an unlimited scale point","stars":5},{"author":"Peter Anderson","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/3a42017d638ad2f69b7684ef06096f728f43c69e-80x80.jpg","link":"https://clutch.co/profile/all-front#review-1798657","position":"CTO, Forest Holidays","review":"They were very quick in finding a solution that worked for us","stars":4.5}],"single-bloghow-to-take-your-angular-apps-offline":[{"_id":"65e33703-8214-4a61-a4cb-513296192321","authorAvatar":"https://cdn.sanity.io/images/68sc9ce8/production/fa58038f1eb37081db809350ceb6c48eedeb0945-200x200.jpg","body":[{"_key":"f9038bc8d641","_type":"block","children":[{"_key":"5861ff26e97b0","_type":"span","marks":[],"text":"Weâve all been there â you are filling up a form, click submit, and oops â something went wrong with your connection. Now you need to refresh the page and fill everything up again because the app broke ð¤"}],"markDefs":[],"style":"normal"},{"_key":"be94d28585c7","_type":"block","children":[{"_key":"d7b1885cc6270","_type":"span","marks":[],"text":"The reality of this is that intermittent connections are prevalent worldwideâ as of 2017, "},{"_key":"d7b1885cc6271","_type":"span","marks":["c13d5326f02c"],"text":"43 million brits have reported unreliable copper connections"},{"_key":"d7b1885cc6272","_type":"span","marks":[],"text":" . In Germany, a decision to stick with copper over fibre-optic connections in the 1980s has left the internet infrastructure there to be one of the more unreliable ones in the EU "},{"_key":"d7b1885cc6273","_type":"span","marks":["54153b67184a"],"text":"(at least until optic-fibre is implemented in 2025)"},{"_key":"d7b1885cc6274","_type":"span","marks":[],"text":" ."}],"markDefs":[{"_key":"c13d5326f02c","_type":"link","href":"https://www.ispreview.co.uk/index.php/2017/08/uswitch-study-claims-43-million-uk-adults-blighted-poor-broadband.html"},{"_key":"54153b67184a","_type":"link","href":"https://www.euractiv.com/section/broadband/news/german-german-dispute-over-fibre-optic-funding-to-be-decided-in-brussels/"}],"style":"normal"},{"_key":"cc27dc11ecd4","_type":"block","children":[{"_key":"f122bcf2be590","_type":"span","marks":[],"text":"As you probably already know, thereâs a way to improve experiences for offline users. Letâs take a simple todo list as an example â ideally, offline users can still take new notes which get synced once you get back online."}],"markDefs":[],"style":"normal"},{"_key":"f03e729f5182","_type":"block","children":[{"_key":"f9a5499a0e470","_type":"span","marks":[],"text":"TL;DR: If you donât want an explanation, you can view the "},{"_key":"f9a5499a0e471","_type":"span","marks":["90a116e5dfd4"],"text":"github link directly"},{"_key":"f9a5499a0e472","_type":"span","marks":[],"text":" . Explanations are grouped by branch."}],"markDefs":[{"_key":"90a116e5dfd4","_type":"link","href":"https://github.com/DEMO-4/offline-todo-list"}],"style":"blockquote"},{"_key":"4c54b4edacd4","_type":"block","children":[{"_key":"f28ab428153b0","_type":"span","marks":[],"text":"Whatâs next?"}],"markDefs":[],"style":"h4"},{"_key":"39852c3e5feb","_type":"block","children":[{"_key":"ef665573f62a0","_type":"span","marks":[],"text":"To take your apps offline, you first need to add a service worker. At its core, a service worker is a Javascript file that intercepts and caches requests. The cool thing about it is that it runs separate from the main thread, so it can get in touch with a server while the app is not running. You can use this to add push notifications to your app, for example."}],"markDefs":[],"style":"normal"},{"_key":"0b4281372772","_type":"block","children":[{"_key":"8c9acd89c0b40","_type":"span","marks":[],"text":"Whereâs the catch?"}],"markDefs":[],"style":"h4"},{"_key":"6bda3bfa964a","_type":"block","children":[{"_key":"0ed08ae856f60","_type":"span","marks":[],"text":"Since service workers are designed to be run completely asynchronously from the main app, there are a few things you need to keep in mind:"}],"markDefs":[],"style":"normal"},{"_key":"e07d29fc6114","_type":"block","children":[{"_key":"07613af6512b0","_type":"span","marks":["strong"],"text":"No access to local storage"},{"_key":"07613af6512b1","_type":"span","marks":[],"text":" â the official docs by google state that this is "},{"_key":"07613af6512b2","_type":"span","marks":["a37cf814b16d"],"text":"due to its asynchronous design"},{"_key":"07613af6512b3","_type":"span","marks":[],"text":" . You can use something like "},{"_key":"07613af6512b4","_type":"span","marks":["37702652c0f0"],"text":"indexedDB"},{"_key":"07613af6512b5","_type":"span","marks":[],"text":" are instead."}],"level":1,"listItem":"bullet","markDefs":[{"_key":"a37cf814b16d","_type":"link","href":"https://developers.google.com/web/ilt/pwa/introduction-to-service-worker#:~:text=A%20service%20worker%20is%20a,cache%2C%20and%20delivering%20push%20messages."},{"_key":"37702652c0f0","_type":"link","href":"https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API"}],"style":"normal"},{"_key":"860d73932bae","_type":"block","children":[{"_key":"191dd128eaa00","_type":"span","marks":["strong"],"text":"Service workers only run over HTTPS"},{"_key":"191dd128eaa01","_type":"span","marks":[],"text":" . Weâre mentioning this only because the default "},{"_key":"86e6e769dbd4","_type":"span","marks":["9e5c848367e3"],"text":"Angular development"},{"_key":"f8f2008d1e47","_type":"span","marks":[],"text":" environment runs over HTTP."}],"level":1,"listItem":"bullet","markDefs":[{"_key":"9e5c848367e3","_type":"link","href":"https://allfront.io/quick-start/angular/ "}],"style":"normal"},{"_key":"db0c90332c37","_type":"block","children":[{"_key":"b8bc01969b8b0","_type":"span","marks":[],"text":"Service workers can become idle and "},{"_key":"b8bc01969b8b1","_type":"span","marks":["strong"],"text":"can also restart"},{"_key":"b8bc01969b8b2","_type":"span","marks":[],"text":" when the browser deems fit. This means that you need to "},{"_key":"b8bc01969b8b3","_type":"span","marks":["strong"],"text":"persist your state."}],"level":1,"listItem":"bullet","markDefs":[],"style":"normal"},{"_key":"4afa856960a5","_type":"block","children":[{"_key":"0629add408df0","_type":"span","marks":["strong"],"text":"No access to the DOM"},{"_key":"0629add408df1","_type":"span","marks":[],"text":" due to separation of concerns in the design"}],"level":1,"listItem":"bullet","markDefs":[],"style":"normal"},{"_key":"e09358c28805","_type":"block","children":[{"_key":"c08d0fd8ad4a0","_type":"span","marks":[],"text":"ð° The good news"}],"markDefs":[],"style":"h4"},{"_key":"6cbd0fbc42ea","_type":"block","children":[{"_key":"4cf00d7482e10","_type":"span","marks":[],"text":"One of the many advantages of converting your app to a PWA is that you also auto-magically "},{"_key":"4cf00d7482e11","_type":"span","marks":["82c6fc3adb35"],"text":"add a service worker during the installation"},{"_key":"4cf00d7482e12","_type":"span","marks":[],"text":" !"}],"markDefs":[{"_key":"82c6fc3adb35","_type":"link","href":"https://allfront.io/blog/creating-a-pwa-with-angular/"}],"style":"normal"},{"_key":"c04a139c4df7","_type":"block","children":[{"_key":"87c03d05738f0","_type":"span","marks":[],"text":"While converting your app into a PWA may seem counter-intuitive, keep in mind that PWAs are installable â so your app works while itâs offline."}],"markDefs":[],"style":"normal"},{"_key":"cb60835967f2","_type":"block","children":[{"_key":"606215e9be1c0","_type":"span","marks":[],"text":"And more good news! Angularâs implementation of service workers already caches your requests. You have these nifty features right out of the box:"}],"markDefs":[],"style":"normal"},{"_key":"c5f5924cf5df","_type":"block","children":[{"_key":"d960adacda340","_type":"span","marks":[],"text":"Cached files are "},{"_key":"d960adacda341","_type":"span","marks":["strong"],"text":"tied to your app"},{"_key":"d960adacda342","_type":"span","marks":[],"text":" version â so if you update your app on the server, the clientâs app wonât suddenly break."}],"level":1,"listItem":"bullet","markDefs":[],"style":"normal"},{"_key":"e1ec5b69aa2b","_type":"block","children":[{"_key":"89461d532b700","_type":"span","marks":["strong"],"text":"New versions"},{"_key":"89461d532b701","_type":"span","marks":[],"text":" of the app are "},{"_key":"89461d532b702","_type":"span","marks":["strong"],"text":"downloaded in the background"},{"_key":"89461d532b703","_type":"span","marks":[],"text":" . This means that your site viewers will view the latest version of their app after revisiting the page. You can also hook onto refresh events â which can be a powerful way to handle app updates"}],"level":1,"listItem":"bullet","markDefs":[],"style":"normal"},{"_key":"eee5dbd5ae07","_type":"block","children":[{"_key":"fc59f58b766b0","_type":"span","marks":[],"text":"Service workers "},{"_key":"fc59f58b766b1","_type":"span","marks":["strong"],"text":"only download changed resources"},{"_key":"fc59f58b766b2","_type":"span","marks":[],"text":" and try to conserve bandwidth when possible"}],"level":1,"listItem":"bullet","markDefs":[],"style":"normal"},{"_key":"5cb3501a3701","_type":"block","children":[{"_key":"22d42f4463740","_type":"span","marks":[],"text":"ð¶ Forms are hard"}],"markDefs":[],"style":"h4"},{"_key":"8fc95c93e23f","_type":"block","children":[{"_key":"5f20a781333c0","_type":"span","marks":[],"text":"Hereâs where things get spicy. Service workers primarily cac
133he incoming requests, not outgoing requests. This means that each app will need to build its own implementation of form syncing. Letâs jump right in!"}],"markDefs":[],"style":"normal"},{"_key":"31b229c28b70","_type":"block","children":[{"_key":"84761a3928b70","_type":"span","marks":[],"text":"You donât have time to waste, "},{"_key":"84761a3928b71","_type":"span","marks":["3eee7ee5e7da"],"text":"so letâs start off with all the boring bits taken care of"},{"_key":"84761a3928b72","_type":"span","marks":[],"text":" . The linked demo includes a simple json backend server (json-server), with a pwa todo list."}],"markDefs":[{"_key":"3eee7ee5e7da","_type":"link","href":"https://github.com/gabrielstellini/serviceworker-example/tree/feature/basic-list"}],"style":"normal"},{"_key":"427d7f697f75","_type":"block","children":[{"_key":"301d406c337b0","_type":"span","marks":[],"text":"Please note that due to the deployment limitations on stackblitz, this demo needs to be run locally. This is due to a multitude of reasons â the normal âng-serveâ does not work with service workers, and a backend needs to be available on a separate port. To view the service worker, you need to run the provided â "},{"_key":"301d406c337b1","_type":"span","marks":["strong"],"text":"start:service-worker"},{"_key":"301d406c337b2","_type":"span","marks":[],"text":" â command."}],"markDefs":[],"style":"normal"},{"_key":"a3857b544633","_type":"block","children":[{"_key":"b8e346b6de790","_type":"span","marks":[],"text":"It should also be noted that hot reload does not work when youâre using the â "},{"_key":"b8e346b6de791","_type":"span","marks":["strong"],"text":"start:service-worker"},{"_key":"b8e346b6de792","_type":"span","marks":[],"text":" â command"}],"markDefs":[],"style":"normal"},{"_key":"5e74ce462ba0","_type":"block","children":[{"_key":"6647b893b71a0","_type":"span","marks":[],"text":"If you have any questions or issues any of the steps, please refer to the Q&A at the bottom of the article."}],"markDefs":[],"style":"blockquote"},{"_key":"2bff17a3fa1b","_type":"block","children":[{"_key":"6f0c983060cb","_type":"span","marks":[],"text":"If everything is working as expected, you should be able to see the service worker in the development tools, and your app should have an installation prompt (the small plus icon on the right of the URL bar in the case of chrome)"}],"markDefs":[],"style":"normal"},{"_key":"6f388c04838f","_type":"image","asset":{"_ref":"image-d8d0a60ca204785148089b4752fc6679451a3dcc-3508x1828-png","_type":"reference"}},{"_key":"0aea028bcaa1","_type":"block","children":[{"_key":"12a31b9a0ffc0","_type":"span","marks":["strong"],"text":"Whatâs next?"}],"markDefs":[],"style":"normal"},{"_key":"6f3bee062267","_type":"block","children":[{"_key":"78722b4eaf9e0","_type":"span","marks":[],"text":"The first step is to detect whether the user is online or offline. The window object has some nifty events which we can quickly attach to an "},{"_key":"78722b4eaf9e1","_type":"span","marks":["9dac1a7d1452"],"text":"rxjs subject which you can read more on here"},{"_key":"78722b4eaf9e2","_type":"span","marks":[],"text":" ."}],"markDefs":[{"_key":"9dac1a7d1452","_type":"link","href":"https://medium.com/all-front/what-is-rxjs-8a46220d7f6d"}],"style":"normal"},{"_key":"6c84715c9d66","_type":"block","children":[{"_key":"e10e78bf1381","_type":"span","marks":[],"text":""}],"markDefs":[],"style":"normal"},{"_key":"ceddc770ded4","_type":"code","code":"import { Injectable } from '@angular/core';\nimport {BehaviorSubject} from 'rxjs';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class OnlineService {\n isOnline$ = new BehaviorSubject\u003Cboolean>(window.navigator.onLine);\n\n constructor() {\n this.listenToOnlineStatus();\n }\n\n listenToOnlineStatus(): void {\n window.addEventListener('online', () => this.isOnline$.next(true));\n window.addEventListener('offline', () => this.isOnline$.next(false));\n }\n}","language":"javascript"},{"_key":"8e21f30fc19d","_type":"block","children":[{"_key":"49e46b87fe430","_type":"span","marks":[],"text":"The next step is to initialize the indexDB. It is important that you do NOT store your data in variables, as your app may restart at any point in t
133ime."}],"markDefs":[],"style":"normal"},{"_key":"547b821fcabf","_type":"block","children":[{"_key":"449e1ece9b470","_type":"span","marks":[],"text":"There are several wrappers we can use to wrap the browserâs native indexDB in Angular; For this example, we will be using "},{"_key":"449e1ece9b471","_type":"span","marks":["67e4e7b2ca82"],"text":"ngx-indexed-db"},{"_key":"449e1ece9b472","_type":"span","marks":[],"text":" ."}],"markDefs":[{"_key":"67e4e7b2ca82","_type":"link","href":"https://www.npmjs.com/package/ngx-indexed-db"}],"style":"normal"},{"_key":"4211f3e8853a","_type":"block","children":[{"_key":"4ad0d48e237d","_type":"span","marks":[],"text":""}],"markDefs":[],"style":"normal"},{"_key":"be7160dd66bc","_type":"code","code":"...\nconst dbConfig: DBConfig = {\n name: 'MyDb',\n version: 1,\n objectStoresMeta: [{\n store: 'tasks',\n storeConfig: { keyPath: 'id', autoIncrement: true },\n storeSchema: [\n { name: 'name', keypath: 'name', options: { unique: false } },\n { name: 'isComplete', keypath: 'isComplete', options: { unique: false } },\n { name: 'isOnServer', keypath: 'isOnServer', options: {unique: false}}\n ]\n }]\n};\n\n@NgModule({\n declarations: [...],\n imports: [\n ...\n NgxIndexedDBModule.forRoot(dbConfig),\n ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })\n ],\n providers: [],\n bootstrap: [AppComponent]\n})\nexport class AppModule { }","language":"javascript"},{"_key":"d9b745816bae","_type":"block","children":[{"_key":"d91d6542d4cb0","_type":"span","marks":[],"text":"We now need to rework our task service to sync when online and use the dbService when youâre offline. To keep the demo simple, we:"}],"markDefs":[],"style":"normal"},{"_key":"b4e9ffbdb719","_type":"block","children":[{"_key":"a5eeb6c72c640","_type":"span","marks":[],"text":"Disable the delete and editing buttons when the user is offline â this means that I donât need to keep track of records to delete or updates when syncing"}],"level":1,"listItem":"bullet","markDefs":[],"style":"normal"},{"_key":"50f32e1a7362","_type":"block","children":[{"_key":"ab244a6aaf9a0","_type":"span","marks":[],"text":"One service is being used to handle API communication and offline handling â by keeping the logic together, it is easier to understand (but not as maintainable)"}],"level":1,"listItem":"bullet","markDefs":[],"style":"normal"},{"_key":"a325c1f0a9ee","_type":"block","children":[{"_key":"e7a75099727e0","_type":"span","marks":[],"text":"You can see what the final code looks like by checking out "},{"_key":"e7a75099727e1","_type":"span","marks":["698825b21fab"],"text":"our git repo"},{"_key":"e7a75099727e2","_type":"span","marks":[],"text":" ."}],"markDefs":[{"_key":"698825b21fab","_type":"link","href":"https://github.com/DEMO-4/offline-todo-list/blob/feature/add-offline/src/app/services/tasks.service.ts"}],"style":"normal"},{"_key":"7e1ec5cfda83","_type":"block","children":[{"_key":"6b2fff8d71780","_type":"span","marks":[],"text":"Key takeaways"}],"markDefs":[],"style":"h4"},{"_key":"45a0c038b7ae","_type":"block","children":[{"_key":"22cbdd5a76ce0","_type":"span","marks":[],"text":"Making your app work offline is "},{"_key":"22cbdd5a76ce1","_type":"span","marks":["em"],"text":"expensive"},{"_key":"22cbdd5a76ce2","_type":"span","marks":[],"text":". Adding offline functionality required almost 3x as much code in the service (without accounting for tests)."}],"markDefs":[],"style":"normal"},{"_key":"95622cb41cb8","_type":"block","children":[{"_key":"a7ffaee6f4da0","_type":"span","marks":[],"text":"Hereâs a quick summary of what needs to be done:"}],"markDefs":[],"style":"normal"},{"_key":"5ea37724e3f4","_type":"block","children":[{"_key":"0a02798b96e50","_type":"span","marks":[],"text":"Make use of a database on the front end and sync with your remote data"}],"level":1,"listItem":"bullet","markDefs":[],"style":"normal"},{"_key":"743d5ac51c45","_type":"block","children":[{"_key":"e75aef7f77e30","_type":"span","marks":[],"text":"Handle data migrations for new upgrades on your browser database without losing the old sync data"}],"level":1,"listItem":"bullet","markDefs":[],"style":"normal"},{"_key":"ad8026936061","_type":"block","children":[{"_key":"adfaab80c4ab0","_type":"span","marks":[],"text":"Keep your backend APIs backwards-compatible for possible syncs on older app versions"}],"level":1,"listItem":"bullet","markDefs":[],"style":"normal"},{"_key":"6ef0a09b2d0c","_type":"block","children":[{"_key":"a2732c9a22e50","_type":"span","marks":[],"text":"Handling conflicts: what happens if two offline users edit the same record? Thereâs two main approaches â either avoiding this situation entirely by making sure that shared data canât be modified offline, or merging records."}],"level":1,"listItem":"bullet","markDefs":[],"style":"normal"},{"_key":"eb942621ca27","_type":"block","children":[{"_key":"c1f95314994a0","_type":"span","marks":[],"text":"Handling of Angular app versioning â while you can allow angular to update your app ânaturallyâ in the background, you may want to just push users to the latest version directly"}],"level":1,"listItem":"bullet","markDefs":[],"style":"normal"},{"_key":"8e1924ab8714","_type":"block","children":[{"_key":"e25608dba6560","_type":"span","marks":[],"text":"Error handling of incomplete syncing"}],"level":1,"listItem":"bullet","markDefs":[],"style":"normal"},{"_key":"5fd15b0d31a2","_type":"block","children":[{"_key":"87fbe1954d3b0","_type":"span","marks":[],"text":"Reworking of all your services to switch between 2 sources of truth"}],"level":1,"listItem":"bullet","markDefs":[],"style":"normal"},{"_key":"8798c4d51525","_type":"block","children":[{"_key":"204839fb18120","_type":"span","marks":[],"text":"Integration works better if you are using angular-redux or ngrx."}],"level":1,"listItem":"bullet","markDefs":[],"style":"normal"},{"_key":"abab00b090b0","_type":"block","children":[{"_key":"0e3ba047e44d0","_type":"span","marks":[],"text":"This may look like a lot of work â because it is. While adding a service worker and convert
133ing your app to a PWA is an easier task with strong performance benefits, making your app work offline requires getting your hands dirtier and modifying critical parts of your appâs architecture."}],"markDefs":[],"style":"normal"},{"_key":"4aa56d0443e3","_type":"block","children":[{"_key":"4169a9a3eec00","_type":"span","marks":[],"text":"You will also need significant backend rework, and possible optimization of APIs to handle the very large syncing requests."}],"markDefs":[],"style":"normal"},{"_key":"03b7cfba697f","_type":"block","children":[{"_key":"40d7ff0499ab0","_type":"span","marks":[],"text":"Q&A"}],"markDefs":[],"style":"h4"},{"_key":"a063286e5566","_type":"block","children":[{"_key":"24378cc651170","_type":"span","marks":[],"text":"Whatâs the â"},{"_key":"24378cc651171","_type":"span","marks":["em"],"text":"start:service-workerâ"},{"_key":"24378cc651172","_type":"span","marks":[],"text":" command doing?"}],"markDefs":[],"style":"normal"},{"_key":"f98866bea385","_type":"block","children":[{"_key":"8aa1c8df357f0","_type":"span","marks":[],"text":"â¢ï¸ The command adds your device to a global botnet which is using brute force attacks to hack nuclear codes."}],"markDefs":[],"style":"normal"},{"_key":"1984c7099801","_type":"block","children":[{"_key":"e3637b59e7300","_type":"span","marks":[],"text":"Just kidding â The command concurrently:"}],"markDefs":[],"style":"normal"},{"_key":"a86fdcbe2a8c","_type":"block","children":[{"_key":"00fafb60e6cb0","_type":"span","marks":[],"text":"builds the production version of the project in the dist folder"}],"level":1,"listItem":"bullet","markDefs":[],"style":"normal"},{"_key":"b5c3a0a90acd","_type":"block","children":[{"_key":"897b366e30ac0","_type":"span","marks":[],"text":"Deploys the built code on port 8080 using http-server â since we canât use ng-serve, we need a new way to view the deployed files"}],"level":1,"listItem":"bullet","markDefs":[],"style":"normal"},{"_key":"6895a1f3a2af","_type":"block","children":[{"_key":"599bd102a6060","_type":"span","marks":[],"text":"Runs a simple json backend âthe backend needs to be separate from the app for this demo to be realistic"}],"level":1,"listItem":"bullet","markDefs":[],"style":"normal"},{"_key":"ac4cc3d5bfa9","_type":"block","children":[{"_key":"249dd08bc6ce0","_type":"span","marks":[],"text":"My changes arenât showing up, HELP!"}],"markDefs":[],"style":"normal"},{"_key":"9148591fa252","_type":"block","children":[{"_key":"6f054f6f70950","_type":"span","marks":[],"text":"Keep in mind that the angular service workers prioritize app speed and stability over showing you the latest changes. What this means to you is that the service worker downloads the latest version of your app when you open it, but does not switch to it immediately. You need to refresh/hard-refresh your page to show the latest app version."}],"markDefs":[],"style":"normal"},{"_key":"679ebba7baa0","_type":"block","children":[{"_key":"4d4e15cab7280","_type":"span","marks":[],"text":"If using the latest version of the app is absolutely necessary, you can "},{"_key":"4d4e15cab7281","_type":"span","marks":["66a3570f0012"],"text":"listen to the service worker events"},{"_key":"4d4e15cab7282","_type":"span","marks":[],"text":" and manually call window.reload()."}],"markDefs":[{"_key":"66a3570f0012","_type":"link","href":"https://angular.io/guide/service-worker-communications"}],"style":"normal"},{"_key":"9ef893cb0468","_type":"block","children":[{"_key":"f43cc0133aca","_type":"span","marks":[],"text":""}],"markDefs":[],"style":"normal"},{"_key":"0cfb10ec3ee6","_type":"code","code":"@Injectable()\nexport class LogUpdateService {\n\n constructor(updates: SwUpdate) {\n updates.available.subscribe(event => {\n console.log('current version is', event.current);\n console.log('available version is', event.available);\n });\n updates.activated.subscribe(event => {\n console.log('old version was', event.previous);\n console.log('new version is', event.current);\n });\n }\n}","language":"javascript"},{"_key":"858cae0b2145","_type":"block","children":[{"_key":"ba0c6eed56000","_type":"span","marks":[],"text":"Any other development tips?"}],"markDefs":[],"style":"normal"},{"_key":"552d96f5f61d","_type":"block","children":[{"_key":"3d966fd8edaa0","_type":"span","marks":[],"text":"1. You should keep in mind that when using developer tools, the service worker is kept running in the background and never restarts."}],"markDefs":[],"style":"normal"},{"_key":"8cbebeef60e0","_type":"block","children":[{"_key":"d4933bc04d630","_type":"span","marks":[],"text":"2. If you want to remove the service worker entirely, remove the "},{"_key":"d4933bc04d631","_type":"span","marks":["em","strong"],"text":"ngsw.json"},{"_key":"d4933bc04d632","_type":"span","marks":[],"text":" file. When the Angular service worker canât find this file, it deactivates itself as a fail-safe."}],"markDefs":[],"style":"normal"},{"_key":"5730a98dbf9a","_type":"block","children":[{"_key":"b63a32048f7c0","_type":"span","marks":[],"text":"3. When switching your app versions on the frontend, you might want to migrate your data. You can read more about migration handling in the "},{"_key":"b63a32048f7c1","_type":"span","marks":["1a4740649db7"],"text":"official ngx-indexed-db quickstart guide"}],"markDefs":[{"_key":"1a4740649db7","_type":"link","href":"https://www.npmjs.com/package/ngx-indexed-db"}],"style":"normal"},{"_key":"c2ca4e5d6876","_type":"block","children":[{"_key":"7c766b424d950","_type":"span","marks":[],"text":"4. It is recommended to keep a versioning system in y
133our API â this way you can segregate older APIs from newer ones and make it easier to deprecate the old APIs."}],"markDefs":[],"style":"normal"},{"_key":"72ab173dd55c","_type":"block","children":[{"_key":"d89d2de71c060","_type":"span","marks":[],"text":"5. Consider adding some form of logging in your app to track the app versions out there in the wild. This will make future decisions on API deprecations easier."}],"markDefs":[],"style":"normal"},{"_key":"d8f62bddaa00","_type":"block","children":[{"_key":"d70d2e42c9280","_type":"span","marks":["strong"],"text":"Are pages really being cached? How can I confirm this?"}],"markDefs":[],"style":"normal"},{"_key":"53849ff3df5e","_type":"block","children":[{"_key":"78ec8d472b7a0","_type":"span","marks":[],"text":"Files cached by the service worker can be viewed in the âcache storageâ section. As can be seen in the screenshot below, the main and the index files are being stored in this specific cache. The files stored here depend largely on the contents in your "},{"_key":"78ec8d472b7a1","_type":"span","marks":["strong"],"text":"ngsw-config"},{"_key":"78ec8d472b7a2","_type":"span","marks":[],"text":" file. You can read more about configuring it in "},{"_key":"78ec8d472b7a3","_type":"span","marks":["1f5b41b8ae14"],"text":"the official docs"},{"_key":"78ec8d472b7a4","_type":"span","marks":[],"text":" ."}],"markDefs":[{"_key":"1f5b41b8ae14","_type":"link","href":"https://angular.io/guide/service-worker-config"}],"style":"normal"},{"_key":"30fc8a70db31","_type":"block","children":[{"_key":"3e0bdafd6756","_type":"span","marks":[],"text":""}],"markDefs":[],"style":"normal"},{"_key":"a1b734dc0a35","_type":"image","asset":{"_ref":"image-c157dfc482c5376efab6ededac008279044abbfa-2940x1954-png","_type":"reference"}},{"_key":"df6cb5b55c47","_type":"block","children":[{"_key":"bb2a26884d0e0","_type":"span","marks":[],"text":"If you hit refresh, you can also see that your app should load much faster but the todo app is small in itself, so you can fix this by slowing down your connection speed manually with the chrome dev tools:"}],"markDefs":[],"style":"normal"},{"_key":"2a90607b1c84","_type":"block","children":[{"_key":"c0ac45d1249e","_type":"span","marks":[],"text":""}],"markDefs":[],"style":"normal"},{"_key":"aa357ac589c5","_type":"image","asset":{"_ref":"image-946f1d8fbcd7d78ce244ca5571309583b6ba4888-3239x2351-png","_type":"reference"}},{"_key":"6f3b6c7a0b69","_type":"block","children":[{"_key":"22d83bd19e7d0","_type":"span","marks":["em"],"text":"The connection was slowed down to âSlow 3Gâ. The tasks API request took 2s, but the main bundle was loaded in 10ms due to the service worker caching!"}],"markDefs":[],"style":"h6"},{"_key":"32ff467dac4f","_type":"block","children":[{"_key":"5d0b1a318653","_type":"span","marks":[],"text":""}],"markDefs":[],"style":"normal"}],"description":"Everything you need to know about Service Workers","image":{"_type":"image","asset":{"_ref":"image-08433bd56482e454af1e6f6f751071502f3b5ab9-728x419-png","_type":"reference"},"caption":"Unplug your app from the internet!"},"imageAlt":null,"imageCaption":"Unplug your app from the internet!","mainImageLqip":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB1klEQVQokW2Q2WrbUBgG9d59g9K73peuoQttKZgYSgIpdUPixrEdx1viXV4lL5IsHa3WlCMrwbF7YK4OfMP8Sltzac9cWjOX+6lLcyJoTAT1saA2ElRHgtuhQ0V1uFEdygOHUt+m2Le57tkUejZXXZu/nXWC0p97SHq6R1fz6OwJ7iZiKxkL6juCRDIUBxJlvPKRjJY+w6WPuvAY7Er0Q0lNtcjXJ1xUR5S7BrWRQzWVKLoVINHMgJkZMDUCJsZ/JItUMvcot2Z8yZzx9vMxv4stmmOHu/RciuGErJyQpR2ykKxD5utUYj2VDOYuDdUgV2zx4WuW10ffyRUatGdOUiNLFNuLWEvcCMuNMEWEIQ4lA90hV1H5+POaT9kLTs5LFG7b3KsLhgv3sUbxgg1uivA3OH6ElNg7Et30+HPT58W7E569/Mb7zDn17hTN9J6USJQwipEEUYwfbhJ2JZYIafQ1jrJ5nr865s2PHMWGim66mCJk/2TKJo5J2MREKQ8SOa5qBqeXdTK/SuQrHdTZCksESYmzV2KJCCWOY/aRgjiGtfAp1AecXtZoqxrCC5OShxpvp0aeS7IdhGRgy3ZUvunC4uyqSbHRR7h+8v9YkZZsBdtTSf4BRdI4aeiiCggAAAAASUVORK5CYII=","metaDescription":"Discover everything you need to know about Service Workers and how to add them to enable offline capabilities for your Angular apps.","metaTitle":"How to take your Angular apps offline","postedBy":"Gabriel Stellini","publishedAt":"2020-10-18T11:01:00.000Z","slug":"how-to-take-your-angular-apps-offline","tags":["Angular","Business","Service Workers"],"title":"How to take your Angular apps offline"}],"blog-summary":[{"description":null,"imageAlt":"proof of concept vs prototype vs mvp","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/7f8ec92d4
1331a89ca3211791b8a043f1efd4ea1fdd-727x420.png","metaDescription":"What's the difference between a Proof of Concept, a Prototype and an MVP? When to use which? Check out our article for a full comparison.","metaTitle":"Proof of Concept vs Prototype vs MVP: Differences","publishedAt":"2025-02-05T10:40:35.880Z","slug":"proof-of-concept-vs-prototype-vs-mvp","tags":["Business"],"title":"Proof of Concept vs Prototype vs MVP: Differences"},{"description":null,"imageAlt":"web app vs website comparison","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/4dd492e4e51dbf40a833ab391b7e620c4d371773-727x420.png","metaDescription":"Understand the key differences between web apps and websites, their advantages and disadvantages and what to choose for your business.","metaTitle":"Web App vs Website Explained: Full Comparison","publishedAt":"2023-07-26T09:22:24.934Z","slug":"web-app-vs-website","tags":["Web App","Business"],"title":"Web App vs Website Explained: Full Comparison"},{"description":null,"imageAlt":"experience design","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/fcf178a59aa731afb823beff01c8eabf34e89b4b-727x420.png","metaDescription":"Dive into the fundamentals of experience design, why it can be so important for your business and how you can craft memorable experiences.","metaTitle":"What is Experience Design, and Why It Matters","publishedAt":"2023-11-23T08:17:26.783Z","slug":"experience-design","tags":["Design"],"title":"What is Experience Design, and Why It Matters"},{"description":"Everything you need to know about Service Workers","imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/08433bd56482e454af1e6f6f751071502f3b5ab9-728x419.png","metaDescription":"Discover everything you need to know about Service Workers and how to add them to enable offline capabilities for your Angular apps.","metaTitle":"How to take your Angular apps offline","publishedAt":"2020-10-18T11:01:00.000Z","slug":"how-to-take-your-angular-apps-offline","tags":["Angular","Business","Service Workers"],"title":"How to take your Angular apps offline"},{"description":null,"imageAlt":"api first","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/6cc261e50e4f39e33b113e02fd721881f44ee2e3-727x420.png","metaDescription":"API-first can be a game-changer for B2B. Read about how and why it works, and get a practical guide on how to implement it for your product or business. ","metaTitle":"API-first: Why itâs a winner for B2B products","publishedAt":"2025-04-23T08:25:17.693Z","slug":"api-first-development","tags":["Business","Development"],"title":"API-first: Why itâs a winner for B2B products"},{"description":null,"imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/559d5c1df1ea03ad29448af7a77c831aa934d16a-728x419.png","metaDescription":"Learn what an MVP is in business, how to build one the right way and why it is a vital tool to have in your arsenal as the new kid on the block.","metaTitle":"What is an MVP in business, and why is it necessary?","publishedAt":"2021-09-15T11:29:00.000Z","slug":"what-is-an-mvp-in-business","tags":["Business","Agile"],"title":"What is an MVP in business, and why is it necessary?"},{"description":null,"imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/4486036ffffc5affe88177024f80d1cd8bd06afd-728x419.png","metaDescription":"Clutch, the leading platform for B2B ratings and reviews, highlights All Front as one of the highest-performing B2B app development providers in Malta.","metaTitle":"All Front: Top B2B Service Provider Malta 2022","publishedAt":"2022-08-13T11:59:00.000Z","slug":"clutch-award","tags":["Business"],"title":"All Front: Top B2B Service Provider Malta 2022"},{"description":"With NativeScript, you can write one app to rule them all â a NativeScript app can be an android, ios and web app all at the same time.","imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/dd4f797c305390f29c970f849ea74d0cc7932d31-728x419.png","metaDescription":"What is NativeScript? How do I use it? Follow our guide and write one app that can be an Android, IOS and Web App at the same time!","metaTitle":"Getting started with NativeScript","publishedAt":"2020-03-12T19:48:00.000Z","slug":"getting-started-with-nativescript","tags":["Angular","NativeScript","IOS"],"title":"Getting started with NativeScript What is NativeScript?"},{"description":null,"imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/13ee7519359a930137d6ef56550a32b0a942ae4e-728x419.png","metaDescription":"We answer the question âHow much does an MVP costâ with a detailed breakdown of factors to consider and clever tips to save money.","metaTitle":"A Startup Guide to Estimating MVP Cost","publishedAt":"2022-11-29T13:03:03.853Z","slug":"mvp-cost-startup-guide","tags":["Business","Agile"],"title":"A Startup Guide to Estimating MVP Cost"},{"description":"The undocumented truth. PWAs have a few disadvantages over native applications. A lot of these disadvantages can be removed if we manage to call native android code within our app.","imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/03817ca82cd31d73f0b6661079a61b3ad2b7f799-728x419.png","metaDescription":"Using native APIs with PWAs - should you give it a try and how? Learn how to call Android APIs from Angular or call Angular functions from Android.","metaTitle":"Using native APIs with PWAs","publishedAt":"2020-05-15T05:08
133:00.000Z","slug":"using-native-apis-with-pwas","tags":["Angular","PWA","Android","API"],"title":"Using native APIs with PWAs"},{"description":null,"imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/b9da8edbd9187ad0c03256ae31a2af8954c9ca85-728x420.png","metaDescription":"Learn what team extension is, differences from other outsourcing models, and best practices on how to leverage team extension for project success.","metaTitle":"Navigating Team Extension for Project Success","publishedAt":"2023-04-18T11:30:16.175Z","slug":"navigating-team-extension-for-project-success","tags":["Business","Team Extension"],"title":"Navigating Team Extension for Project Success"},{"description":null,"imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/3914d167ea02822a5bb1a2e0ce34349da6dbb5f2-740x419.png","metaDescription":"Test in isolation by mocking - when is it useful and how do we mock a GraphQL backend. Read on and take a deeper dive into intercepting GQL requests.","metaTitle":"Mocking in Angular: Faking a GraphQL backend","publishedAt":"2019-11-20T18:31:00.000Z","slug":"mocking-in-angular-faking-a-graphql-backend","tags":["Angular","GraphQL"],"title":"Mocking in Angular: Faking a GraphQL backend"},{"description":null,"imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/8983fcac4e20d38f7a46d7e63844d37d257696a0-728x419.png","metaDescription":"2022 is almost over, and it's time to see what 2023 has in store for SaaS. Check out the latest SaaS statistics and trends that are shaping the space!","metaTitle":"SaaS statistics & trends to watch out for in 2023","publishedAt":"2022-12-15T14:42:39.036Z","slug":"saas-statistics-and-trends-2023","tags":["SaaS","Business"],"title":"SaaS statistics & trends to watch out for in 2023"},{"description":null,"imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/a005fb9d8d4866ad51b7e9d2b40f5666866e97df-727x420.png","metaDescription":"The European Accessibility Act 2025 deadline is coming. Learn what it means for digital products, who must comply, and how businesses can prepare.","metaTitle":"European Accessibility Act 2025: How to prepare","publishedAt":"2025-03-17T09:18:52.056Z","slug":"european-accessibility-act-2025","tags":["Design"],"title":"European Accessibility Act 2025: How to prepare"},{"description":null,"imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/b5169a06920986e2c6820e6796ba65013138a254-728x419.png","metaDescription":"Want to find out what are the similarities and differences between Deno vs Node? Which solution is better? Visit our website and check out a quick comparison.","metaTitle":"Deno vs Node comparison. Similarities and differences","publishedAt":"2021-09-29T11:41:00.000Z","slug":"node-vs-deno","tags":["Node","Deno"],"title":"Deno vs Node comparison. Similarities and differences"},{"description":"10 actionable tips from a UX & UI designer","imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/d7c2e42bd2e4fd782eefab87b7b2429414f6affc-728x419.png","metaDescription":"Want to know how to improve user experience on your web app? Look no further! Start with these 10 practical tips from our experienced UX & UI designer.","metaTitle":"How to improve user experience and wow your users","publishedAt":"2022-11-08T11:57:21.501Z","slug":"how-to-improve-user-experience","tags":["UX","UI"],"title":"How to improve user experience and wow your users"},{"description":null,"imageAlt":"different types of web apps","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/2d2332b207461ddd23aec2bad1b64531a9b30cff-727x420.png","metaDescription":"Learn about different types of web apps, their strengths and weaknesses, and how to choose the right one for your project.","metaTitle":"A Look Into Different Types of Web Apps ","publishedAt":"2023-08-18T07:41:53.013Z","
133slug":"different-types-of-web-apps","tags":["Web App","PWA"],"title":"A Look Into Different Types of Web Apps "},{"description":null,"imageAlt":"project discovery phase","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/93b8818f3c713771d22c7ac3d41c45611f2c0ac6-727x420.png","metaDescription":"What is the discovery phase, and why should you care? All about its purpose, deliverables and a simple step-by-step process to get started.","metaTitle":"The Discovery Phase in Software Projects","publishedAt":"2023-06-22T08:26:28.155Z","slug":"project-discovery-phase-guide","tags":["Agile","Project Management"],"title":"The Discovery Phase in Software Projects"},{"description":"A collection of important stuff you may not know about RxJS","imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/cbce2c039ca5e746ee6bb3005b4e961c45e8ff55-740x419.png","metaDescription":"Find out what RxJS is! Visit our website and check collection of important tips you may not know about it. All about pipes, observables, subjects and operators.","metaTitle":"What is RxJS? Levelling up your skills","publishedAt":"2020-09-04T07:07:00.000Z","slug":"what-is-rxjs","tags":["RxJS"],"title":"What is RxJS? Levelling up your skills"},{"description":null,"imageAlt":"how to choose a software development partner","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/a1318877607ac81650263164dfafa89aba701450-728x419.png","metaDescription":"Find out how to choose the right software development partner. Our step-by-step guide includes expert tips, key factors to consider, and red flags to avoid.","metaTitle":"How to Choose a Software Development Partner: Expert Tips","publishedAt":"2022-08-22T13:24:00.000Z","slug":"we-asked-500-ceos-how-they-chose-a-software-development-partner-heres-what-they-had-to-say","tags":["Business"],"title":"How to Choose a Software Development Partner: Expert Tips"},{"description":null,"imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/f0014d80332bf907011986b479d192e6b7cfe210-728x419.png","metaDescription":"Visit our website and find out which are the top software testing tools for web applications in 2021, from unit testing to end to end testing. Check it out!","metaTitle":"Top Software Testing tools for Web Applications in 2021","publishedAt":"2021-02-03T12:05:00.000Z","slug":"top-software-testing-tools-for-2021","tags":["Testing"],"title":"Top Software Testing tools for Web Applications in 2021"},{"description":"A guide to progressive web apps","imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/c872ebe245c301b87b6ec6a416e89751c4b7f6d6-728x419.png","metaDescription":"Learn how to create Angular Progressive Web App (PWA). Our article covers speed, platform and browser support, instructions for implementation and a demo.","metaTitle":"Angular Progressive Web App (PWA). How to create it?","publishedAt":"2020-03-24T19:34:00.000Z","slug":"creating-a-pwa-with-angular","tags":["Angular","PWA"],"title":"Creating a Progressive Web App with Angular"},{"description":"Will it fly or should you stick to Angular?","imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/87cb1697976495166750baca35a992f08c1753cc-728x419.png","metaDescription":"From an Angular Developer's perspective, we cover Flutter's architecture, language, template engine, library support and performance.","metaTitle":"Flutter â an Angular developerâs perspective","publishedAt":"2020-07-13T06:55:00.000Z","slug":"flutter-an-angular-developers-perspective","tags":["Angular","Flutter"],"title":"Flutter â an Angular developerâs perspective"},{"description":null,"imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/60f6b110d4bfd60d2c17887f3981cfabb2417c7e-727x420.png","metaDescription":"Can no-code and low-code tools measure up to custom development? Dive into the pros and cons, featuring real-world insights from tech founders.","metaTitle":"No Code, Low Code, or Custom? Founders Insights","publishedAt":"2024-07-25T10:59:29.133Z","slug":"no-code-low-code-vs-custom-development","tags":[],"title":"No Code, Low Code, or Custom? Founders Insights"},{"description":null,"imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/a0192438464fea4f4b43ddc4de0180957fe61b8b-727x420.png","metaDescription":"If you've launched your MVP and are wondering \"What next?â, this is for you. Learn how to measure and scale your MVP, and what comes after an MVP.","metaTitle":"From Viable to Marketable: What comes after an MVP?","publishedAt":"2023-10-05T10:31:18.072Z","slug":"what-comes-after-
133an-mvp","tags":["Agile","Business"],"title":"From Viable to Marketable: What comes after an MVP?"},{"description":"A guide to optimizing your page for SEO","imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/fddaf60eb82cf8a0aaf5f66e3871c1e4f489689f-740x419.png","metaDescription":"Learn how to optimise your page for SEO with keywords, and some tools that you can use to improve your ranking.","metaTitle":"Optimizing your site for SEO","publishedAt":"2020-01-27T18:40:00.000Z","slug":"optimising-your-site-for-seo","tags":["SEO"],"title":"Optimizing your site for SEO"},{"description":null,"imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/aa56f46a5fa0ab4471a32fbdb201ef1bc9301dd8-728x419.png","metaDescription":"Check out our front-end developer hiring guide with tips on expanding your reach, identifying key skills and crafting an effective job description.","metaTitle":"The Ultimate Front-End Developer Hiring Guide","publishedAt":"2022-01-04T12:43:00.000Z","slug":"how-to-hire-the-best-front-end-developers-the-ultimate-guide","tags":["Angular","Frontend","React","Hiring"],"title":"How to Hire the Best Front-End Developers (The Ultimate Guide)"},{"description":null,"imageAlt":"team extension mistakes","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/dcd4f2054dd58eda30a946b721096443b296c739-727x420.png","metaDescription":"Check out the most common team extension mistakes companies make and how you can avoid them to maximise the potential of your extended tech team.","metaTitle":"Don't Get Tripped Up: 6 Team Extension Mistakes","publishedAt":"2023-05-17T09:54:14.056Z","slug":"team-extension-mistakes","tags":["Business","Team Extension","Agile"],"title":"Don't Get Tripped Up: 6 Team Extension Mistakes"},{"description":"Saying no to app regressions with Google Lighthouse integration","imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/2e062cedc32a5d82cf44c077273b10323b80f73e-728x419.png","metaDescription":"Google Lighthouse - what is it, why and how can it help you prevent app regressions in Angular. This guide teaches you how to integrate it into your pipelines.","metaTitle":"Preventing app regressions in Angular with Google Lighthouse","publishedAt":"2020-01-30T07:16:00.000Z","slug":"preventing-app-regressions-in-angular","tags":["Angular","SEO","Lighthouse"],"title":"Preventing app regressions in Angular with Google Lighthouse"},{"description":"Building trusted web activities (TWA)","imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/a272f92e163a181dc34add8c766252b98ae504fb-728x419.png","metaDescription":"Learn all about TWA, advantages and limitations. Using Google Play Console and deploying your Angular app to Google Play Store in 8 easy steps!","metaTitle":"Deploy your Angular app to the Google Play Store","publishedAt":"2020-03-31T18:58:00.000Z","slug":"deploying-your-angular-app-to-the-the-google-play-store","tags":["Angular","Google Play Store"],"title":"Deploy your Angular app to the Google Play Store"},{"description":"An overview with examples to get your first React Native app out the door","imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/1497c72a995348cf15f35c74cb9e88236aeb97f2-728x419.png","metaDescription":"Visit our website and check how to create your first React Native App. Learn why to use it, how to get started, navigation elements, speed and design languages.","metaTitle":"Create your first React Native App. Tutorial for Beginners","publishedAt":"2020-04-08T06:24:00.000Z","slug":"creating-your-first-react-native-app","tags":["React","React Native"],"title":"Letâs talk about React Native"},{"description":"In the day and age of remote work","imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/fddf946ecdf32bcd7caf7e112afaed24f4e44895-728x419.png","metaDescription":"Set up your new staff for success from day one. Overcome challenges with our arsenal of tips and tools to make remote onboarding a walk in the park.","metaTitle":"Onboarding 101: How to boost employee retention","publishedAt":"2022-02-07T12:51:00.000Z","slug":"onboarding-101","tags":null,"title":"Onboarding 101: How to boost employee retention"},{"description":null,"imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/9b9c325631fdf5221f1fafbe1dfa883a71729935-727x420.png","metaDescription":"Dive into Sanity.io - a headless CMS, and its features, benefits and use cases for streamlined, flexible and personalized content management.","metaTitle":"What is Sanity CMS? A practical deep dive","publishedAt":"2024-11-21T11:54:57.727Z","slug":"sanity-cms","tags":["Business"],"title":"What is Sanity CMS? A practical deep dive"},{"description":null,"imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/3a13d677b3a90926972146b382b2e7f4a
1337fa562b-728x419.png","metaDescription":"A guide on masking and validating card numbers, with Luhn's algorithm using Angular. Identify brands, from VISA to JCB, just by looking at the digits.","metaTitle":"Angular Card Masking and Validation. Complete Guide","publishedAt":"2019-12-18T19:25:00.000Z","slug":"card-masking-and-validation","tags":["Angular","Validation"],"title":"Angular Card Masking and Validation"},{"description":null,"imageAlt":"gold plating in project management","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/ceaec71ba3ba1e11936f05dc416deac18cfe1bcd-727x420.png","metaDescription":"Learn about gold plating in project management, its consequences and practical strategies to avoid it, stay on track and deliver value-driven projects.","metaTitle":"What is gold plating, and how to avoid it ","publishedAt":"2023-05-31T08:49:31.704Z","slug":"what-is-gold-plating-and-how-to-avoid-it","tags":["Project Management","Agile"],"title":"What is gold plating, and how to avoid it "},{"description":"Our guide with questions and answers to help you prepare for Angular interviews","imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/dc6f20bb7ffa4670bcc568bec9be72ee479965f5-740x419.png","metaDescription":"Get prepared for your next Frontend interview. We cover some of the most common Angular interview questions and answers for development positions.","metaTitle":"Top Angular Interview Questions and Answers for 2021","publishedAt":"2021-01-04T12:26:00.000Z","slug":"top-angular-interview-questions-and-answers-for-2021","tags":["Angular","Frontend","Interview"],"title":"Angular Interview questions for 2021"},{"description":null,"imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/d7a2a2fb7049ec798bd194e130fd1c09a3b16522-728x419.png","metaDescription":"From shift in priorities to new approaches to sourcing talent, check out four of the top staff augmentation & outsourcing trends in 2023.","metaTitle":"Top 4 Staff Augmentation & Outsourcing Trends in 2023","publishedAt":"2023-01-17T09:48:34.074Z","slug":"staff-augmentation-and-outsourcing-trends-2023","tags":["Business"],"title":"Top 4 Staff Augmentation & Outsourcing Trends in 2023"},{"description":null,"imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/102f6e39f06064e14e0bf7645f0072f78fea5f7a-740x419.png","metaDescription":"Need to ramp up your development team quickly and affordably? Check out our guide on staff augmentation and how to hire dedicated developers.","metaTitle":"Hire Dedicated Developers: Staff Augmentation Guide ","publishedAt":"2022-11-17T13:19:05.775Z","slug":"hire-dedicated-developers-staff-augmentation-guide","tags":["Business"],"title":"Hire Dedicated Developers: Staff Augmentation Guide "},{"description":null,"imageAlt":"web app design","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/17c39680b295602c292940b9c508dfcb9dd24fdf-727x420.png","metaDescription":"Learn more about modern web app design, why it's so important to get right, and take inspiration from successful products that nailed it!","metaTitle":"How to Design a Web App That Stands Out","publishedAt":"2024-03-13T12:27:09.620Z","slug":"web-app-design-guide","tags":["UX","UI","Web App"],"title":"How to Design a Web App That Stands Out"},{"description":null,"imageAlt":"rapid prototyping in software development","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/0a46c90e197253bcc9d4f2f44c7f3a63ebb6f0f9-727x420.png","metaDescription":"Read about the basics of rapid prototyping in software development: how it works, how to choose the right tools and our tips to get it right.","metaTitle":"Rapid Prototyping in Software Development: Guide","publishedAt":"2024-05-10T07:53:29.745Z","slug":"rapid-prototyping-in-software-development","tags":["Agile","Design"],"title":"Rapid Prototyping in Software Development: Guide"},{"description":"Technical analysis of the competition and criteria that matter","imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/7f62f0fc9ea769bd03925111239a8d0ed0c8007d-728x419.png","metaDescription":"We cover the best live chat tools for startups, with a technical analysis of the competition. We compare accessibility, cost, website impact and more.","metaTitle":"The best live chat tools for startups. Technical analysis","publishedAt":"2020-05-08T05:30:00.000Z","slug":"the-best-live-chat-tools-available-for-startups","tags":["Live Chat"],"title":"The best live chat tools for startups"},{"description":null,"imageAlt":"SaaS application development","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/ad3e0b7b24607357d569161859e23c1c6c418a66-728x419.png","metaDescription":"SaaS is an increasingly growing sector and a popular business model. Check out our step-by-step guide to SaaS application development.","metaTitle":"A Step-By-Step Guide to SaaS App Development","publishedAt":"2022-03-28T18:40:00.000Z","slug":"building-your-own-saas-application","tags":["Business"],"title":"A Step-By-Step Guide to SaaS App Development"},{"description":null,"imageAlt":"Google Maps vs Mapbox ","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/3292826547ee23642fceab99f52423ff1d7b6a21-727x420.png","metaDescription":"Choose the right map API for your project with this comparison of Mapbox vs Google Maps based on pricing, integration, customization and key features.","metaTitle":"Mapbox vs Google Maps: Which Map API to Choose?","publishedAt":"2024-10-29T08:44:45.958Z","slug":"mapbox-vs-google-maps","tags":["API"],"title":"Mapbox vs Google Maps: Which Map API to Choose?"},{"description":"Debugging tips and tricks","imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/596ef8d8bb44d578770b8d096001982395894c78-728x419.png","metaDescription":"Visit our website and check out some helpful tips and tricks to learn how to easily debug Angular applications. Fix your errors faster than ever!","metaTitle":"Angular Debugging Tips and Tricks","publishedAt":"2021-08-09T11:09:00.000Z","slug":"fix-your-angular-errors-faster-than-ever","tags":["Angular"],"title":"Fix your Angular errors faster than ever"},{"description":null,"imageAlt":"how to build a software product","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/4dd5817a38e391eeb1ecc436864b0dea516c5421-727x420.png","metaDescription":"Check out the best advice and actionable tips from founders and entrepreneurs on how to build a successful software product - from ideation to post-launch. ","metaTitle":"How to Build a Software Product: Founders Tips","publishedAt":"2024-02-12T11:48:42.474Z","slug":"how-to-build-software-product","tags":["Business"],"title":"How to Build a Software Product: Founders Tips"},{"description":"Introducing Web assembly with Blazor","imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/6e62a71531c3064360ee849bf6aa6db6a48c7a92-728x419.png","metaDescription":"WebAssembly with Blazor. Architecture, performance, frameworks and how to write web assembly code. Follow our guide write your first Blazor app!","metaTitle":"What is Blazor WebAssembly? The future of web app security","publishedAt":"2020-04-17T06:08:00.000Z","slug":"the-future-of-web-app-security","tags":["Security","Blazor"],"title":"Web assembly and the future of the web"},{"description":null,"imageAlt":"performance optimization ","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/3c7c0b0826190de8ee44fd4374481cf1649ee5bb-727x420.png","metaDescription":"Improve your app's speed, efficiency, and user experience with proven performance optimization strategies, leading to better conversions and happy users.","metaTitle":"Performance Optimization for Lightning-Fast Apps","publishedAt":"2024-06-03T09:38:15.940Z","slug":"performance-optimization","tags":["Agile","Development"],"title":"Performance Optimization for Lightning-Fast Apps"},{"description":null,"imageAlt":"web development trends 2024","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/bc5546d780a763165f488f541a5e44245ae07d1b-727x420.png","metaDescription":"From the growing role of AI in development to the rise of serverless architecture, hereâs our take on the top web development trends in 2024. ","metaTitle":"Top Web Development Trends in 2024","publishedAt":"2024-01-23T12:18:16.688Z","slug":"web-development-trends-2024","tags":["Development","Trends"],"title":"Top Web Development Trends in 2024"},{"description":null,"imageAlt":null,"imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/1a20f2c485f2dd3deb2911a5a932c9f6bcb86087-728x419.png","metaDescription":"Learn what Socket.io is, as well as how and when to use it. See how to build a simple chat application using Socket.io. Visit our website and check it out!","metaTitle":"How to build a Node.js chat app with Socket.io","publishedAt":"2021-03-03T12:36:00.000Z","slug":"building-a-node-js-chat-with-socket-io","tags":["Socket.io","Node.js","Chat"],"title":"How to build a Node.js chat app with Socket.io"},{"description":null,"imageAlt":"App monetization strategies","imageUrl":"https://cdn.sanity.io/images/68sc9ce8/production/54de52f795bdd64809d0ac79cd4fbe8b23388b3e-727x420.png","metaDescription":"Not sure how to monetize your app? Check out our guide to choosing the right app monetization strategy and driving revenue without hurting UX.","metaTitle":"Building sustainable apps: Monetization strategies","publishedAt":"2025-06-04T11:27:38.442Z","slug":"app-monetization-strategies","tags":["Business"],"title":"Building sustainable apps: Monetization strategies"}],"__ɵnghData__":[{"t":{"0":"t9","1":"t11","2":"t12"},"c":{"0":[{"i":"t9","r":1,"t":{"1":"t10"},"c":{"1":[{"i":"t10","r":1,"x":7}]}}],"1":[],"2":[]}},{},{"t":{"0":"t133"},"c":{"0":[{"i":"t133","r":1,"n":{"1":"0f2"},"x":3}]}},{"t":{"0":"t9","1":"t11","2":"t12"},"c":{"0":[],"1":[{"i":"t11","r":1,"t":{"1":"t28"},"c":{"1":[{"i":"t28","r":1,"x":7}]}}],"2":[]}},{"n":{"3":"0fn"}},{"t":{"0":"t9","1":"t11","2":"t12"},"c":{"0":[],"1":[{"i":"t11","r":1,"t":{"1":"t28"},"c":{"1":[{"i":"t28","r":1,"x":8}]}}],"2":[]}},{"t":{"0":"t135"},"c":{"0":[{"i":"t135","r":1,"n":{"3":"2f2"},"t":{"4":"t136","10":"t137","12":"t138","13":"t139"},"c":{"4":[{"i":"t136","r":1}],"10":[{"i":"t137","r":1}],"12":[{"i":"t138","r":1}],"13":[]}}]}},{"t":{"0":"t9","1":"t11","2":"t12"},"c":{"0":[],"1":[{"i":"t11","r":1,"t":{"1":"t28"},"c":{"1":[{"i":"t28","r":1,"x":9}]}}],"2":[]}},{"t":{"1":"t134"},"c":{"1":[{"i":"t134","r":1,"x":3}]}},{"t":{"0":"t76"},"c":{"0":[{"i":"t76","r":1,"x":3}]}},{"t":{"29":"t69","33":"t70","38":"t73"},"c":{"29":[{"i":"t69","r":1}],"33":[{"i":"t70","r":1,"t":{"1":"t71","2":"t72"},"c":{"1":[{"i":"t71","r":1}],"2":[]},"x":6}],"38":[{"i":"t73","r":1,"t":{"1":"t74","2":"t75"},"c":{"1":[{"i":"t74","r":1}],"2":[]},"x":6}],"51":[]}},{"t":{"3":"t131"},"c":{"3":[{"i":"t131","r":1,"t":{"9":"t132"},"c":{"9":[{"i":"t132","r":1}]}}]}},{"c":{"2":[{"i":"c1778523502","r":1}]}},{"c":{"0":[]}},{"t":{"0":"t77"},"c":{"0":[{"i":"t77","r":1,"t":{"13":"t78"},"c":{"13":[{"i":"t78","r":1,"t":{"2":"t79"},"c":{"2":[{"i":"t79","r":1,"x":6}]}}]}}]}}]}</script>
133</body></html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.