{"id":1737,"date":"2021-05-24T10:09:33","date_gmt":"2021-05-24T10:09:33","guid":{"rendered":"https:\/\/lvboard.infostore.in.ua\/?p=1737"},"modified":"2021-05-24T10:09:33","modified_gmt":"2021-05-24T10:09:33","slug":"building-web-desktop-and-mobile-apps-from-a-single-codebase-using-angular","status":"publish","type":"post","link":"https:\/\/lvboard.infostore.in.ua\/?p=1737","title":{"rendered":"Building Web, Desktop and Mobile apps from a single codebase using Angular"},"content":{"rendered":"\n<p>From monoliths to&nbsp;<a href=\"https:\/\/blog.nrwl.io\/misconceptions-about-monorepos-monorepo-monolith-df1250d4b03c\" target=\"_blank\" rel=\"noreferrer noopener\">monorepos<\/a>, every application architecture has its own advantages and disadvantages. Monorepo-style development is an approach where you develop multiple projects in the same repository.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>Projects in a monorepo can depend on each other. This allows code-sharing between projects. For instance, you can define a single interface which the frontend and backend teams can use.<\/p>\n\n\n\n<p>Code changes in one project do not necessarily force all other projects to be rebuilt. You only rebuild or retest the projects affected by the code change. As a result, your Continuous Integration pipeline is faster, giving teams working in a monorepo more independence.<\/p>\n\n\n\n<p>This article will show how we can organize our code in a monorepo-style architecture and build a simple (yet extensible) Angular application that targets several platforms including iOS, Android and Desktop (Windows, Linux and macOS).<\/p>\n\n\n\n<h3 id=\"why-angular\">Why Angular?<\/h3>\n\n\n\n<p>Angular provides tooling which allows you to build features quickly with simple declarative templates. This article assumes basic knowledge of Angular and its&nbsp;<a href=\"https:\/\/angular.io\/guide\/styleguide\" target=\"_blank\" rel=\"noreferrer noopener\">best practices<\/a>. If you have never used it before, I encourage you to&nbsp;<a href=\"https:\/\/angular.io\/tutorial\" target=\"_blank\" rel=\"noreferrer noopener\">try it out<\/a>.<\/p>\n\n\n\n<p>Much has been&nbsp;<a href=\"https:\/\/indepth.dev\/angular\" target=\"_blank\" rel=\"noreferrer noopener\">written<\/a>&nbsp;about Angular and other JavaScript frameworks, however, I have had a great developer experience building applications using Angular. I have given a&nbsp;<a href=\"https:\/\/www.youtube.com\/watch?v=ksVQ68wHR9c\" target=\"_blank\" rel=\"noreferrer noopener\">talk<\/a>&nbsp;about why Angular has been my framework of choice; you can watch it&nbsp;<a href=\"https:\/\/www.youtube.com\/watch?v=ksVQ68wHR9c\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>.<\/p>\n\n\n\n<p>The following is an excerpt from the&nbsp;<a href=\"https:\/\/angular.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">Angular.io<\/a>&nbsp;docs &#8211; \u201c<em>Learn one way to build applications with Angular and reuse your code and abilities to build apps for any deployment target. For web, mobile web, native mobile and native desktop<\/em>\u201d. This pretty much sums up the intent of this article.<\/p>\n\n\n\n<h3 id=\"why-electron\">Why Electron?<\/h3>\n\n\n\n<p><a href=\"https:\/\/www.electronjs.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Electron<\/a>&nbsp;is a framework that enables developers to create desktop applications with JavaScript, HTML, and CSS. These applications can then be packaged to run directly on macOS, Windows, or Linux, or distributed via the Mac App Store or the Microsoft Store.<\/p>\n\n\n\n<p>Typically, you create a desktop application for an operating system (OS) using each operating system&#8217;s specific native application frameworks. Electron makes it possible to write your application once using technologies that you already know.<\/p>\n\n\n\n<h3 id=\"why-capacitor\">Why Capacitor?<\/h3>\n\n\n\n<p><a href=\"https:\/\/capacitorjs.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Capacitor<\/a>&nbsp;is an open source native runtime for building web native apps. It allows you to create cross-platform iOS, Android, and Progressive Web Apps (PWAs) with JavaScript, HTML, and CSS. Also, it provides access to the full Native SDKs on each platform, so you can deploy to the App Stores while still able to target the web.<\/p>\n\n\n\n<p>You may have heard of&nbsp;<a href=\"https:\/\/cordova.apache.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Cordova<\/a>&nbsp;and wondering why we are not using it. The answer is simple, I wanted to try out a technology that I had never used before. Also, Capacitor supports Cordova plugins, so you are not limited in that sense. You can read more about the official differences according to Capacitor.js creators&nbsp;<a href=\"https:\/\/capacitorjs.com\/docs\/cordova\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>.<\/p>\n\n\n\n<h3 id=\"introduction-to-the-sample-application-and-tools-\">Introduction to the sample application (and tools)<\/h3>\n\n\n\n<p>The sample application is a multi-project architecture Angular-CLI application. We will go through each step of weaving together all the target platforms starting with Electron, then Android and finally iOS. This might be useful if you have an existing Angular application as you will see where and how you can add support for other platforms in your application.<\/p>\n\n\n\n<p>If you are starting a project from scratch or do not care about the details of how the different platforms are put together and just want to see the end result (or fork the project),&nbsp;<a href=\"https:\/\/github.com\/Sliqric7053\/cross-platform-monorepo\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>&nbsp;is the git repo.<\/p>\n\n\n\n<p>In any case, I encourage every reader to go through the details as it might come in handy down the line when your app is all grown up and you have to debug it.<\/p>\n\n\n\n<h3 id=\"why-should-i-care\">Why should I care?<\/h3>\n\n\n\n<p><em>Betty Eats Carrots And Uncle Sells Eggs<\/em>&nbsp;&#8211; a mnemonic a first grader might be taught to memorize how to spell the word \u201cbecause\u201d. Prompts or memory aids can be handy especially when learning something new. The more cues you have for remembering things the better and faster you will acquire new knowledge.<\/p>\n\n\n\n<p>In this article, I will show how we can use our Angular knowledge to build a game application using the Angular framework. As a default, you will be able to play the game on the web. We will add Electron to have the game installable on a computer. Lastly, we will enable our game to be deployable on a mobile device for maximum user reach. All this from a single (and simple) repository.<\/p>\n\n\n\n<p>As an aside, if you are interested in game development with Angular, you can check out this&nbsp;<a href=\"https:\/\/medium.com\/angular-in-depth\/game-development-tetris-in-angular-64ef96ce56f7\" target=\"_blank\" rel=\"noreferrer noopener\">article<\/a>, which our game application is based on. I will not dwell in the details of how the game is built as the article goes in-depth and shows you how to build one from scratch. Our focus will be on how to stitch together disparate technologies to help us, not only to understand all the moving parts, but also enable us to target as many platforms as possible. With that said, let\u2019s get started!<\/p>\n\n\n\n<h3 id=\"setting-up-the-project\">Setting up the project<\/h3>\n\n\n\n<p>First, let us start by globally installing&nbsp;<a href=\"https:\/\/cli.angular.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">Angular-CLI<\/a>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>npm install -g @angular\/cli<\/code><\/pre>\n\n\n\n<p>\u200cSince we plan to have multiple applications in the workspace, we create an empty workspace using&nbsp;<a href=\"https:\/\/angular.io\/cli\/new\" target=\"_blank\" rel=\"noreferrer noopener\">ng new<\/a>&nbsp;and set the&nbsp;<em>&#8211;createApplication<\/em>&nbsp;option to&nbsp;<em>false<\/em>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>ng new cross-platform-monorepo --createApplication=false<\/code><\/pre>\n\n\n\n<p>Then add the first Angular application to the workspace:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>ng generate application tetris<\/code><\/pre>\n\n\n\n<p>The above allows a workspace name to be different from the initial app name, and ensures that all applications (and libraries) reside in the&nbsp;<em>\/projects<\/em>&nbsp;subfolder, matching the workspace structure of the&nbsp;<a href=\"https:\/\/angular.io\/guide\/workspace-config\" target=\"_blank\" rel=\"noreferrer noopener\">configuration file<\/a>&nbsp;in&nbsp;<em>angular.json<\/em>.<\/p>\n\n\n\n<p>Add the second Angular application (placeholder) to the workspace:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>ng generate application tetris2<\/code><\/pre>\n\n\n\n<p>We want to reuse code in our applications, we can take advantage of the Angular multi-project architecture and create a library project which will hold the game engine logic.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>ng generate library game-engine-lib<\/code><\/pre>\n\n\n\n<p>By separating the shell parts (apps) from the logic parts (libs), we ensure that our code is manageable, reusable and our projects can be easily extended and shared with other dev teams.\u200c\u200c<\/p>\n\n\n\n<p>Now that we have the core structure of our game application, the next thing is to add the code for our game.\u200c\u200c<\/p>\n\n\n\n<p>As mentioned above, the game is based on this&nbsp;<a href=\"https:\/\/medium.com\/angular-in-depth\/game-development-tetris-in-angular-64ef96ce56f7\" target=\"_blank\" rel=\"noreferrer noopener\">article<\/a>, which also contains a link to the GitHub repository which you can find&nbsp;<a href=\"https:\/\/github.com\/melcor76\/ng-tetris\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>. I have modified the code a bit to demonstrate the use of native APIs (e.g.&nbsp;<a href=\"https:\/\/capacitorjs.com\/docs\/apis\/filesystem\" target=\"_blank\" rel=\"noreferrer noopener\">file system access<\/a>) both on desktop and mobile apps. More on this later.\u200c\u200c<\/p>\n\n\n\n<h3 id=\"using-libraries-in-angular-apps-\">Using libraries in Angular apps\u200c\u200c<\/h3>\n\n\n\n<p>Angular framework allows us to easily build npm libraries. We do not have to&nbsp;<a href=\"https:\/\/indepth.dev\/posts\/1238\/complete-beginner-guide-to-publishing-an-angular-library-to-npm\" target=\"_blank\" rel=\"noreferrer noopener\">publish<\/a>&nbsp;a library to the&nbsp;<a href=\"https:\/\/www.npmjs.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">npm<\/a>&nbsp;package manager to use it in our own apps, however, we cannot use a library before it is built so let us do that now.\u200c\u200c<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\"><p>Note: I use the terms \u201clib\u201d and \u201clibrary\u201d interchangeably \u2013 both refer to an Angular library as described&nbsp;<a href=\"https:\/\/angular.io\/guide\/libraries\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a><\/p><\/blockquote>\n\n\n\n<p>In the terminal of your choice, run this command:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>ng build game-engine-lib<\/code><\/pre>\n\n\n\n<p>If the operation was successful, you should see an output like the one below:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img src=\"https:\/\/lh6.googleusercontent.com\/hkWr-BjPEYLVFt8IYibTSDPDd42GLrKAgL0s-3X50AB-Tfk-uvWjCJy_xi-7bxPl2mv7x1lqTv09297Bv-Ug5P5zPduulzVCjgfpU_uOsKd-dR1UMTQigG33ZVmNtIM93S6reg0\" alt=\"Graphical user interface, text  Description automatically generated\"\/><\/figure>\n\n\n\n<p>\u200cTo make our lives a little easier, let\u2019s add some scripts to the&nbsp;<em>package.json<\/em>&nbsp;file:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>\"scripts\": {\n    \"ng\": \"ng\",\n    \"start:tetris\": \"ng serve tetris -o\",\n    \"build:tetris\": \"ng build tetris\",\n    \"test:tetris\": \"ng test tetris\",\n    \"lint:tetris\": \"ng lint tetris\",\n    \"e2e:tetris\": \"ng e2e tetris\",\n    \"start:tetris2\": \"ng serve tetris2 -o\",\n    \"build:game-engine-lib\": \"ng build game-engine-lib --watch\",\n    \"test:game-engine-lib\": \"ng test game-engine-lib\",\n    \"lint:game-engine-lib\": \"ng lint game-engine-lib\",\n    \"e2e:game-engine-lib\": \"ng e2e game-engine-lib\"\n  }<\/code><\/pre>\n\n\n\n<p>Lastly, we will use&nbsp;<a href=\"https:\/\/angular.io\/guide\/creating-libraries#use-typescript-path-mapping-for-peer-dependencies\" target=\"_blank\" rel=\"noreferrer noopener\">TypeScript path mapping<\/a>&nbsp;for peer dependencies to reference our lib within our apps.\u200c\u200c<\/p>\n\n\n\n<p>In the root&nbsp;<em>tsconfig.json<\/em>&nbsp;inside&nbsp;<em>compilerOptions<\/em>, modify the code as follows:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>\"paths\": {\n      \"@game-engine-lib\": [\"dist\/game-engine-lib\"]\n}<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote\"><p>Note: I prefer to add \u201c@\u201d in front of the library name to easily distinguish it from local file imports.<\/p><\/blockquote>\n\n\n\n<p>In the&nbsp;<em>game-engine-lib.service.ts<\/em>&nbsp;file, add the following getter:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>get testing(): string {\n    return \"GameEngineLibService works!\";\n  }<\/code><\/pre>\n\n\n\n<p>Each time we make changes to a lib, we need to rebuild it \u2013 alternatively, we can use the&nbsp;<a href=\"https:\/\/angular.io\/guide\/creating-libraries#building-and-rebuilding-your-library\" target=\"_blank\" rel=\"noreferrer noopener\"><em>&#8211;watch<\/em><\/a>&nbsp;flag to automatically do so on file save.\u200c\u200c<\/p>\n\n\n\n<p>Let\u2019s rebuild the lib using one of the scripts we have just added:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>npm run build:game-engine-lib<\/code><\/pre>\n\n\n\n<p>Now let us test whether or not we are able to consume the exports specified in the&nbsp;<em>public-api.ts&nbsp;<\/em>file.\u200c\u200c<\/p>\n\n\n\n<p>In the&nbsp;<em>app.module.ts<\/em>&nbsp;of the tetris app, import the lib to make it available tetris app-wide:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>import {GameEngineLibModule} from \"@game-engine-lib\";<\/code><\/pre>\n\n\n\n<p>\u200c\u200cThen add the lib module to the imports array under&nbsp;<em>@NgModule<\/em>&nbsp;of the same file:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>imports: [GameEngineLibModule]<\/code><\/pre>\n\n\n\n<p>Add the following code in&nbsp;<em>app.component.ts<\/em>&nbsp;of the tetris app:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>constructor(private engineService: GameEngineLibService) {\n    console.info(engineService.testing);\n  }<\/code><\/pre>\n\n\n\n<p>Finally, in your terminal, serve the tetris app using one of the scripts we added earlier:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>npm run start:tetris<\/code><\/pre>\n\n\n\n<p>After the app is compiled and the browser window opened, you should see the following:\u200c\u200c<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img src=\"https:\/\/lh6.googleusercontent.com\/EX5RrRI7SnDZ9I9oF1zrado30NiTMLyAyz2q8LN1uHUxKWyMkbJn7Zc0Sh-6Cm3R76ToLmt4ScDReBwLKNdCIybKTjTo337ISmwb3FOsOviqfu3yO-hNqOniqAdd9oBzyfWkBAQ\" alt=\"Graphical user interface, text  Description automatically generated with medium confidence\"\/><\/figure>\n\n\n\n<p>\u200c\u200c\u200cPat yourself on the back, stretch your legs and when you are ready, let us continue to the fun(ky) parts.\u200c\u200c\u200c<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\"><p><em>Note: The following section involves moving files from the&nbsp;<a href=\"https:\/\/github.com\/melcor76\/ng-tetris\" target=\"_blank\" rel=\"noreferrer noopener\">tetris repo<\/a>&nbsp;to our monorepo. If you feel lost, compare your file structure with that of the&nbsp;<a href=\"https:\/\/github.com\/Sliqric7053\/cross-platform-monorepo\" target=\"_blank\" rel=\"noreferrer noopener\">finished project<\/a>.<\/em><\/p><\/blockquote>\n\n\n\n<h3 id=\"adding-game-code-\">Adding game code\u200c\u200c<\/h3>\n\n\n\n<p>Since we are working in a multi-project repository, we need to&nbsp;<a href=\"https:\/\/angular.io\/guide\/creating-libraries#refactoring-parts-of-an-app-into-a-library\" target=\"_blank\" rel=\"noreferrer noopener\">re-organize<\/a>&nbsp;the game code a bit. The \u201cutility\u201d parts of the code will go into the library and the \u201cshell\u201d will be the application project (tetris folder). We will leave the tetris2 app as is for the time being. \u200c\u200c<\/p>\n\n\n\n<p>To keep our code well-organized, let\u2019s create a components sub-folder inside the lib folder (i.e.&nbsp;<em>projects\/game-engine-lib\/src\/lib<\/em>):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>ng g c components\/board \/\/ add --dry-run and ensure files are created in the correct folder<\/code><\/pre>\n\n\n\n<p>Next, in the same lib directory, create a&nbsp;<em>piece<\/em>&nbsp;folder and rename the&nbsp;<em>GameEngineLibComponent&nbsp;<\/em>classto<em>&nbsp;Piece:<\/em>\u200c\u200c<\/p>\n\n\n\n<p>Copy the contents of&nbsp;<em>board<\/em>&nbsp;(.ts and .html) and&nbsp;<em>piece<\/em>&nbsp;(.ts) files from the tetris repo to the&nbsp;<em>board<\/em>&nbsp;and&nbsp;<em>piece<\/em>&nbsp;components of our monorepo respectively. Grab the&nbsp;<em>constants.ts<\/em>&nbsp;file and add it to&nbsp;<em>projects\/game-engine-lib\/src\/lib&nbsp;<\/em>directory.<\/p>\n\n\n\n<p>Copy the&nbsp;<em>game.service.ts<\/em>&nbsp;contents into&nbsp;<em>game-engine-lib.service.ts&nbsp;<\/em>(rename&nbsp;<em>GameService<\/em>&nbsp;to&nbsp;<em>GameEngineLibService<\/em>)<em>.&nbsp;<\/em>Adjust all imports accordingly and install the&nbsp;<em>ng-zzfx<\/em>&nbsp;npm package. \u200c\u200c<\/p>\n\n\n\n<p>We have a few more adjustments before we can test our game.\u200c\u200c<\/p>\n\n\n\n<p>In the&nbsp;<em>GameEngineLibModule&nbsp;<\/em>of our lib<em>,&nbsp;<\/em>add the following code:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>import {CommonModule} from \"@angular\/common\";\n\n@NgModule({\n  declarations: [BoardComponent],\n  imports: [CommonModule], \/\/ Contains the basic Angular directives (i.e. NgIf, NgForOf etc) \n  exports: [BoardComponent],\n})<\/code><\/pre>\n\n\n\n<p>Lastly, expose the&nbsp;<em>Board<\/em>&nbsp;component in the public API surface of our&nbsp;<em>game-engine-lib<\/em>&nbsp;so it can be consumed by the apps:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>export * from \".\/lib\/components\/board\/board.component\"<\/code><\/pre>\n\n\n\n<p>Our code structure should now look like this:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img src=\"https:\/\/lh6.googleusercontent.com\/gR3RUbRWqTn-UW9x-_BRwelf3pqKIFHJxNwtE7cXELuobQEzQp9MtjzN7GatVBvQi2xyAMHXK-TXArNKTZJwYccyz5316t-Wdw8eca3Bx_rc2EJNBNfjXsjbcTOtwAe9iMQivdg\" alt=\"Text  Description automatically generated\"\/><\/figure>\n\n\n\n<p>Now we are ready to use the game engine logic in the tetris app (or any other app you might decide to add at a later stage).\u200c\u200c<\/p>\n\n\n\n<p>In the tetris app (i.e.&nbsp;<em>\/projects\/tetris\/src\/app<\/em>), replace the placeholder code with the following:<\/p>\n\n\n\n<p><em>app.component.html<\/em>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>&lt;game-board&gt;&lt;\/game-board&gt;<\/code><\/pre>\n\n\n\n<p>Do not forget to copy and paste the&nbsp;<em>styles.scss<\/em>&nbsp;file content to its equivalent as well.\u200c\u200c<\/p>\n\n\n\n<p>Now let us use one of our scripts to build the lib one more time (if not already running with&nbsp;<em>&#8211;watch<\/em>&nbsp;flag) and test if everything works:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>npm run build:game-engine-lib<\/code><\/pre>\n\n\n\n<p>Then fire up the tetris game (<em><strong>npm run start:tetris<\/strong><\/em>). If all worked out fine, you should see the below when your browser opens:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img src=\"https:\/\/lh5.googleusercontent.com\/xCBuXSmOJeeQFSXugjl_gF5bVnXKfXwHluBho2sl4YyVZkrph_Hlh01_6Ynyd44KIL--sX1Pl_UH7Idrcs7DrrOQjvcodRJ7bvk7IZD8WbEu1SAPYB5sXlfGP5BqJ5R7IkGWX90\" alt=\"Chart, waterfall chart  Description automatically generated\"\/><\/figure>\n\n\n\n<p>I must admit, I did get carried away and spent a significant amount of time not writing this article but playing Tetris \u263a\u200c\u200c<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img src=\"https:\/\/images.indepth.dev\/images\/2021\/01\/image-6.png\" alt=\"\"\/><\/figure>\n\n\n\n<p>Unlike me, you are a serious software developer and do not easily get distracted. Very well then, let us move on to our first platform integration \u2013 Electron.js.\u200c\u200c<\/p>\n\n\n\n<h3 id=\"integrating-electron-into-the-workspace-\">Integrating Electron into the workspace\u200c\u200c<\/h3>\n\n\n\n<p>If you were able to complete all the parts up to this point, it means you meet the requirements for installing Electron.js. The reason for this is that from a development perspective, an Electron application is essentially a node.js application. Angular requires node.js\/npm to run. To have an in-depth idea of how to setup a standalone Electron app, have a look&nbsp;<a href=\"https:\/\/www.electronjs.org\/docs\/tutorial\/quick-start#main-and-renderer-processes\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>.\u200c\u200c<\/p>\n\n\n\n<p>For our tetris game app, we need to first install Electron.js<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>npm install --save-dev electron@11.0.5<\/code><\/pre>\n\n\n\n<p>An Electron app uses the&nbsp;<em>package.json<\/em>&nbsp;file as its main entry point (as any other node.js app). So let us modify the&nbsp;<em>package.json<\/em>&nbsp;file as follows:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>{\n...\n\"name\": \"cross-platform-monorepo\",\n \"version\": \"0.0.0\",\n \"description\": \"Cross-platform monorepo Angular app\",\n \"author\": {       \/\/ author and description fields are required for packaging (electron-builder)\n    \"name\": \"your name\",\n    \"email\": \"your@email.address\"\n  },\n \"main\": \"main.js\", \/\/ Electron entry-point\n...\n}<\/code><\/pre>\n\n\n\n<p>If you have written a lot of Angular code or worked in large codebases like I have, you would know how&nbsp;<a href=\"https:\/\/www.typescriptlang.org\/docs\/handbook\/migrating-from-javascript.html\" target=\"_blank\" rel=\"noreferrer noopener\">indispensable<\/a>&nbsp;TypeScript (TS) is, so let us create a&nbsp;<em>main.ts&nbsp;<\/em>file instead of writing&nbsp;<a href=\"https:\/\/www.typescriptlang.org\/docs\/handbook\/migrating-from-javascript.html#early-benefits\" target=\"_blank\" rel=\"noreferrer noopener\">error-prone<\/a>&nbsp;pure JavaScript (JS) code. When we build the tetris app, the&nbsp;<em>main.ts<\/em>&nbsp;code will be transpiled to JS code by the TS compiler (<em>tsc<\/em>). The output of this process will be the&nbsp;<em>main.js<\/em>&nbsp;file. This is what gets served to Electron.\u200c\u200c<\/p>\n\n\n\n<p>Create the&nbsp;<em>main.ts<\/em>&nbsp;file and fill it with the following code:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>import { app, BrowserWindow, screen } from \"electron\";\nimport * as path from \"path\";\nimport * as url from \"url\";\n\nlet win: BrowserWindow = null;\nconst args = process.argv.slice(1),\n  serve = args.some((val) =&gt; val === \"--serve\");\n\nfunction createWindow(): BrowserWindow {\n  const electronScreen = screen;\n  const size = electronScreen.getPrimaryDisplay().workAreaSize;\n\n  \/\/ Create the browser window:\n  win = new BrowserWindow({\n    x: 0,\n    y: 0,\n    width: size.width,\n    height: size.height,\n    webPreferences: {\n      nodeIntegration: true,\n      allowRunningInsecureContent: serve ? true : false,\n      contextIsolation: false, \/\/ false if you want to run e2e tests with Spectron\n      enableRemoteModule: true, \/\/ true if you want to run e2e tests with Spectron or use remote module in renderer context (i.e. Angular apps)\n    },\n  });\n\n  if (serve) {\n    win.webContents.openDevTools();\n\n    require(\"electron-reload\")(__dirname, {\n      electron: path.join(__dirname, \"node_modules\", \".bin\", \"electron\"),\n    });\n    win.loadURL(\"http:\/\/localhost:4200\");\n  } else {\n    win.loadURL(\n      url.format({\n        pathname: path.join(__dirname, \"dist\/index.html\"),\n        protocol: \"file:\",\n        slashes: true,\n      })\n    );\n  }\n\n  \/\/ Emitted when the window is closed.\n  win.on(\"closed\", () =&gt; {\n    \/\/ Deference from the window object, usually you would store window\n    \/\/ in an array if your app supports multi windows, this is the time\n    \/\/ when you should delete the corresponding element.\n    win = null;\n  });\n\n  return win;\n}\n\ntry {\n  \/\/ This method will be called when Electron has finished\n  \/\/ initialization and is ready to create browser windows.\n  \/\/ Some APIs can only be used after this event occurs.\n  \/\/ Added 400ms to fix the black background issue while using a transparent window.\n  app.on(\"ready\", () =&gt; setTimeout(createWindow, 400));\n\n  \/\/ Quit when all windows are closed.\n  app.on(\"window-all-closed\", () =&gt; {\n    \/\/ On OS X it is common for applications and their menu bar\n    \/\/ to stay active until the user quits explicitly with Cmd + Q\n    if (process.platform !== \"darwin\") {\n      app.quit();\n    }\n  });\n\n  app.on(\"activate\", () =&gt; {\n    \/\/ On OS X it's common to re-create a window in the app when the\n    \/\/ dock icon is clicked and there are no other windows open.\n    if (win === null) {\n      createWindow();\n    }\n  });\n} catch (e) {\n  \/\/ handle error\n}<\/code><\/pre>\n\n\n\n<p>Let\u2019s now add a couple more npm scripts to help us with the compilation and serving of the Electron app:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>{\n...\n\"start\": \"npm-run-all -p electron:serve start:tetris\",\n\"electron:serve-tsc\": \"tsc -p tsconfig.serve.json\",\n\"electron:serve\": \"wait-on tcp:4200 &amp;&amp; npm run electron:serve-tsc &amp;&amp; npx electron . --serve\"\n...\n}<\/code><\/pre>\n\n\n\n<p>As you can see from the above scripts, there are a few files and packages we need to create for this to work properly. Let\u2019s go ahead and do that now.\u200c\u200c<\/p>\n\n\n\n<p>Firstly, add the following npm packages:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>npm install wait-on \/\/ wait for resources (e.g. http) to become available before proceeding\nnpm install electron-reload \/\/ load contents of all active BrowserWindows when files change\nnpm install npm-run-all \/\/ run multiple npm-scripts in parallel<\/code><\/pre>\n\n\n\n<p>Then create a&nbsp;<em>tsconfig.serve.json<\/em>&nbsp;file in the root directory and top it up with this code:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>{\n  \"compilerOptions\": {\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es5\",\n    \"types\": [\n      \"node\"\n    ],\n    \"lib\": [\n      \"es2017\",\n      \"es2016\",\n      \"es2015\",\n      \"dom\"\n    ]\n  },\n  \"files\": [\n    \"main.ts\"\n  ],\n  \"exclude\": [\n    \"node_modules\",\n    \"**\/*.spec.ts\"\n  ]\n}<\/code><\/pre>\n\n\n\n<p>Ok, that\u2019s it \u2013 let us take it for another spin. If all is good, we should be able to play the tetris game, running on desktop.\u200c\u200c<\/p>\n\n\n\n<p>Use the script we added earlier:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>npm start<\/code><\/pre>\n\n\n\n<p>\u200c &nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img src=\"https:\/\/lh5.googleusercontent.com\/xIlSn0a1BTL27WdTxI-AQ907Oy6F9vNycTwhNoBveZ5lLtBtAI-Y1H9RmdjM6-VxTu8ObFujGVKF53XwQYmmQdlvpBXKMh8HPsYTjRBupES-uPeWIQP7nqpGbrCARYnyckGaDAA\" alt=\"Chart, waterfall chart  Description automatically generated\"\/><\/figure>\n\n\n\n<p>Congratulations! We have an Electron desktop app running with hot module reload!\u200c\u200c<\/p>\n\n\n\n<p>Before we jump into the next section, let us tidy up the code and create some helper services to give us a convenient way to communicate between Electron and Angular. Also, we want to package the game into an installable binary for the different operating systems. This is where&nbsp;<a href=\"https:\/\/www.electron.build\/\" target=\"_blank\" rel=\"noreferrer noopener\">electron-builder<\/a>&nbsp;comes into play.\u200c\u200c<\/p>\n\n\n\n<p>First, update the Electron&nbsp;<em>main.ts<\/em>&nbsp;file:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>win.loadURL(\n      url.format({\n        pathname: path.join(__dirname, \"dist\/tetris\/index.html\"), \/\/ add \u201c\/tetris\u201d in the path\n        protocol: \"file:\",\n        slashes: true,\n      })\n    );\n<\/code><\/pre>\n\n\n\n<p>Next, in the root directory, create a&nbsp;<em>electron-builder.json<\/em>&nbsp;file and add this content:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>{\n  \"productName\": \"name-of-your-app\",\n  \"directories\": {\n    \"output\": \"release\/\"\n  },\n  \"files\": [\n    \"**\/*\",\n    \"!**\/*.ts\",\n    \"!*.code-workspace\",\n    \"!LICENSE.md\",\n    \"!package.json\",\n    \"!package-lock.json\",\n    \"!src\/\",\n    \"!e2e\/\",\n    \"!hooks\/\",\n    \"!angular.json\",\n    \"!_config.yml\",\n    \"!karma.conf.js\",\n    \"!tsconfig.json\",\n    \"!tslint.json\"\n  ],\n  \"win\": {\n    \"icon\": \"dist\/tetris\/assets\/icons\",\n    \"target\": [\"portable\"]\n  },\n  \"mac\": {\n    \"icon\": \" dist\/tetris\/assets\/icons\",\n    \"target\": [\"dmg\"]\n  },\n  \"linux\": {\n    \"icon\": \" dist\/tetris\/assets\/icons\",\n    \"target\": [\"AppImage\"]\n  }\n}<\/code><\/pre>\n\n\n\n<p>Now let\u2019s install&nbsp;<em>electron-builder<\/em>&nbsp;using the terminal:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>npm i electron-builder -D<\/code><\/pre>\n\n\n\n<p>In the&nbsp;<em>package.json<\/em>&nbsp;file, add the respective scripts for packaging the game:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>{\n...\n\"postinstall\": \"electron-builder install-app-deps\", \n\"build\": \"npm run electron:serve-tsc &amp;&amp; ng build tetris --base-href .\/\",\n\"build:prod\": \"npm run build -- -c production\",\n\"electron:package\": \"npm run build:prod &amp;&amp; electron-builder build\"\n...\n}<\/code><\/pre>\n\n\n\n<p>That\u2019s it! Build and package the application using&nbsp;<em><strong>npm run electron:package&nbsp;<\/strong><\/em>command and depending on your operating system, you will get an installer (in the newly created&nbsp;<em>\/release&nbsp;<\/em>folder) for Linux, Windows or macOS with \u201cauto update\u201d support out of the box!\u200c\u200c<\/p>\n\n\n\n<p>This is what it looks like on macOS:\u200c\u200c<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img src=\"https:\/\/images.indepth.dev\/images\/2021\/01\/image-5.png\" alt=\"\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h3 id=\"angular-electron-communication-\">Angular-Electron Communication\u200c\u200c<\/h3>\n\n\n\n<p>We cannot directly access all of Electron\u2019s APIs from the Angular app. To easily communicate between Electron and Angular, we need to make use of Inter-Process Communication (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Inter-process_communication\" target=\"_blank\" rel=\"noreferrer noopener\">IPC<\/a>). It is a mechanism the operating system provides so that two different processes (i.e. from main process to browser process and vice versa) can communicate with each other. \u200c\u200c<\/p>\n\n\n\n<p>Let\u2019s create a service in the&nbsp;<em>projects\/tetris\/src\/app<\/em>&nbsp;directory to facilitate this inter-process communication:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>ng generate module core\nng generate service core\/services\/electron<\/code><\/pre>\n\n\n\n<p>Add the following code inside the newly created file (i.e.&nbsp;<em>electron.service.ts<\/em>):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>import { Injectable } from \"@angular\/core\";\nimport { ipcRenderer, webFrame, remote } from \"electron\";\nimport * as childProcess from \"child_process\";\nimport * as fs from \"fs\";\n\n@Injectable({\n  providedIn: \"root\",\n})\nexport class ElectronService {\n  ipcRenderer: typeof ipcRenderer;\n  webFrame: typeof webFrame;\n  remote: typeof remote;\n  childProcess: typeof childProcess;\n  fs: typeof fs;\n\n  get isElectron(): boolean {\n    return !!(window?.process?.type);\n  }\n\n  constructor() {\n    if (this.isElectron) {\n      this.ipcRenderer = window.require(\"electron\").ipcRenderer;\n      this.webFrame = window.require(\"electron\").webFrame;\n\n      \/\/ If you want to use remote object, set enableRemoteModule to true in main.ts\n      \/\/ this.remote = window.require('electron').remote;\n\n      this.childProcess = window.require(\"child_process\");\n      this.fs = window.require(\"fs\");\n    }\n  }\n}<\/code><\/pre>\n\n\n\n<p>Finally, pull in the above electron service in the&nbsp;<em>app.module.ts&nbsp;<\/em>file:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>imports: [BrowserModule, GameEngineLibModule, CoreModule]<\/code><\/pre>\n\n\n\n<p>And consume it in the&nbsp;<em>app.component.ts<\/em>&nbsp;file (or any other file in the projects):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>export class AppComponent {\n  title = \"tetris\";\n  constructor(private electronService: ElectronService) {\n\n    if (electronService.isElectron) {\n      console.log(\"Run in electron\");\n      console.log(\"Electron ipcRenderer\", this.electronService.ipcRenderer);\n      console.log(\"NodeJS childProcess\", this.electronService.childProcess);\n    } else {\n      console.log(\"Run in browser\");\n    }\n  }\n}<\/code><\/pre>\n\n\n\n<p>Whoa that was a mouthful. Look back at what we have done \u2013 with the above setup, you have the power to go wild and use your Angular skills to build apps like VS Code, Slack, Twitch,&nbsp;<a href=\"http:\/\/superpowers-html5.com\/index.en.html\" target=\"_blank\" rel=\"noreferrer noopener\">Superpowers<\/a>&nbsp;and&nbsp;<a href=\"https:\/\/www.electronjs.org\/apps\" target=\"_blank\" rel=\"noreferrer noopener\">all kinds of apps<\/a>&nbsp;you can imagine, and distribute them to the most popular desktop platforms.\u200c\u200c<\/p>\n\n\n\n<p>With that said, let us jump into the last platform integration \u2013 Mobile.\u200c\u200c<\/p>\n\n\n\n<h3 id=\"integrating-ios-and-android-into-the-workspace-\">Integrating iOS and Android into the workspace\u200c\u200c<\/h3>\n\n\n\n<p>For integrating mobile platform support into our workspace, we are going to use&nbsp;<a href=\"https:\/\/capacitorjs.com\/docs\/getting-started\" target=\"_blank\" rel=\"noreferrer noopener\">Capacitor.js<\/a>. It is an open source native runtime for building web native apps and creating cross-platform iOS, Android, and Progressive Web Apps using Angular or any other modern web framework or library.\u200c\u200c<\/p>\n\n\n\n<p>Like many other node.js\/npm based technologies, we first have to install the package. There are other&nbsp;<a href=\"https:\/\/capacitorjs.com\/docs\/getting-started\/dependencies\" target=\"_blank\" rel=\"noreferrer noopener\">pre-requisites<\/a>&nbsp;that you should comply with before you can proceed.\u200c\u200c<\/p>\n\n\n\n<p>Once you have installed the above dependencies, run the following script in the root directory:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>npm install @capacitor\/core@2.4.5 @capacitor\/cli@2.4.5<\/code><\/pre>\n\n\n\n<p>Then, initialize Capacitor with our app data:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>npx cap init \/\/ npx is a utility that executes local binaries or scripts to avoid global installs.<\/code><\/pre>\n\n\n\n<p>Follow the terminal prompts to completion. Once done, you should see the following:\u200c\u200c<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img src=\"https:\/\/lh3.googleusercontent.com\/EVMaBrcvlPycRU6S47a35CIJyuhkxZUDQcyeNBkV9RZD1qgfvyR54XsgxRyrX5-eyxyvKErt9WxR4YHVQBShCLypeiX2VKrwi3SQOY9xts9USl2bs4ohjtf-nlbkORCj3kukBsE\" alt=\"Text  Description automatically generated\"\/><\/figure>\n\n\n\n<p>\u200c\u200cAnd a new file (i.e.&nbsp;<em>capacitor.config.json<\/em>) will be created with the following content:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>{\n  \"appId\": \"com.tetris.game\",\n  \"appName\": \"cross-platform-game\",\n  \"bundledWebRuntime\": false,\n  \"npmClient\": \"npm\",\n  \"webDir\": \"www\",\n  \"plugins\": {\n    \"SplashScreen\": {\n      \"launchShowDuration\": 0\n    }\n  },\n  \"cordova\": {}\n}<\/code><\/pre>\n\n\n\n<p>Lastly, let us add the platforms of our choice. We will first target Android:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>npx cap add android<\/code><\/pre>\n\n\n\n<p>If you run the above command, you will likely get the below error:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>Error: Capacitor could not find the web assets directory \"\/path\/to\/your\/root\/repo\/www\"<\/code><\/pre>\n\n\n\n<p>To fix this, simply adjust&nbsp;<em>capacitor.config.json&nbsp;<\/em>as such:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>{\n...\n\"webDir\": \"www\", \/\/ replace www with \u201cdist\/tetris\u201d\n...\n}<\/code><\/pre>\n\n\n\n<p>Capacitor works on a three-step build process: First, your web code is built (if necessary). Next, the built web code is copied to each platform. Finally, the app is compiled using the platform-specific tooling. There is a recommended&nbsp;<a href=\"https:\/\/capacitorjs.com\/docs\/basics\/workflow\" target=\"_blank\" rel=\"noreferrer noopener\">developer workflow<\/a>&nbsp;that you should follow.\u200c\u200c<\/p>\n\n\n\n<p>Run the script again:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>npx cap add android<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img src=\"https:\/\/lh6.googleusercontent.com\/-5d8ckSykUUWDkaNSnM4uRwzRdYY5lJP5qqEjPbezzVxylAcxBioJzhtKM-KMdNvJN7qO8s2KH2-7fkcn4-6KNLAH4zLVj9-abP9tU68EYEDEXocqY-x6sBB6VatGnT2aNBIEfI\" alt=\"Graphical user interface, text, application  Description automatically generated\"\/><\/figure>\n\n\n\n<p>\u200c\u200cAfter Android has been successfully added, you should see a bunch of android specific files (inside the newly created&nbsp;<em>android<\/em>&nbsp;folder). These files should be part of version control. So add and commit all of them to git.\u200c\u200c<\/p>\n\n\n\n<p>Capacitor relies on each platform\u2019s IDE of choice to run and test your app. \u200c\u200c<\/p>\n\n\n\n<p>Therefore, we need to launch Android Studio to test our game. To do so, simply run:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>npx cap open android<\/code><\/pre>\n\n\n\n<p>Once Android Studio opens, you can build, emulate or run your app through the standard Android Studio&nbsp;<a href=\"https:\/\/developer.android.com\/studio\/workflow\" target=\"_blank\" rel=\"noreferrer noopener\">workflow<\/a>.\u200c<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img src=\"https:\/\/images.indepth.dev\/images\/2021\/01\/image-7.png\" alt=\"\"\/><\/figure>\n\n\n\n<p>\u200c\u200cThe details of Android Studio workflow and Android Studio are outside the scope of this article, however, there are many&nbsp;<a href=\"https:\/\/developer.android.com\/studio\/workflow\" target=\"_blank\" rel=\"noreferrer noopener\">resources<\/a>&nbsp;you can check out to assist you in that regard.\u200c\u200c<\/p>\n\n\n\n<p>As a final point, let us do the same as above to add iOS platform support:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>npx cap add ios<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img src=\"https:\/\/lh4.googleusercontent.com\/QNw1hsXwHsOMQ4K5h4pbBgfmkvRvmApAwyMad9sLrMtE7dEI6RWxYCJ7fM2gpEUwgkaznJmsYtTJd5OSLTGPTDoJ16i5BOLH4oFcf1gKHeDmfRFodiUqE0YGR-kYbTQC3xkNmwQ\" alt=\"Text  Description automatically generated\"\/><\/figure>\n\n\n\n<p>\u200c\u200cJust like we saw when adding Android, after iOS has been successfully added, you should see a bunch of iOS specific files (inside the newly created&nbsp;<em>ios<\/em>&nbsp;folder). These files should be added to source control.\u200c\u200c<\/p>\n\n\n\n<p>To start Xcode and build the app for the emulator, run:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>npx cap open ios<\/code><\/pre>\n\n\n\n<p>After you build and run the app in Xcode, you should see the below:\u200c\u200c<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img src=\"https:\/\/lh3.googleusercontent.com\/bh90beZCB6u3CBJlMXWwvoh4ueNMeFlOn4NKgfywifZZh_FwmRmAogviHS02RU7W84sZlxJb4Vy1gagySreUOkPtxV5poxGFtHE3hiuKj8yvk5fJTgubgz_hgU7QTDezECTz7HQ\" alt=\"A picture containing graphical user interface  Description automatically generated\"\/><\/figure>\n\n\n\n<p>\u200c\u200cCapacitor will package your app files and hand them over to Xcode. The rest of the development is up to you. \u200c\u200c<\/p>\n\n\n\n<p>The beauty of Capacitor is that it features a native iOS bridge that enables developers to communicate between JavaScript and Native Swift or Objective-C code. This means you have the freedom to author your code by using the various APIs available, Capacitor or Cordova plugins, or custom native code to build out the rest of your app.\u200c\u200c<\/p>\n\n\n\n<p>As mentioned in the Capacitor&nbsp;<a href=\"https:\/\/capacitorjs.com\/docs\/basics\/workflow\" target=\"_blank\" rel=\"noreferrer noopener\">developer workflow<\/a>, each time we build a project, we need to copy the app assets into their respective mobile platform folders. Let us add some scripts to automate that:\u200c\u200c<\/p>\n\n\n\n<p><em>package.json<\/em><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>{\n...\n\"copy-android\": \"npx cap copy android\",\n\"copy-ios\": \"npx cap copy ios\",\n\"open:android-studio\": \"npx cap open android\",\n\"open:xcode\": \"npx cap open ios\",\n\"add-android\": \"npx cap add android\",\n\"add-ios\": \"npx cap add ios\"\n...\n}<\/code><\/pre>\n\n\n\n<h3 id=\"cleaning-up-\">Cleaning up\u200c\u200c<\/h3>\n\n\n\n<p>Keeping with the theme of simple and clean multi-project architecture, and since we now have another platform to maintain, it makes sense to create a new Angular library to hold all the services, components, directives, etc. that are common across the platforms:\u200c\u200c<\/p>\n\n\n\n<p>Go ahead and create the lib:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>ng g library shared-lib \/\/ use \u2013dry-run to ensure your files are in the correct folder<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img src=\"https:\/\/lh6.googleusercontent.com\/0DYOVpYi7dRaeJLaYJQiKb9TAHSkgZk1bU26n7OazhEYJNzKa08xQShs6EY4J752txX2umrqqYY8XLjp08mM-0uiFhbc-aT_w7Zpq1t3huFDA_WnGEELy9KIbBd8dYGMUWLHnVg\" alt=\"Text  Description automatically generated\"\/><\/figure>\n\n\n\n<p>\u200c\u200cAs with the previous lib, we have to build it before it can be used \u2013 add a script to do so and then run it:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>{\n...\n\"build:shared-lib\": \"ng build shared-lib --watch\"\n...\n}<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img src=\"https:\/\/lh6.googleusercontent.com\/iVje2gBnPa1mPL9R_UJsWpZV4EtEOylAeJFnjZZ_-0qVtCm3nyej1iMHCj73T_9Rr1zun8DdhePoDJKN3YZilIssJJTKDi0Sk3o0UFukHghRufcyKP01U_2N12QajoqT9F21vmc\" alt=\"Graphical user interface, text, application  Description automatically generated\"\/><\/figure>\n\n\n\n<p>\u200c\u200cIn&nbsp;<em>tsconfig.json<\/em>&nbsp;under&nbsp;<em>compilerOptions<\/em>, add:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>\"paths\": {\n      \"@game-engine-lib\": [ \n        \"dist\/game-engine-lib\"\n      ],\n      \"@shared-lib\": [               \/\/ the newly created lib\n        \"dist\/shared-lib\"\n      ]\n    }<\/code><\/pre>\n\n\n\n<p>Move all services from&nbsp;<em>projects\/tetris\/src\/app\/core\/services&nbsp;<\/em>to<em>&nbsp;projects\/shared-lib\/src\/lib\/services&nbsp;<\/em>and make sure to export the classes via the lib\u2019s public API (i.e.<em>&nbsp;public-api.ts<\/em>)<em>.&nbsp;<\/em>\u200c\u200c<\/p>\n\n\n\n<p>Lastly, let\u2019s add a new service which we will need in the next section:\u200c\u200c<\/p>\n\n\n\n<p>Run the following command in&nbsp;<em>projects\/shared-lib\/src\/lib\/services&nbsp;<\/em>folder:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>ng g s \/capacitor\/capacitorstorage<\/code><\/pre>\n\n\n\n<p>And add this code:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>import { Injectable } from \"@angular\/core\";\nimport { Plugins } from \"@capacitor\/core\";\n\nconst { Storage } = Plugins;\n\n@Injectable({\n  providedIn: \"root\",\n})\nexport class CapacitorStorageService {\n  constructor() {}\n\n  async set(key: string, value: any): Promise&lt;void&gt; {\n    await Storage.set({\n      key: key,\n      value: JSON.stringify(value),\n    });\n  }\n\n  async get(key: string): Promise&lt;any&gt; {\n    const item = await Storage.get({ key: key });\n    return JSON.parse(item.value);\n  }\n\n  async remove(key: string): Promise&lt;void&gt; {\n    await Storage.remove({\n      key: key,\n    });\n  }\n}<\/code><\/pre>\n\n\n\n<p>With that done, we are now ready to use the&nbsp;<em>shared-lib<\/em>&nbsp;anywhere in the projects.\u200c\u200c<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\"><p>Warning: libs can import other libs, however, avoid importing services, modules, directives, etc. defined in the projects\u2019 apps into libs. This often leads to circular dependencies which are hard to debug.\u200c\u200c<\/p><\/blockquote>\n\n\n\n<h3 id=\"tying-it-together-\">Tying it together\u200c\u200c<\/h3>\n\n\n\n<p>We are almost at the finish line. Let us import the&nbsp;<em>CapacitorStorageService<\/em>&nbsp;inside the&nbsp;<em>game-engine-lib<\/em>, specifically, inside the&nbsp;<em>board.component.ts&nbsp;<\/em>file:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>import { CapacitorStorageService } from \"@shared-lib\";\n  constructor(\n    private capStorageService: CapacitorStorageService\n  ) {}<\/code><\/pre>\n\n\n\n<p>We want to persist the&nbsp;<em>highscore<\/em>&nbsp;after a webpage refresh or when we reopen the app on mobile phones or desktop, so modify these methods as follows:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code>async ngOnInit() {\n    const highscore = await this.localStorageGet(\"highscore\");  \/\/ newly added\n    highscore ? (this.highScore = highscore) : (this.highScore = 0);  \/\/ newly added\n    this.initBoard();\n    this.initSound();\n    this.initNext();\n    this.resetGame();\n  }\n\ngameOver() {\n   \u2026\n    this.highScore = this.points &gt; this.highScore ? this.points : this.highScore;\n    this.localStorageSet(\"highscore\", this.highScore);  \/\/ newly added\n    this.ctx.fillStyle = \"black\";\n   \u2026\n}<\/code><\/pre>\n\n\n\n<p>Also, add the localStorageSet and localStorageGet methods inside the&nbsp;<em>board.component.ts&nbsp;<\/em>file:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;&gt;<code> async localStorageGet(key: string): Promise&lt;any&gt; {\n    return await this.capStorageService.get(key);\n  }\n\n  localStorageSet(key: string, value: any): void {\n    this.capStorageService.set(key, value);\n  }<\/code><\/pre>\n\n\n\n<p>LocalStorage is considered<em>&nbsp;transient<\/em>, meaning your app can expect that the data will be lost eventually. The same can be said for IndexedDB at least on iOS. On Android, the&nbsp;<a href=\"https:\/\/web.dev\/persistent-storage\/\" target=\"_blank\" rel=\"noreferrer noopener\">persisted storage API<\/a>&nbsp;is available to mark IndexedDB as persisted.<\/p>\n\n\n\n<p>Capacitor comes with a native&nbsp;<a href=\"https:\/\/capacitorjs.com\/docs\/apis\/storage\" target=\"_blank\" rel=\"noreferrer noopener\">Storage API<\/a>&nbsp;that avoids the eviction issues above, but it is meant for&nbsp;<em>key-value<\/em>&nbsp;store of simple data. This API will fall back to using localStorage when not running on mobile. Hence localStorage works for our webApp, Electron as well as mobile platforms.\u200c\u200c<\/p>\n\n\n\n<h3 id=\"end-of-the-road-\">End of the road\u200c\u200c<\/h3>\n\n\n\n<p>The final workspace file structure and npm scripts should look like this &#8211; clean and simple:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img src=\"https:\/\/lh6.googleusercontent.com\/52tiY-105nXHPg-HAg6NJ1PkC3SZnnpOsSAa54E1i_IL836DW2qDbtQLbJ8mzumr8O3W5z9a0FK9kAW64_q_IPCDYJQzMi7ttyPJ2nnMEtIVCSVJmaK7uU5w6-fxqCptAXhaiJ0\" alt=\"Text  Description automatically generated\"\/><\/figure>\n\n\n\n<p>\u200c\u200cHere are all the platforms running at the same time:\u200c\u200c<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img src=\"https:\/\/lh6.googleusercontent.com\/lsPjb-WSn-QzDSCzE_NQn8OQa8tGMbywYhMOoFG2QtYFwJuFrHThH9NZpthCy3JXLzcnw4j51jWFP4o5HKzFCwTA7GfXHYFvObJJvNP4yYkPQgnhdrhvA6i3CWNyRSS08F5MfXI\" alt=\"Graphical user interface  Description automatically generated\"\/><\/figure>\n\n\n\n<p>\u200cWell done! You have made it to the end of the article. Take a deep breath and marvel at your creations \u263a<\/p>\n\n\n\n<h2 id=\"recap-\">Recap\u200c\u200c<\/h2>\n\n\n\n<p>We have seen why and how to create a monorepo-style workspace using Angular. We have also seen how effortless it is to add support for platforms other than the web. What started out as a seemingly insurmountable amount of work turned out to be quite an enjoyable journey. \u200c\u200c<\/p>\n\n\n\n<p>We have only scratched the surface of&nbsp;<a href=\"https:\/\/whatwebcando.today\/\" target=\"_blank\" rel=\"noreferrer noopener\">what the web can do today<\/a>&nbsp;and by extension, as I have demonstrated in this article \u2013 what you as a developer can do to reach as many users as possible on many devices of their choice.\u200c\u200c<\/p>\n\n\n\n<p>If you are new to software development, I hope this article has sparked your interest and makes you eager to go out there and discover more. If you are in the veteran club, I also hope this piece has inspired you and that you will share this newly acquired knowledge (and the article) with your fellow developers and teams.\u200c\u200c<\/p>\n\n\n\n<h3 id=\"acknowledgements-\">Acknowledgements\u200c\u200c<\/h3>\n\n\n\n<p>Thank you for taking the time to go on this journey with me, I highly appreciate your feedback and comments. I would also like to thank my colleagues who continue to inspire me with their sheer technical skills as well as their humbleness. To the reviewers (Agnieszka, Andrej, Diana, Hartmut, \u0418\u0433\u043e\u0440\u044c \u041a\u0430\u0446\u0443\u0431\u0430,&nbsp;<a href=\"https:\/\/twitter.com\/maxkoretskyi\" target=\"_blank\" rel=\"noreferrer noopener\">Max<\/a>, \u200c\u200cNikola, Ren\u00e9, Stacy, &nbsp;Torsten, Wiebke) of this article \u2013 a big shout out and thank you for all your input.\u200c\u200c<\/p>\n\n\n\n<h3 id=\"what-is-next-\">What is next?\u200c\u200c<\/h3>\n\n\n\n<p>Try out&nbsp;<a href=\"https:\/\/nx.dev\/latest\/angular\/getting-started\/getting-started\" target=\"_blank\" rel=\"noreferrer noopener\">Nx devtools<\/a>&nbsp;(out of the box tooling) for monorepos. There is also&nbsp;<a href=\"https:\/\/nestjs.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">NestJS&nbsp;<\/a>&#8211; a backend integration which works well with the current tech&nbsp;<a href=\"https:\/\/indepth.dev\/posts\/1247\/code-sharing-made-easy-in-a-full-stack-app-with-nx-angular-and-nestjs\" target=\"_blank\" rel=\"noreferrer noopener\">stack<\/a>&nbsp;i.e. Angular + Nodejs. Remember we also created a tetris2 project placeholder? Go ahead and fill that out with the next version of tetris i.e. make it look \u201cpretty\u201d and playable, for example, using native key gestures \u2013 as they say, the sky is the proverbial limit.\u200c\u200c<\/p>\n\n\n\n<h3 id=\"about-the-article-author-\">About the article author\u200c\u200c<\/h3>\n\n\n\n<p>Richard Sithole is a passionate frontend developer at OPTIMAL SYSTEMS Berlin where he leads efforts to build, maintain and extend a feature-rich propriety Enterprise Content Management software called&nbsp;<a href=\"https:\/\/www.optimal-systems.de\/en\/enaio\" target=\"_blank\" rel=\"noreferrer noopener\">enaio\u00ae<\/a>&nbsp;<a href=\"https:\/\/www.optimal-systems.de\/en\/enaio\/webclient\" target=\"_blank\" rel=\"noreferrer noopener\">webclient<\/a>. Previously he worked for one of the largest banks in Africa where he focused on full-stack development, application architecture, software developer&nbsp;<a href=\"https:\/\/jobportal.optimal-systems.de\/\" target=\"_blank\" rel=\"noreferrer noopener\">hiring<\/a>&nbsp;and mentoring. Say \u201challo\u201d to him on twitter&nbsp;<a href=\"https:\/\/twitter.com\/sliqric\" target=\"_blank\" rel=\"noreferrer noopener\">@sliqric<\/a><\/p>\n\n\n\n<h3 id=\"inspirational-sources-\">Inspirational sources\u200c\u200c<\/h3>\n\n\n\n<ol><li><a href=\"https:\/\/github.com\/maximegris\/angular-electron\" target=\"_blank\" rel=\"noreferrer noopener\">Bootstrap and package your project with Angular and Electron &#8211; Maxime Gris<\/a><\/li><li><a href=\"https:\/\/fireship.io\/lessons\/desktop-apps-with-electron-and-angular\/\" target=\"_blank\" rel=\"noreferrer noopener\">Desktop Apps with Electron and Angular &#8211; Jeff Delaney<\/a><\/li><li><a href=\"https:\/\/about.gitlab.com\/blog\/2019\/08\/23\/a-single-codebase-for-gitlab-community-and-enterprise-edition\/\" target=\"_blank\" rel=\"noreferrer noopener\">Why we&#8217;re using a single codebase for GitLab Community and Enterprise editions<\/a><\/li><li><a href=\"https:\/\/www.youtube.com\/watch?v=8JHz402bz34\" target=\"_blank\" rel=\"noreferrer noopener\">Angular and Electron &#8211; More than just a desktop app with Aristeidis Bampakos<\/a><\/li><li><a href=\"https:\/\/www.youtube.com\/watch?v=v8_1lDSDdgM\" target=\"_blank\" rel=\"noreferrer noopener\">Give Your Angular App Unlimited Powers with Electron &#8211; Stephen Fluin<\/a><\/li><li><a href=\"https:\/\/www.youtube.com\/watch?v=oXbRcpsytGQ\" target=\"_blank\" rel=\"noreferrer noopener\">Capacitor Workflow for iOS and Android Applications &#8211; Joshua Morony<\/a>\u200c\u200c\u200c\u200c<\/li><\/ol>\n\n\n\n<p>Fin.\u200c\u200c<\/p>\n","protected":false},"excerpt":{"rendered":"<p>From monoliths to&nbsp;monorepos, every application architecture has its own advantages and disadvantages. Monorepo-style development is an approach where you develop multiple projects in the same repository.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[30],"tags":[123],"_links":{"self":[{"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=\/wp\/v2\/posts\/1737"}],"collection":[{"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1737"}],"version-history":[{"count":1,"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=\/wp\/v2\/posts\/1737\/revisions"}],"predecessor-version":[{"id":1738,"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=\/wp\/v2\/posts\/1737\/revisions\/1738"}],"wp:attachment":[{"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1737"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1737"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1737"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}