{"id":74765,"date":"2017-10-31T11:49:58","date_gmt":"2017-10-31T11:49:58","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=74765"},"modified":"2022-05-24T20:05:39","modified_gmt":"2022-05-24T20:05:39","slug":"developing-google-chrome-extension-using-angular-4","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/javascript\/developing-google-chrome-extension-using-angular-4\/","title":{"rendered":"Developing a Google Chrome Extension using Angular 4"},"content":{"rendered":"<p>When I took on the task of developing Google Chrome extensions using Type Script and Angular 4, it seemed quite a daunting challenge; but after puzzling out how to register our Angular app as a Chrome extension, choose the optimum build process and configuration and to use the messaging system, it proved to be a great opportunity to learn some new technologies in depth.<\/p>\n<p>The use of Angular 4 makes the developer\u2019s life easier because the project acquires a well-defined structure. It can can be more easily maintained and enhanced thanks to the modularity of Angular. Also, TypeScript, as a superset of JavaScript, brings the object-oriented programming experience with strong typing to the development of client-side browser-based applications.<\/p>\n<p>This article describes the general approach, the problems I needed to face, and the solution I adopted.<\/p>\n<p>The basics of Google Chrome extensions development is best learned by using its official <a href=\"https:\/\/developer.chrome.com\/extensions\/getstarted\">documentation<\/a>, so I\u2019ll just give a brief overview and move quickly on to the detail of creating a simple Chrome Extension using Angular 4.<\/p>\n<p>A Chrome Extension can have three distinct front-end components:<\/p>\n<ul>\n<li><strong>Extension icon<\/strong> (browser action) \u2013 this is an icon that is displayed next to the browser\u2019s Omnibox (Google Chrome&#8217;s address bar)<\/li>\n<li><strong>Popup<\/strong> \u2013 This is a popup HTML page that is displayed when the extension icon is clicked. It can reference JavaScript and CSS files<\/li>\n<li><strong>Extension pages<\/strong> \u2013 these are HTML pages hosted by the extension. Each page can reference both JavaScript and CSS files<\/li>\n<\/ul>\n<p>Although the front-end part of the extension is crucial for the end user, the back-end part is more important from the developer perspective. The documentation lists the following elements of the back-end:<\/p>\n<ul>\n<li><strong>Content script<\/strong> \u2013 a JavaScript file that runs in the context of a page displayed in the browser tab. It has limited access to Chrome extension API (eg. it cannot influence other tabs), but it can do a lot of things in context of the page, eg.:\n<ul>\n<li>explore DOM elements<\/li>\n<li>inject new objects<\/li>\n<li>read the page\u2019s local storage<\/li>\n<\/ul>\n<\/li>\n<li><strong>Event page<\/strong> (background script) \u2013 a page that runs in the background, that is developed either as and HTML page or as a single JavaScript file. It has full access to the Chrome extension API. It is typically used to receive the requests and send replies to other extension elements. External requests (eg. to external APIs) should be executed in this part.<\/li>\n<li><strong>Extension\/popup page script<\/strong> \u2013 a JavaScript file referenced by HTML page hosted by extension. It has full access to Chrome extension API.<\/li>\n<\/ul>\n<h2>Manifest your stuff!<\/h2>\n<p>The configuration of our extension needs to be defined in a special manifest.JSON file. In this file, we can define the constituent parts of our extension. It also allows us to specify what kind of privileges our extension requests. The example below shows the manifest structure that defines these access-request elements.<\/p>\n<pre class=\"theme:vs2012 lang:js decode:true\">{\r\n    \"manifest_version\": 2,\r\n    \"name\": \"Test Extension\",\r\n    \"version\": \"1.0.0\",\r\n    \"permissions\": [ \"tabs\", \"activeTab\" ],\r\n    \"content_scripts\": [\r\n      {\r\n        \"matches\": [ \"http*:\/\/*\/*\" ],\r\n        \"js\": [ \"content-script.js\" ]\r\n      }\r\n    ],\r\n    \"background\": {\r\n      \"page\": \"index.html#\/event-page\",\r\n      \"persistent\": false\r\n    },\r\n    \"browser_action\": {\r\n      \"default_title\": \"Open Popup!\",\r\n      \"default_popup\": \"index.html#\/popup\"\r\n    },\r\n    \"icons\": {\r\n      \"19\": \"assets\/Icon-19.png\",\r\n      \"38\": \"assets\/Icon-38.png\"\r\n    },\r\n    \"content_security_policy\": \"script-src 'self' 'unsafe-eval'; object-src 'self'\"\r\n  }<\/pre>\n<p><em>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br \/>\nmanifest.json<\/em><\/p>\n<p>Some parts of this configuration needs additional comment:<\/p>\n<ul>\n<li>Most of the extensions need to have some permissions for access. In our example we want to be able to open new browser tabs and access currently-opened ones. That\u2019s why we define a <strong>tabs<\/strong> and an <strong>activeTab <\/strong>permission request<\/li>\n<li>It is mandatory to define the <strong>matches<\/strong> parameter for content script node. It allows us to limit the set of pages to which our content script will be added<\/li>\n<li>The <strong>persistent<\/strong> parameter of background script determines if we need our background page\/script to run continuously. By setting this param to<strong> false<\/strong> we agree that the script will get deactivated when it\u2019s not needed (eg. when the extension is not in use at the moment)<\/li>\n<li>The <strong>content_security_policy<\/strong> node is necessary to run JavaScript files compiled from Angular 4 sources.<\/li>\n<\/ul>\n<h2>Testing the extension locally<\/h2>\n<p>Normally, extensions are made available in the Extensions Store. Because we\u2019re just starting with the development, we prefer not to publish our extension there yet. Once we have our manifest file, we can test how it is working locally by turning the developer-mode on in the <em>Chrome:\/\/extensions<\/em> browser page. Having done that, the browser allows us to install the extension by pointing to the location on the hard drive:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"774\" height=\"353\" class=\"wp-image-74766\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2017\/10\/word-image-25.png\" \/><\/p>\n<p>Because we point the path to the location where the manifest file is stored, our extension gets installed. As you can see, the extension id gets assigned to it. You can use it to navigate to HTML pages that are included to the extension \u2013 once it\u2019s installed, Chrome is hosting its files. As an example, to open the popup.HTML page you need to go to the following address:<\/p>\n<p><a id=\"post-74765-OLE_LINK2\"><\/a><em>Chrome-extension:\/\/[extension-id]\/popup.html<\/em><\/p>\n<p>JavaScript and CSS files can be referenced in HTML files by relative paths, eg.:<\/p>\n<pre class=\"theme:vs2012 lang:js decode:true\">&lt;link rel=\"stylesheet\" type=\"text\/CSS\" href=\"popup.CSS\"&gt;\r\n  &lt;script type=\"text\/JavaScript\" src=\"event-page.js\"&gt;&lt;\/script&gt;<\/pre>\n<div class=\"note\">\n<p><strong>Note: <\/strong>Chrome doesn\u2019t treat <strong>index.html<\/strong> as the default page when you navigate to an extension address, eg. <em>Chrome-extension:\/\/[extension-id]<\/em>. In order to display a page, you always need to name the file that you want to see.<\/p>\n<\/div>\n<h2>Introducing Angular 4<\/h2>\n<p>There is no out-of-the-box support for Angular 2\/4 apps in Chrome Extensions. However, because these apps can be developed in TypeScript, which is then transcoded to JavaScript, it is possible to develop the app that is compactible with the extensions standard.<\/p>\n<p>As the prerequisite, we need to install some software in order to be able to start with Angular development. We will need to install:<\/p>\n<ul>\n<li>Node JS for Windows<\/li>\n<li>NPM Package Manager<\/li>\n<li>Angular CLI (<a href=\"https:\/\/github.com\/angular\/angular-cli\/wiki\">documentation<\/a>)<\/li>\n<\/ul>\n<p>The benefit of using Angular CLI is that it allows us to generate new modules, components and services using the command line. We can also generate new Angular application that way:<\/p>\n<pre class=\"theme:vs2012 lang:js decode:true\">ng new [project-name] \u2013routing<\/pre>\n<p>A routing flag will ensure that the routing-definition file will get generated to our app. As a result, we get ready-to-use applications with the local GIT repository that can be built (<em>ng build)<\/em> or served (<em>ng serve<\/em>). However, before we can use use it, we also need to install the required packages:<\/p>\n<pre class=\"theme:vs2012 lang:js decode:true\">cd [project-name]\r\n  npm install<\/pre>\n<p>After building the project, the resulting files can be found in the <em>dist<\/em> folder. That\u2019s the place where the extension manifest file should also be located. However, it is not a good idea to keep manifest file there, because the <em>dist<\/em> folder gets removed each time Angular CLI builds the project.<\/p>\n<p>It is better to store the <strong>manifest.json<\/strong> file in the<strong> src<\/strong> folder and declare it in <em>.<\/em><strong>angular-cli.json<\/strong> configuration file in the <strong>assets<\/strong> node:<\/p>\n<pre class=\"theme:vs2012 lang:js decode:true\">\u2026\r\n\"assets\": [\r\n  \"assets\",\r\n  \"favicon.ico\",\r\n  \"manifest.json\"\r\n],\r\n----------------------\r\n.angular-cli.json\r\n<\/pre>\n<p>Thanks to this, the manifest file will be always be present in the build output folder.<\/p>\n<h2>Manifest your Angular stuff!<\/h2>\n<p>As Angular CLI is focused on building single-page applications, it produces a single HTML file (<em>index.html<\/em>). However, as our application has a routing module, it can contain different content depending on the route. Firstly, let\u2019s set up some components that will be typical for a Chrome Extension:<\/p>\n<pre class=\"theme:vs2012 lang:js decode:true \">ng g component homepage\r\nng g component event-page\r\nng g component popup<\/pre>\n<p>As the result of the component generation command, Angular is automatically creating some files that define a new Component &#8211; the most basic building block of an UI in an Angular application. Here\u2019s the example component structure:<\/p>\n<ul>\n<li><strong>app\/homepage\/homepage.component.ts<\/strong> \u2013 component class definition (back-end part)<\/li>\n<li><strong>app\/homepage\/homepage.component.spec.ts<\/strong> \u2013 component tests class<\/li>\n<li><strong>app\/homepage\/homepage.component.html<\/strong> \u2013 an html template of the component (front-end part)<\/li>\n<li><strong>app\/homepage\/homepage.component.CSS<\/strong> \u2013 a stylesheet for component layout<\/li>\n<\/ul>\n<p>As you can see, Angular CLI takes care of placing the new files in their appropriate folder. It also generates the basic code in component files, so you don\u2019t need to copy-paste it from previously implemented components.<\/p>\n<p>Having our components initialized, we can use them to provide routing rules in a routing module:<\/p>\n<pre class=\"theme:vs2012 lang:js decode:true\">import { PopupComponent } from '.\/popup\/popup.component';\r\n  import { EventPageComponent } from '.\/event-page\/event-page.component';\r\n  import { HomepageComponent } from '.\/homepage\/homepage.component';\r\n  import { NgModule } from '@angular\/core';\r\n  import { Routes, RouterModule } from '@angular\/router';\r\n  const routes: Routes = [\r\n      { path: 'homepage', component: HomepageComponent },\r\n      { path: 'event-page', component: EventPageComponent },\r\n      { path: 'popup', component: PopupComponent },\r\n      { path: '', redirectTo: 'homepage', pathMatch: 'full' }\r\n  ];\r\n  @NgModule({\r\n      imports: [RouterModule.forRoot(routes, { useHash: true })],\r\n      exports: [RouterModule]\r\n  })\r\n  export class AppRoutingModule { }\r\n-----------------------------\r\napp-routing.module.ts\r\n<\/pre>\n<p>The <em>useHash<\/em> setting for router module enables routing in old-fashioned way, using the <em>#<\/em> sign. For example, to open our app routed to the Popup component, we need to use the following URL address:<\/p>\n<p><em>chrome-extension:\/\/[extension-id]\/index.html#\/popup<\/em><\/p>\n<p>Finally, we need to update the extension manifest file. It must point to the appropriate pages:<\/p>\n<pre class=\"theme:vs2012 lang:js decode:true\">{\r\n    \"manifest_version\": 2,\r\n    \"name\": \"Test Extension\",\r\n    \"version\": \"1.0.0\",\r\n    \"permissions\": [ \"tabs\", \"activeTab\" ],\r\n    \"background\": {\r\n      \"page\": \"index.html#\/event-page\",\r\n      \"persistent\": false\r\n    },\r\n    \"browser_action\": {\r\n      \"default_title\": \"Open Popup!\",\r\n      \"default_popup\": \"index.html#\/popup\"\r\n    },\r\n    \"icons\": {\r\n      \"19\": \"assets\/Icon-19.png\",\r\n      \"38\": \"assets\/Icon-38.png\"\r\n    },\r\n    \"content_security_policy\": \"script-src 'self' 'unsafe-eval'; object-src 'self'\"\r\n------------------\r\nmanifest.json\r\n<\/pre>\n<h2>Messaging<\/h2>\n<p>We\u2019ve successfully prepared our Angular-based app so that it can be registered as a Chrome extension. Now it\u2019s time to implement some functionality, so that the extension can do something useful.<\/p>\n<p>The Chrome browser delivers the API for extensions. It would be nice to take an advantage of it in our app. We can achieve that by installing Chrome types to our project:<\/p>\n<pre class=\"theme:vs2012 lang:js decode:true\">npm install @types\/Chrome \u2013save-dev<\/pre>\n<p>To ensure that the project will get correctly compiled once we start using Chrome typings, we need to include them in the <strong>tsconfig.app.json<\/strong> file in the <strong>compilerOptions\/types<\/strong> node:<\/p>\n<p><em>\u2026<br \/>\n<\/em> &#8220;types&#8221;: [&#8220;Chrome&#8221;]<em><br \/>\n&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br \/>\ntsconfig.app.json<\/em><\/p>\n<p>At some point, you might discover that you require some way of connecting between extension elements, allowing particular pieces of the extension to communicate with each other. To make it possible,the Chrome extensions API provides three messaging methods. Here\u2019s a brief description of those methods:<\/p>\n<ul>\n<li><strong>Chrome.tabs.connect<\/strong> &#8211; This opens a communication port to the selected browser tab, such as the currently active one; and allows it to send the message. This can\u2026\n<ul>\n<li>communicate within single extension elements<\/li>\n<li>define a method to handle an incoming response event<\/li>\n<\/ul>\n<\/li>\n<li><strong>Chrome.tabs.sendMessage<\/strong> &#8211; This sends the message to the selected browser tab, such as the currently active one. This allows us to \u2026\n<ul>\n<li>send message within currently executed extension by default.<\/li>\n<li>define an ID of the extension, especially to a different one<\/li>\n<li>define a method to handle an incoming response event<\/li>\n<\/ul>\n<\/li>\n<li><strong>Chrome.runtime.sendMessage<\/strong> \u2013 This sends the message that can be received by any part of the extension such as an event page or content script. This has similar characteristics to the <strong>Chrome.tabs.sendMessage<\/strong><\/li>\n<\/ul>\n<p>Once the message is sent, it can be received by other part of the extension. The Google Chrome Extension API provides the following message-receiving methods:<\/p>\n<ul>\n<li>\n<ul>\n<li><strong>Chrome.runtime.onConnect <\/strong>\u2013 This allows us to define a listener that receives messages sent through <strong>Chrome.tabs.connect<\/strong> communication port. This is typically implemented in a content script that is being executed in a defined set of tabs. The listener receives the message and communication port that allows it to send the response<\/li>\n<li><strong>Chrome.runtime.onMessage<\/strong> \u2013 This allows us to define a listener that receives messages sent through <strong>Chrome.tabs.sendMessage<\/strong> and <strong>Chrome.runtime.sendMessage<\/strong> methods. It can be implemented in any part of the extension: The listener receives the message, the sender information and the function allowing it to send the response<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h2>Missing the content script<\/h2>\n<p>As you may have noticed, we\u2019re missing the content script in our project. The problem is that it must be developed as a JavaScript file, whereas the Angular app assets need to be bootstrapped by an HTML tag. Unfortunately, Angular CLI doesn\u2019t provide us a way to easily build selected TypeScript files to separate out the JavaScript asset as long as we are using the standard building definition. Let\u2019s figure out what other options that we have instead:<\/p>\n<ol>\n<li>We can develop a content script as a JavaScript source file and add it to <em>assets<\/em> node in <strong>.angular-cli.json<\/strong><\/li>\n<li>We can develop it as single TypeScript source file without internal modules dependencies and add it to <em>scripts<\/em> node in <strong>.angular-cli.json<\/strong>. Angular CLI builds files defined in that node to <strong>scripts.bundle.js<\/strong> file with the dependencies to the code included in<strong> inline.bundle.js <\/strong>file<\/li>\n<li>Develop it as TypeScript application and use 3<sup>rd<\/sup> party build tool (eg. Gulp) to compile it.<\/li>\n<\/ol>\n<p>The decision you take will depend on the project needs. If your content script doesn\u2019t need to have complex logic, the first or second approach might be sufficient. Otherwise, it may be a good decision to define a Gulp building rule.<\/p>\n<h2>Building content script with Gulp<\/h2>\n<p>At first we will need to install Gulp:<\/p>\n<pre class=\"theme:vs2012 lang:js decode:true\">npm install gulp \u2013save-dev<\/pre>\n<p>We will also need to install the following npm packages:<\/p>\n<ul>\n<li>gulp-typescript<\/li>\n<li>gulp-sourcemaps<\/li>\n<li>gulp-uglify<\/li>\n<li>browserify<\/li>\n<li>vinyl-source-stream<\/li>\n<li>vinyl-buffer tsify<\/li>\n<\/ul>\n<p>Now we can develop the TypeScript modules that we would like to compile as the content script. Let\u2019s develop some logic that will implement the <strong>Chrome.runtime.onConnect<\/strong> and <strong>Chrome.runtime.onMessage<\/strong> listeners.<\/p>\n<pre class=\"theme:vs2012 lang:js decode:true\">export class ConnectListener {\r\n      constructor() {\r\n          this.initializeMessagesListener();\r\n      }\r\n      initializeMessagesListener() {\r\n          Chrome.runtime.onConnect.addListener(this.onConnectHandler.bind(this));\r\n      }\r\n      onConnectHandler(port: Chrome.runtime.Port) {\r\n          port.onMessage.addListener(this.onConnectMessageHandler.bind(this));\r\n      }\r\n      onConnectMessageHandler(msg, port) {\r\n          console.log('Received connection message: ' + msg);\r\n          const response = 'Greetings!';\r\n          port.postMessage(response);\r\n      }\r\n  }\r\n------------------------------------------------------\r\ncontent-script\/connect-listener.ts\r\n<\/pre>\n<pre class=\"theme:vs2012 lang:js decode:true\">export class RuntimeListener {\r\n      constructor() {\r\n          this.initializeMessagesListener();\r\n      }\r\n      initializeMessagesListener() {\r\n          Chrome.runtime.onMessage.addListener((message, sender, sendResponse) =&gt; {\r\n              const command = message['command'];\r\n              console.log('Received runtime command: ' + command);\r\n              const response = { message: 'Aye!' };\r\n              sendResponse(response);\r\n          });\r\n      }\r\n-------------------------------------------\r\ncontent-script\/runtime-listener.ts\r\n<\/pre>\n<pre class=\"theme:vs2012 lang:js decode:true\">import { ConnectListener } from '.\/connect-listener';\r\nimport { RuntimeListener } from '.\/runtime-listener';\r\nconst runtimeListener = new RuntimeListener();\r\n--------------------------------------\r\ncontent-script\/boot.ts\r\n<\/pre>\n<p>Finally, we will need to define a Gulp build that will compile our content script to JavaScript file:<\/p>\n<pre class=\"theme:vs2012 lang:js decode:true\">var gulp = require('gulp');\r\n  var ts = require('gulp-typescript');\r\n  var exec = require('child_process').exec;\r\n  var browserify = require(\"browserify\");\r\n  var source = require('vinyl-source-stream');\r\n  var buffer = require('vinyl-buffer');\r\n  var uglify = require('gulp-uglify');\r\n  var sourcemaps = require('gulp-sourcemaps');\r\n  var tsify = require(\"tsify\");\r\n  gulp.task('ng-build', function(cb) {\r\n      console.log('running ng build...');\r\n      exec('ng build', function (err, stdout, stderr) {\r\n          console.log(stdout);\r\n          console.log(stderr);\r\n          cb(err);\r\n          return true;\r\n      });\r\n  });\r\n  gulp.task('content-script', function() {\r\n      return browserify({\r\n              basedir: '.',\r\n              debug: true,\r\n              entries: 'content-script\/boot.ts'\r\n          })\r\n          .plugin(tsify)\r\n          .bundle()\r\n          .pipe(source('content-script.js'))\r\n          .pipe(buffer())\r\n          .pipe(sourcemaps.init({loadMaps: true}))\r\n          .pipe(uglify())\r\n          .pipe(sourcemaps.write('.\/'))\r\n          .pipe(gulp.dest('.\/dist\/'));\r\n  });\r\ngulp.task('default', ['ng-build', 'content-script']);\r\n------------\r\ngulpfile.js\r\n<\/pre>\n<p>The default tasks consist of 2 sub tasks: the first of them simply performs the <strong>ng build<\/strong> command; the second task builds the minified content script to <strong>dist\/content-script.js<\/strong> file. Now we can bring back the content script to our manifest file:<\/p>\n<pre class=\"theme:vs2012 lang:js decode:true\">\u2026\r\n\"content_scripts\": [\r\n    {\r\n      \"matches\": [\"*:\/\/*\/*\"],\r\n      \"js\": [\"content-script.js\"]\r\n    }\r\n  ],\r\n\u2026\u2026\u2026\u2026\u2026...\r\nmanifest.json\r\n<\/pre>\n<p>To build the whole application (including the content script) we can simply use the short command:<\/p>\n<pre class=\"theme:vs2012 lang:js decode:true\">gulp<\/pre>\n<h2>Sending messages to the content script<\/h2>\n<p>Now that we have implemented the content script that handles messages, it\u2019s time to implement the sending part. Let\u2019s do this in Popup component backend:<\/p>\n<pre class=\"theme:vs2012 lang:js decode:true\">import { Component, OnInit } from '@angular\/core';\r\n  @Component({\r\n      selector: 'app-popup',\r\n      templateUrl: '.\/popup.component.html',\r\n      styleUrls: ['.\/popup.component.CSS']\r\n  })\r\n  export class PopupComponent implements OnInit {\r\n      ngOnInit() {\r\n          this.connectWithContentScript();\r\n          this.sendContentScriptCommand();\r\n      }\r\n      connectWithContentScript = () =&gt; {\r\n          const tabQueryData = { active: true, currentWindow: true };\r\n          Chrome.tabs.query(tabQueryData, (tabs) =&gt; {\r\n              const port = Chrome.tabs.connect(tabs[0].id);\r\n              port.postMessage('Hello!');\r\n              port.onMessage.addListener((response) =&gt; {\r\n                  alert('Content script responded: ' + response);\r\n              });\r\n          });\r\n      }\r\n      sendContentScriptCommand() {\r\n          const tabQueryData = { active: true, currentWindow: true };\r\n          Chrome.tabs.query(tabQueryData, (tabs) =&gt; {\r\n              const commandMessage = { command: 'salute' };\r\n              Chrome.tabs.sendMessage(tabs[0].id, commandMessage, (response) =&gt; {\r\n                  const responseMessage = response['message'];\r\n                  alert('Content script responded: ' + responseMessage);\r\n              });\r\n          });\r\n      }\r\n  }\r\n------------------------------------------\r\napp\/popup\/popup.component.ts\r\n<\/pre>\n<p>The messages are getting sent to the content script each time we open the popup. Unless the page browser that is currently displaying matches the pattern that is provided in the manifest file for the content script, nothing will happen. Otherwise the popup will show some alerts:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"481\" height=\"494\" class=\"wp-image-74767\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2017\/10\/word-image-26.png\" \/><\/p>\n<h2>Summary<\/h2>\n<p>Although the task of implementing Chrome extensions using Angular CLI might cause some initial problems, these are easily dealt with. We can then benefit from a nicely modularized app that can easily be enhanced and developed using TypeScript. By combining this with the Chrome API we can develop powerful and fast-working extensions.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It may seem a tricky task to create a Chrome extension with TypeScript and Angular 4 but it turns out to be relatively simple. The advantage is that you gain the modularity of Angular and the object-oriented programming experience with strong typing of Typescript. Jacub explains how it is done.&hellip;<\/p>\n","protected":false},"author":316282,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[146992,146044],"tags":[],"coauthors":[50410],"class_list":["post-74765","post","type-post","status-publish","format-standard","hentry","category-angular","category-javascript"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/74765","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/316282"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=74765"}],"version-history":[{"count":9,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/74765\/revisions"}],"predecessor-version":[{"id":94476,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/74765\/revisions\/94476"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=74765"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=74765"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=74765"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=74765"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}