# Chrome Extension with Angular — from Zero to a Little Hero

Published 2021-07-14 · https://www.justjeb.com/post/chrome-extension-with-angular-from-zero-to-a-little-hero

In this article we're going to review Chrome Extensions architecture and learn how to incorporate Angular into it. We will be starting from scratch with ng new and finishing with a fully functional Chrome extension, convenient dev environment and hopefully a decent understanding of how it works.

*or a short how-to and why-in-the-world*

![Google Chrome logo with an Angular shield and letter A inside a white puzzle piece at its centre](https://www.justjeb.com/_astro/img-01._tB2DsXp_2tTL1i.webp)

Hey folks.

Today we’re going to explore the process of developing a Chrome extension with Angular.

If you're looking for the older version of the article (covering Manifest V2) you can find it [on Medium](https://medium.com/angular-in-depth/chrome-extension-with-angular-why-and-how-778200b87575).

If you’re not feeling like reading and going through the code you can [watch my talk on this topic](https://www.youtube.com/watch?v=T1gLcRa2Fm0) instead (note that the talk only covers Manifest V2).

We will be starting from scratch with **ng new** and finishing with a fully functional Chrome Extension, convenient dev environment and hopefully a decent understanding of how it works.

But first let’s clear out one thing…

![A man in surgical scrubs and cap pulling his face mask down, captioned WHY?](https://www.justjeb.com/_astro/img-02.yHwvxjy3_SsWd1.webp)

## Why would one want to develop a Chrome extension

There are couple of reasons that would make you want to develop a Chrome extension. First (and the most improbable one) is to make money. Yes, there are Chrome extensions that are published in Chrome Store and developers get paid for it. And if you have an idea for an extension and a business model that will be profitable — well, who am I to tell you not to go for it.

However, I believe that the main use case for developing an extension is to serve your needs as a developer.

For example at my company we have a handful of Chrome extensions that were developed by developers for developers and BOY, how much time did it save me!

I’m sure that **you** can think of at least one thing that you are doing repeatedly over and over during the development cycle, a thing that can be easily automated with a simple Chrome extension.

Today you’re going to learn how to build one and in order to have even more fun we’re going to do that with Angular.

## What a Chrome extension consists of

![A diagram depicting Chrome extension architecture: background.js sits above a sketch of a browser window whose page area is outlined in red, popup.html hangs off the orange toolbar icon, popup.js exchanges dashed two-way messages with background.js and with popup.html, contentscript.js exchanges dashed two-way messages with the page, and envelope icons carry messages from contentscript.js up to background.js and popup.js](https://www.justjeb.com/_astro/img-03.fiNTPwKc_25XqPn.webp)

### Background script (service worker)

> The extension service worker is the extension's event handler; it contains listeners for browser events that are important to the extension. It lies dormant until an event is fired then performs the instructed logic; it is only loaded when it is needed and unloaded when it goes idle. The service worker has access to all the [Chrome APIs](https://developer.chrome.com/docs/extensions/reference/api), as long it declares the required permissions in the **manifest.json**.

### Content script

> Content scripts are files that run in the context of web pages. By using the standard [Document Object Model](https://www.w3.org/TR/DOM-Level-2-HTML/) (DOM), they are able to read details of the web pages the browser visits, make changes to them, and pass information to their parent extension.

*We’re not going to touch these in this tutorial (well, almost)*

### Popup

> An action's popup will be shown when the user clicks on the extension's action button in the toolbar. The popup can contain any HTML contents you like, and will be automatically sized to fit its contents. The popup cannot be smaller than 25x25 and cannot be larger than 800x600.

*Spoiler: this is where we use Angular*

## Communication between pages

> Different components in an extension can communicate with each other using [message passing](https://developer.chrome.com/docs/extensions/mv3/messaging/). Either side can listen for messages sent from the other end, and respond on the same channel.

In this tutorial we’re going to focus on popup and background page but there are [much more options](https://developer.chrome.com/docs/extensions/develop) than you can use, make sure to check them out.

So…

![A woman in a pink costume and curly white wig shouting into a vintage microphone, captioned LET THE GAMES BEGIN](https://www.justjeb.com/_astro/img-04.DAUAve1q_1iFYPj.webp)

As I mentioned we’re going to start from scratch with **ng new**. Obviously we need Angular CLI for that, so if for some weird reason you don’t have it installed yet, please do:

```bash
npm i -g @angular/cli
```

## Creating a project

1. Start with creating a new project: `ng new angular-chrome-extension`

2. **cd angular-chrome-extension**

3. Create a manifest in the **/src/** folder

```json
{
  "name": "Great Extension",
  "version": "1.0",
  "description": "Build an Extension with Angular",
  "manifest_version": 3
}
```

> Extensions start with their [**manifest**](https://developer.chrome.com/docs/extensions/reference/manifest). Create a file called *manifest.json* and include the following code, or download the file [**here**](https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/functional-samples/tutorial.hello-world).

## Loading the extension into Chrome

We’re going to load the unpacked extension from the **dist** directory so we need the manifest to be there as well.

Let’s include it in **assets** *(***angular.json***):*

```js
  "projects": {
    "angular-chrome-extension": {
      ...
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            ...
            "assets": [
              "src/favicon.ico",
              "src/assets",
              "src/manifest.json"
            ],
            ...
          },
          ...
```

Run **ng build** to make sure **manifest.json** is indeed present in **dist** directory.

Time to load it into Chrome and watch it working:

- Open Chrome.

- Open the Extensions Management page by navigating to **chrome://extensions.**

- Enable Developer Mode by toggling the switch next to **Developer Mode.**

![The top right corner of the Chrome extensions management page with the Developer mode toggle switched on, a green arrow pointing at it](https://www.justjeb.com/_astro/img-05.jgH0l9Ct_8QzHj.webp)

- Click the Load unpacked button and select the extension directory **(dist/angular-chrome-extension).**

![The Chrome Extensions page with the Load unpacked, Pack extension and Update buttons that Developer mode reveals, a green arrow pointing at Load unpacked, and an installed 1Password extension card version 4.7.5.90 on the right](https://www.justjeb.com/_astro/img-06.1rvxEs20_1awhoi.webp)

- Now you should be able to see the extension in Chrome:

![The new extension's grey G icon in the Chrome toolbar with a green arrow pointing at it, its right-click menu open below listing Getting Started Example, Options, Remove from Chrome..., Hide in Chrome Menu and Manage Extensions](https://www.justjeb.com/_astro/img-07.De6qkHiZ_Z1Mk858.webp)

The menu entry in this screenshot reads Getting Started Example, which was the name in the manifest I started from. Yours will show Great Extension.

However, as you’ve probably noticed, there is nothing in the extension apart from the default icon and the title we gave it in *manifest.json.*

The reason is that we didn’t define any [action](https://developer.chrome.com/docs/extensions/reference/api/action). Let’s define one:

```json
{
  "name": "Great Extension",
  "version": "1.0",
  "description": "Build an Extension with Angular",
  "manifest_version": 3,
  "action": {
      "default_popup": "index.html"
  }
}
```

*The* [***Action API***](https://developer.chrome.com/docs/extensions/reference/action/) *controls the extension's action (toolbar icon). It can either open a* [***popup***](https://developer.chrome.com/docs/extensions/reference/api/action#popup) *or trigger some functionality when it's* [***clicked***](https://developer.chrome.com/docs/extensions/reference/api/action#event-onClicked)*.*

Now let’s run **ng build** again and reload our extension:

![The Great Extension 1.0 card on the Chrome extensions page, described as Build an Extension with Angular with ID nanplceegkgnhfjeljdboffppmapkdco, a green arrow pointing at the circular reload button beside the enabled toggle](https://www.justjeb.com/_astro/img-08.CDMFeJZ3_Z2nBHeQ.webp)

Click on your extension and... Voila!

![The default Angular welcome page rendered inside the extension popup, with a rocket icon and the text angular-chrome-extension app is running, a Resources list of Learn Angular, CLI Documentation, Angular Blog and Angular DevTools, and a Next Steps heading](https://www.justjeb.com/_astro/img-09.DrIquNi1_1DNG8M.webp)

It works with Angular, yay!

However, if you look at the extensions page you may notice that there are some errors:

![The Great Extension card on the Chrome extensions page now showing a red Errors button next to Details and Remove, with a green arrow pointing at it](https://www.justjeb.com/_astro/img-10.BFU5XaCB_jOYUj.webp)

And if you click on the "Errors" button you'll see the details:

![The Chrome extension Errors page showing a Content Security Policy failure reading Refused to execute inline event handler because it violates the directive script-src self wasm-unsafe-eval, with Context index.html and a stack trace at index.html line 7, and that line highlighted in the source as a stylesheet link with media print and an onload handler](https://www.justjeb.com/_astro/img-11.Bxd6qpm9_2gDrOY.webp)

What does it mean?

In **index.html** that is generated by Angular CLI in **dist** folder you can find the following line of code: `<link rel="stylesheet" href="styles.css" media="print" onload="this.media='all'">` As the error message suggests, the default security policy disallows execution of inline scripts, and event handlers considered as such. Therefore the solution would be to remove the **onload** event handler.

Removing it manually wouldn't help, since it's generated upon every build, however, you can disable it in **angular.json** by changing the optimization settings. Add the following to your **angular.json**:

```js
"architect": {
  "build": {
      "options": {
        ...
        "optimization": {
          "styles": {
            "inlineCritical": false
          }
        }
      }
    }
    ...
  }
  ...
}
```

This will disable the inlining of critical styles but since we're not fetching the files from a remote server but rather have everything packed inside the extension we don't need this optimization anyways.

Build, reload - error gone.

ANGULAR IN CHROME EXTENSION — CHECK

## Live reload and ng serve

Ideally we would want to use **ng serve** just like we use it for web applications and expect the extension to be updated automatically every time there was a change. Unfortunately that’s not the case.

We can’t use **ng serve** here because it writes the bundle into the memory of the dev server, while Chrome needs an actual physical folder on disk in order to load the extension.

We can use **`ng build --watch`** though.

This command watches the sources (and assets) and rebuilds the relevant files while updating the bundles in **dist** directory. It will take care of updating the **dist** folder when one of the sources or the manifest changes.

The extension is still not updated automatically but using **`ng build --watch`** does make your life easier:

1. If you’ve changed any source file that affects extension popup, **re-open the popup.** *Chrome loads the popup directly from* ***dist*** *folder every time you open it so any changes to the popup are applied automatically when it is re-opened.*

2. If you’ve changed the manifest you have to **reload the extension on the extensions page** (just like we did before). *Manifest is a set of definitions, rules and instructions for your extension so it is applied when the extension is installed. By pressing the reload button you’re effectively reinstalling the unpacked extension.*

3. Background scripts *can* be reloaded automatically, but we’ll cover it later.

## Make the extension do stuff

At the moment we have an extension that shows the default Angular app page as a popup and other than that does absolutely nothing. Let’s change it.

First we’ll define what this extension will do:

1. It will be active on all sites except for the **google.com** domain

2. It will contain a color picker

3. It will change the background color of a page to the selected color upon pressing a button

## Disable extension for certain sites

### The Service Worker

To remind you:

> The extension service worker is the extension's event handler; it contains listeners for browser events that are important to the extension. It lies dormant until an event is fired then performs the instructed logic; it is only loaded when it is needed and unloaded when it goes idle.

In our case we would like to listen to web navigation completed event, do the URL matching and disable the extension if there is a match.

1. Install the types for Chrome API: `yarn add -D @types/chrome`

2. Add **chrome** to **types** array in **tsconfig.app.json**

3. Create a file called **background.ts** in the **src/** folder:

```ts
chrome.webNavigation.onCompleted.addListener(() => {
  chrome.tabs.query({ active: true, currentWindow: true }, ([{ id }]) => {
    if (id) {
      chrome.action.disable(id);
    }
  });
}, { url: [{ hostContains: 'google.com' }] });
```

Chrome APIs are mostly asynchronous so this is what happens here:

1. We add a listener that once a user goes to any URL that contains **google.com** will…

2. …query the open tabs to pick an active one and once there is an answer will…

3. …disable the action on this tab

Now let’s run **ng build**.

Did it compile the service worker? Obviously it did not. The file exists but it is not referenced from anywhere and there is no way for Angular CLI to actually know that this script is part of the build.

We need to integrate the service worker into the build process.

## Compiling service worker as part of Angular build

Since the service workers run in a context different than the extension’s popup we cannot just add it to the **scripts** section in **angular.json**. Anything that’s in scripts section is compiled by Angular CLI into **scripts** chunk and loaded by **index.html** (a popup context in our case). This is also the reason we can’t just import the background script in **main.ts** or any other file that is compiled as part of popup build.

What we *can* do is create another entry for background script in Webpack build that Angular CLI runs behind the scenes. For that purpose we’ll use Custom Webpack Builder:

- Install it: `yarn add -D @angular-builders/custom-webpack`

- Update **angular.json**:

```js
"projects": {
  "angular-chrome-extension": {
    ...
    "architect": {
      "build": {
        "builder": "@angular-builders/custom-webpack:browser",
        "options": {
          "customWebpackConfig": {
            "path": "./custom-webpack.config.ts"
          }
          ...
        },
        ...
```

- In the root of your workspace create a file called **custom-webpack.config.ts** with the following content:

```ts
import type { Configuration } from 'webpack';

module.exports = {
  entry: { background: 'src/background.ts' },
} as Configuration;
```

- Add **src/background.ts** to **files** array in **tsconfig.app.json**.

- Run **ng build**.

You should see it in the output now (and also in **dist/** directory):

![Terminal output from ng build listing the Initial Chunk Files with hashed names: main.9d60e0ff3aada226ca34.js at 133.80 kB, polyfills at 36.00 kB, runtime at 1.03 kB, background.a6c2cb2563540ec13d76.js at 381 bytes and styles at 0 bytes](https://www.justjeb.com/_astro/img-12.DNUMnVLR_2uCuxf.webp)

Notice that the file names contain hash, which enables better cache busting for web apps. In our case it is irrelevant, moreover, it might complicate our build setup, since we need to specify background and content scripts in **manifest.json** and it is much easier to do when the names are constant.

Let’s disable this option in **angular.json**:

```js
"architect": {
  "build": {
    ...
    "configurations": {
      "production": {
        ...
        "fileReplacements": [
          {
            "replace": "src/environments/environment.ts",
            "with": "src/environments/environment.prod.ts"
          }
        ],
        "outputHashing": "none"
      },
      ...
```

Running **ng build** now should result in following:

![Terminal output from ng build with output hashing disabled, listing main.js at 133.80 kB, polyfills.js at 36.00 kB, runtime.js at 1.03 kB, background.js at 381 bytes, styles.css at 0 bytes and an Initial Total of 171.20 kB](https://www.justjeb.com/_astro/img-13.5Ukd8RP3_ZD67DG.webp)

Now we need to make Chrome aware of this background script. We also need to grant some permissions to our extension. Update your **manifest.json**:

```json
{
  "name": "Great Extension",
  "version": "1.0",
  "description": "Build an Extension with Angular",
  "manifest_version": 3,
  "permissions": [
      "activeTab",
      "webNavigation"
  ],
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  "action": {
      "default_popup": "index.html"
  }
}
```

If you build & reload the extension now and go to **<http://google.com>** you’ll see it’s still active. If you dig a bit into it you’ll see that the background script is *being loaded* but *it is not being executed*. And there is a reason for that.

Let’s take a look at the compiled version of our **background.ts**:

```js
(self["webpackChunkangular_chrome_extension"] = self["webpackChunkangular_chrome_extension"] || []).push([["background"],{

/***/ 4788:
/*!***************************!*\
  !*** ./src/background.ts ***!
  \***************************/
/***/ (() => {

"use strict";

chrome.webNavigation.onCompleted.addListener(() => {
  // Blah blah the rest of our code
}, { url: [{ hostContains: 'google.com' }] });
```

As you can see our code is being pushed into webpack chunks array but no one invokes it. So yes, the script is being loaded but nothing happens, because no one actually runs the code. Angular CLI (Webpack) puts the code that is responsible for chunks execution in a separate chunk called **runtime**. Since this code split doesn't give us much in terms of performance (remember, we have all the files available locally), we can just disable this behavior for **background.ts** entry:

```ts
import type { Configuration } from 'webpack';

module.exports = {
  entry: { background: { import: 'src/background.ts', runtime: false } },
} as Configuration;
```

Run the build again and notice the change in **background.js:**

```js
/******/ (() => { // webpackBootstrap
/******/ 	"use strict";
var __webpack_exports__ = {};
/*!***************************!*\
  !*** ./src/background.ts ***!
  \***************************/

chrome.webNavigation.onCompleted.addListener(() => {
 // Blah blah the rest of our code
}, { url: [{ hostContains: 'google.com' }] });

/******/ })()
;
//# sourceMappingURL=background.js.map
```

As you can see the code is executed right away, the moment it's loaded, no Webpack runtime whatsoever.

Reload the extension, go to **<http://google.com>** and see that the extension is disabled for this site.

ACTIVE ON ALL SITES EXCEPT GOOGLE DOMAIN — CHECK

## Background page live reload? Who said live reload?

**Note: this section was written before webpack-ext-reloader supported Manifest v3. Support landed in September 2023 (**[**issue**](https://github.com/SimplifyJobs/webpack-ext-reloader/issues/28)**), and the steps below haven't been re-run against it since.**

Currently the background script behaves much like the manifest — it is reloaded only when you reload the extension. However, unlike the manifest (that is updated rarely after the initial phase of development) it contains significant part of our code and is updated quite often during the development. And believe me, it is very annoying to go to Extensions page and press Reload button every time you change a name of a variable in the background script.

So what can be done here? Since we’ve already incorporated Custom Webpack Builder in our build chain we can benefit from it a bit more.

There is a [webpack-ext-reloader](https://www.npmjs.com/package/webpack-ext-reloader) plugin that does exactly what we want. It augments background script code with code that listens to the changes of the chunks and when there was a change in the background chunk it tells Chrome to reload the extension.

We want this augmentation only during development so let’s create another webpack config that will make use of our initial configuration and add **webpack-ext-reloader** plugin on top of it:

- Install extension reloader plugin: `yarn add -D webpack-ext-reloader`

- Create a file named **custom-webpack-dev.config.ts** in the root of your workspace:

```ts
import type { Configuration } from 'webpack';
const ExtensionReloader = require('webpack-ext-reloader');
const config = require('./custom-webpack.config');

module.exports = {
  ...config,
  mode: 'development',
  plugins: [
    new ExtensionReloader({
      reloadPage: true, // Force the reload of the page also
      entries: { // The entries used for the content/background scripts or extension pages
        background: 'background',
      }
    })
  ]
} as Configuration;
```

*Refer to the plugin’s README for detailed API description*

- Add this config to your **development** configuration in **angular.json**:

```js
"architect": {
  "build": {
    "builder": "@angular-builders/custom-webpack:browser",
    ...
    "configurations": {
      "production": {
        ...
        "outputHashing": "none"
      },
      "development": {
        ...
        "customWebpackConfig": {
          "path": "./custom-webpack-dev.config.ts"
        }
      }
```

Let’s make sure it works:

1. Run the build with this config: **`yarn build --configuration=development`**

2. Reload the extension (the old background script still lacks the live reload code, so you have to do it one last time)

3. Navigate to **<http://google.com>**

4. See the action disabled

5. Go to **background.ts** and change the URL match pattern to **blahblah**

6. Navigate to **<http://google.com>**

7. See the action enabled

*Side note: we could have written the background script in JavaScript instead of TypeScript and add it to the assets just like we did with manifest.json but it is not recommended as it diversifies your code. You also lose the live reload ability which is a dealbreaker for me.*

*Side note 2: everything that we’ve done here for background script (including building it with ng build , live reload etc.) is applicable to content script as well.*

## Adding color picker

This will be an easy one. We will use [ngx-color-picker](https://www.npmjs.com/package/ngx-color-picker) component.

- Install ngx-color-picker: `yarn add ngx-color-picker`

- Add **ColorPickerModule** to imports in **app.module.ts***:*

```ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ColorPickerModule } from 'ngx-color-picker';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    ColorPickerModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

- Add color property to **app.component.ts**:

```ts
export class AppComponent {
  color = '#ffffff';
}
```

- Replace default Angular content with color picker in **app.component.html**:

```html
<span [style.color]="color" 
  [cpToggle]="true" 
  [cpDialogDisplay]="'inline'" 
  [cpPositionRelativeToArrow]="true"
  [(colorPicker)]="color" 
  [cpOKButtonText]="'Apply'" 
  [cpOKButton]="true">
</span>
```

*Refer to the color picker README for detailed API description*

- If you don’t want the white frame around the color picker remove the margins from **body** in **styles.scss***:*

```
body {
    margin: 0;
}
```

- Open the popup and behold:

![The extension popup open under the Chrome toolbar showing an inline colour picker: a red saturation and brightness square, a rainbow hue slider, an alpha slider, a hex field reading #ffffff and an Apply button](https://www.justjeb.com/_astro/img-14.IKsV3-Kn_257rSj.webp)

ADD COLOR PICKER — CHECK

*But currently it doesn’t do anything. Which brings us to the next (and the last) milestone.*

## Change the background color of a page

Now that we have all the tools in place we want to actually apply the picked color to the site background.

Let’s do that:

- Allow your component to execute [programmatically injected](https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts#programmatic) content scripts by adding **scripting** permissions to your manifest:

```json
{
  "name": "Great Extension",
  "version": "1.0",
  "description": "Build an Extension with Angular",
  "manifest_version": 3,
  "permissions": [
      "activeTab",
      "webNavigation",
      "scripting"
  ],
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  "action": {
      "default_popup": "index.html"
  }
}
```

- Add **colorize** method to **app.component.ts**:

```ts
export class AppComponent {
  color = '#ffffff';

  public colorize() {
    chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
      chrome.scripting.executeScript({
        target: { tabId: tabs[0].id! },
        func: updateBackgroundColor,
        args: [this.color]
      });
    });
  }
}

const updateBackgroundColor = (color: string) => document.body.style.backgroundColor = color;
```

- Bind the method to **colorPickerSelect** event (called upon Apply button pressed):

```html
<span [style.color]="color" 
  [cpToggle]="true" 
  [cpDialogDisplay]="'inline'" 
  [cpPositionRelativeToArrow]="true"
  [(colorPicker)]="color" 
  [cpOKButtonText]="'Apply'" 
  [cpOKButton]="true" 
  (colorPickerSelect)="colorize()">
</span>
```

- Open the popup, select a color and press Apply. Gee, did it actually work?!

![The same colour picker popup with a red swatch and the hex field reading #e61212, and the page behind it filled with that same red](https://www.justjeb.com/_astro/img-15.-cKZby4s_Z1yAauB.webp)

## Bonus: saving the last selected color

To make things neater we’d want to save the last selected color and select it next time we open the popup.

For this we’ll need a few things:

- Add **storage** permission to the manifest and reload the extension:

```json
{
  "name": "Great Extension",
  "version": "1.0",
  "description": "Build an Extension with Angular",
  "manifest_version": 3,
  "permissions": [
      "activeTab",
      "webNavigation",
      "storage",
      "scripting"
  ],
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  "action": {
      "default_popup": "index.html"
  }
}
```

- Add **updateColor** method to **app.component.ts** and bind it to **colorPickerChange** event:

```ts
export class AppComponent implements OnInit {
  color = '#ffffff';
  ...
  public updateColor(color: string) {
    chrome.storage.sync.set({ color });
  }
  ...
}
```

```html
<span [style.color]="color" 
  [cpToggle]="true" 
  [cpDialogDisplay]="'inline'" 
  [cpPositionRelativeToArrow]="true"
  [(colorPicker)]="color" 
  [cpOKButtonText]="'Apply'" 
  [cpOKButton]="true" 
  (colorPickerChange)="updateColor($event)"
  (colorPickerSelect)="colorize()">
</span>
```

- Set initial color from the storage once the popup is created:

```ts
  ngOnInit(): void {
    chrome.storage.sync.get('color', ({ color }) => {
      this.color = color;
    });
  }
```

- Optional: set a default color in background script:

```ts
chrome.runtime.onInstalled.addListener(() => {
  chrome.storage.sync.set({ color: '#3aa757' });
});

chrome.webNavigation.onCompleted.addListener(() => {
  chrome.tabs.query({ active: true, currentWindow: true }, ([{ id }]) => {
    if (id) {
      chrome.action.disable(id);
    }
  });
}, { url: [{ hostContains: 'google.com' }] });
```

Now the extension will remember the last selected color and select it when you open the popup.

## If something goes wrong

Errors are an integral part of the development process, therefore it’s important to know how to tackle them. If something goes wrong and your extension is not loading properly you can see the errors in two ways:

- **Errors** button on your extension configuration page:

![The Great Extension card on the Chrome extensions page with Details, Remove and a red Errors button](https://www.justjeb.com/_astro/img-16.CErF4JYG_Z1R0UCc.webp)

- Right click on the popup and Inspect Popup:

![The right-click menu on the extension's toolbar icon listing a greyed out Great Extension heading, Options, Remove from Chrome..., Unpin, Manage Extensions and Inspect Popup, with Inspect Popup highlighted at the bottom](https://www.justjeb.com/_astro/img-17.DSEm1hvd_Zlm0p1.webp)

If you want to debug your service worker then you can inspect it from here:

![The Great Extension card on the Chrome extensions page showing an Inspect views service worker link below the extension ID, with a green arrow pointing at it](https://www.justjeb.com/_astro/img-18.BxP7lQoq_1wPmhp.webp)

## Finishing words

While most of the extensions do not have a complicated UI, there are some of them, such as [Checker Plus](https://chrome.google.com/webstore/detail/checker-plus-for-google-c/hkhggnncdpfibdhinjiegagmopldibha?hl=en) or [1Password](https://chrome.google.com/webstore/detail/1password-x-%E2%80%93-password-ma/aeblfdkhhhdcdjpifhhbdiojplfjncoa?hl=en), that have quite complicated one. These extensions would be easier to build and maintain using one of the popular web frameworks, among them Angular.

I hope this article gives you all the necessary tools and understanding for developing a Chrome Extension with Angular in convenient and efficient way.

All the sources are available on [Github](https://github.com/just-jeb/angular-chrome-extension).
