<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Cartona]]></title><description><![CDATA[Engineering posts]]></description><link>https://engineering.cartona.com/</link><image><url>https://engineering.cartona.com/favicon.png</url><title>Cartona</title><link>https://engineering.cartona.com/</link></image><generator>Ghost 5.78</generator><lastBuildDate>Sun, 15 Mar 2026 21:01:21 GMT</lastBuildDate><atom:link href="https://engineering.cartona.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Ruby on Rails Optimization Part 1]]></title><description><![CDATA[<h3 id="overview">Overview</h3><p><strong>Hello there!</strong>&#xA0;If you&apos;re diving into this article, I&apos;m guessing you have a thing for Ruby on Rails (RoR). If not, then perhaps curiosity has led you here&#x2014;welcome either way! &#x1F604;</p><p>RoR, known for its elegant syntax, vibrant community, and the almost</p>]]></description><link>https://engineering.cartona.com/ruby-on-rails-optimization-part-1/</link><guid isPermaLink="false">65d4ae72d9fd15000139ddd3</guid><dc:creator><![CDATA[Mohamed Salem]]></dc:creator><pubDate>Tue, 20 Feb 2024 13:53:21 GMT</pubDate><media:content url="https://engineering.cartona.com/content/images/2024/02/images.jpeg" medium="image"/><content:encoded><![CDATA[<h3 id="overview">Overview</h3><img src="https://engineering.cartona.com/content/images/2024/02/images.jpeg" alt="Ruby on Rails Optimization Part 1"><p><strong>Hello there!</strong>&#xA0;If you&apos;re diving into this article, I&apos;m guessing you have a thing for Ruby on Rails (RoR). If not, then perhaps curiosity has led you here&#x2014;welcome either way! &#x1F604;</p><p>RoR, known for its elegant syntax, vibrant community, and the almost magical metaprogramming capabilities, truly makes coding an enjoyable experience. The syntactic sugar and the &quot;magic&quot; one-liners can often feel like a programming ballet. However, it&apos;s crucial to remember that every dance comes with its own set of moves that, if not executed correctly, can lead to performance pitfalls</p><p><strong>Well I have some bad news,</strong>&#xA0;the syntactic sugar comes at a cost of performance and bad practices, so in this brief article we will go over some of the most common mistakes we do in our everyday coding and how to fix them.</p><h2 id="1-the-misconception-around-pluck">1-The Misconception Around&#xA0;<code>pluck</code></h2><p>While it&apos;s often suggested that&#xA0;<code>pluck</code>&#xA0;should be avoided in favor of&#xA0;<code>select</code>&#xA0;to prevent database calls, the actual advice should be more nuanced.&#xA0;<code>pluck</code>&#xA0;directly converts a database query result into an array of values for specified columns, which can be more efficient than loading ActiveRecord objects when you only need one or a few columns. The real optimization is understanding when to use&#xA0;<code>pluck</code>&#xA0;versus&#xA0;<code>select</code>. Use&#xA0;<code>pluck</code>&#xA0;for retrieving values of a single column directly without the overhead of building ActiveRecord objects. Use&#xA0;<code>select</code>&#xA0;when you need to utilize the returned objects in Rails, especially if you&apos;re chaining more queries onto the result.</p><pre><code class="language-ruby"># Use when you need an array of values from one column
User.pluck(:id)

# Use when you need ActiveRecord objects to chain more queries
User.select(:id, :name)
</code></pre><p>ActiveRecord queries are lazily loaded. This means the query is not executed until the data is actually needed. This can be both an advantage and a disadvantage, depending on how the query is used in the view.</p><p>Consider a model method that&apos;s used in a controller and then viewed in the view. If this method returns an ActiveRecord relation, the database query is only executed when the data is actually accessed in the view:</p><p>In the model :</p><pre><code class="language-ruby">class User &lt; ApplicationRecord
  def published_articles
    articles.where(published: true).select(:title, :content) 
    # This does not execute the query
  end

  def bad_published_articles
    articles.where(published: true).pluck(:title, :content) 
    # This executes the query
  end
end
</code></pre><p>In the controller :</p><pre><code class="language-ruby">class UserController &lt; ApplicationController
  def show
    @user = User.find(params[:id])
    @articles = @user.published_articles 
    # This does not execute the query
  end
end
</code></pre><p>In the view :</p><pre><code class="language-ruby">&lt;% @articles.each do |article| %&gt; &lt;!-- The query executes here --&gt;
  &lt;%= article.title %&gt;
&lt;% end %&gt;
</code></pre><p>The&#xA0;<code>pluck</code>&#xA0;method is often misunderstood. While it&apos;s true that indiscriminate use of&#xA0;<code>pluck</code>&#xA0;over&#xA0;<code>select</code>&#xA0;can lead to performance issues, understanding when to use each can significantly optimize your queries.&#xA0;<code>pluck</code>&#xA0;shines when you need an array of values from a single column without the overhead of ActiveRecord objects. Conversely,&#xA0;<code>select</code>&#xA0;is your go-to when those ActiveRecord objects are needed for further manipulation or when chaining more queries.</p><p><strong>Pro Tip:</strong>&#xA0;Utilize&#xA0;<code>pluck</code>&#xA0;when working with APIs or background jobs where ActiveRecord object instantiation is unnecessary and could lead to increased memory usage.</p><h2 id="2-using-exists-instead-of-any-or-count"><strong>2-Using</strong>&#xA0;<code>exists?</code>&#xA0;Instead of&#xA0;<code>any?</code>&#xA0;or&#xA0;<code>count</code></h2><p>When you want to check if any records exist that meet certain criteria, it&apos;s more efficient to use&#xA0;<code>exists?</code>&#xA0;instead of&#xA0;<code>any?</code>&#xA0;or counting records.&#xA0;<code>exists?</code>&#xA0;will stop scanning as soon as it finds the first record that matches the condition, which is more efficient than loading multiple records into memory.</p><p><strong>Pro tip :</strong>&#xA0;Use&#xA0;<code>exists?</code>&#xA0;for feature toggles or conditional logic in views where you need to check the presence of associated records without loading them</p><pre><code class="language-ruby"># Less efficient
User.where(active: true).any?
User.where(active: true).count &gt; 0

# More efficient
User.exists?(active: true)
</code></pre><h2 id="3understanding-count-vs-size">3-&#xA0;<strong>Understanding</strong>&#xA0;<code>count</code>&#xA0;vs.&#xA0;<code>size</code></h2><p><code>count</code>:</p><ul><li>The&#xA0;<code>count</code>&#xA0;method performs an SQL&#xA0;<code>COUNT</code>&#xA0;query against the database.</li><li>Every time you call&#xA0;<code>count</code>, it executes a new SQL query, which can be less efficient, especially on large tables or complex associations.</li></ul><p><code>size</code>:</p><ul><li>The&#xA0;<code>size</code>&#xA0;method is more intelligent and adaptive than&#xA0;<code>count</code>.</li><li>If the association has been loaded,&#xA0;<code>size</code>&#xA0;will return the result without hitting the database by counting the elements in the loaded array.</li><li>If the association has not been loaded,&#xA0;<code>size</code>&#xA0;will perform an SQL&#xA0;<code>COUNT</code>&#xA0;query, similar to&#xA0;<code>count</code>.</li><li>This makes&#xA0;<code>size</code>&#xA0;more efficient in scenarios where the records are already loaded into memory because it avoids unnecessary database queries.</li></ul><p><strong>Pro tip:</strong>&#xA0;Default to&#xA0;<code>size</code>&#xA0;for a balanced approach to efficiency and accuracy, especially when working with associations that might already be loaded.</p><h3 id="wrapping-up"><strong>Wrapping Up</strong></h3><p>Optimizing RoR applications involves a blend of understanding ActiveRecord&apos;s intricacies and applying best practices judiciously. While syntactic sugar makes RoR delightful, it&apos;s the mindful use of its features that ensures both productivity and performance.</p><p>Stay tuned for more insights, and remember, the goal is not just to write code but to write code that scales gracefully and efficiently. Happy coding, and let&apos;s keep making magic with Ruby &#x1F48E;!</p>]]></content:encoded></item><item><title><![CDATA[[Deep Guide] Upgrading Angular and Ionic App]]></title><description><![CDATA[<blockquote><em>tips and tricks for smooth upgrading process</em></blockquote><p></p><p><strong>Hello Tech GEEKS &#x1F913;</strong><br>Today, I&apos;m gonna walk you through a crucial topic for every software engineer. As software engineers, every day we are trying to cope with newer technologies, and most importantly, the newer versions of our daily used technologies.</p>]]></description><link>https://engineering.cartona.com/upgrading-angular-and-ionic/</link><guid isPermaLink="false">65c2c0feccb5340001a23435</guid><dc:creator><![CDATA[Mazen Abo Elanin]]></dc:creator><pubDate>Mon, 12 Feb 2024 13:13:23 GMT</pubDate><media:content url="https://engineering.cartona.com/content/images/2024/02/1_lRkG_R6jCK9aMzYL0Fc7lw-1.png" medium="image"/><content:encoded><![CDATA[<blockquote><em>tips and tricks for smooth upgrading process</em></blockquote><img src="https://engineering.cartona.com/content/images/2024/02/1_lRkG_R6jCK9aMzYL0Fc7lw-1.png" alt="[Deep Guide] Upgrading Angular and Ionic App"><p></p><p><strong>Hello Tech GEEKS &#x1F913;</strong><br>Today, I&apos;m gonna walk you through a crucial topic for every software engineer. As software engineers, every day we are trying to cope with newer technologies, and most importantly, the newer versions of our daily used technologies.</p><p>In this guide, I&apos;m gonna walk you through upgrading Angular and Ionic versions of your existing app. We will take a deep dive into the steps for the upgrade, tips for a faster upgrading process, issues, and how I managed to solve them.</p><h2 id="%F0%9F%A4%9D%F0%9F%8F%BBintroduction">&#x1F91D;&#x1F3FB;Introduction:</h2><p>We needed to upgrade our Salesman app&apos;s Angular and Ionic versions to cope with the latest versions. The Salesman app was a kind of legacy app that used Angular <code>v12</code> and Ionic <code>v6</code>.</p><p>We all know that the latest version of Angular is <code>v17</code> ,and the latest version of Ionic is <code>v7</code>. After the upgrade, I succeeded in upgrading it to Angular <code>v16</code> and Ionic <code>v7</code>. So, let&apos;s start our journey together with some tips and steps for the upgrade.</p><p></p><h2 id="%F0%9F%92%A1tips-and-steps"><strong>&#x1F4A1;Tips and Steps</strong></h2><hr><h3 id="tips-for-the-upgrade">Tips for the upgrade</h3><hr><ul><li>When upgrading your app to newer versions, you have to upgrade it version by version. You shouldn&#x2019;t upgrade from <code>v12</code> to <code>v16</code> directly; instead, you should upgrade from <code>v12</code> to <code>v13</code>,&#xA0;from <code>v13</code> to <code>v14,</code> and so on, until you reach <code>v16.</code></li><li>After you upgrade your app to the newer version, fix any issues related to the upgrade.</li><li>Replace any deprecated feature or package with a new one.</li><li>You should check all the dependencies and fix any conflicts you face.</li><li>Test your app with the newer version.</li></ul><p></p><h3 id="steps-for-upgrading-angular">Steps for Upgrading Angular</h3><hr><ul><li>Go to the Angular Update Guide: <a href="https://update.angular.io/?ref=engineering.cartona.com" rel="noopener noreferrer nofollow">https://update.angular.io/</a></li><li>Select your current app version and the newer one (select from and to versions).</li><li>Choose <strong>Application Complexity:</strong>&#xA0;&#x201C;<em>Hint: You can keep it basic initially, and after you finish the steps, go to the next complexity and take a look.&#x201D;</em></li><li>Every upgrade has a minimum version of dependencies, such as <code>nodejs, typescript, Zone.js.</code>&#xA0;You should check them first and upgrade them if needed before upgrading the Angular version.</li><li>Follow all the steps in this guide.</li></ul><p></p><h3 id="steps-for-upgrading-ionic">Steps for Upgrading Ionic</h3><hr><ul><li>Go to the Ionic Update Guide:<br><a href="https://ionicframework.com/docs/updating/7-0?ref=engineering.cartona.com" rel="noopener noreferrer nofollow">https://ionicframework.com/docs/updating/7-0</a></li><li>Every version may need a minimum version of dependencies such as <code>RxJS, angular-server, angular-toolkit</code></li><li>Follow all the steps in this guide.</li><li>After the upgrade, you may need to replace some ionic HTML tags or CSS attributes; it depends on the Ionic version.</li></ul><p></p><h3 id="tips-and-tricks">Tips and Tricks</h3><hr><ul><li>Upgrade only one version at a time.</li><li>Separate each upgrade into a new branch: When you upgrade Angular to version 13, create a branch: <code>upgrade-angular-to-v13</code>. After you fix the conflicts, upgrade dependencies, and test the app, Create a new branch for upgrading Angular to version 14: <code>upgrade-angular-to-v14</code> and do the upgrade, and so on.<br><br>Every new upgrade will be in a new branch. This makes you detect the issues faster, and it is easier to rollback the upgrade.</li><li>Run <code>npm outdated</code> to list all the outdated packages in your <code>package.json</code></li><li>Run <code>npm outdated [package-name]</code> to detect specific package versions.</li><li>Run <code>npm install &lt;package-name&gt;@&lt;version&gt;</code> to install a specific version from npm</li><li>Run <code>ionic info</code> to print project, system, and environment information (ionic, capacitor, and cordova versions).</li><li>Run <code>ng version</code> to print versions information like (Angular, Node, npm, typescript, RxJS).</li><li>Learn how to read the dependencies conflict errors: <a href="https://stackoverflow.com/questions/76039613/how-do-i-read-npm-dependency-conflict-errors?ref=engineering.cartona.com">https://stackoverflow.com/questions/76039613/how-do-i-read-npm-dependency-conflict-errors</a></li><li>When you find conflicts between a dependency and its peer dependencies, you have to check the npm and GitHub repo for each to view <code>latest update</code> , <code>versions</code> , <code>releases notes</code>, <code>compatibility table</code> if exists, <code>releases</code> , and <code>issues</code></li></ul><p></p><ul><li><strong>Here&#x2019;s an example</strong> of how to fix compatibility and versions using releases on the GitHub repo:<ul><li>Go to this GitHub repo for angular/toolkit: <a href="https://github.com/ionic-team/angular-toolkit/releases?ref=engineering.cartona.com">https://github.com/ionic-team/angular-toolkit/releases</a></li><li>You will see that every release supports a specific version of Angular, so you need to pick the supported version of your own.</li><li>in this example: I upgraded <code>@ionic/angular-toolkit@9.0.0</code> <strong>and</strong> <code>@ionic/cordova-builders@9.0.0</code> when I upgraded Angular to version 15.</li></ul></li></ul><p></p><ul><li><strong>Another example is</strong>&#xA0;solving <code>ng2-file-upload</code> version conflicts.<ul><li>Go to this GitHub repo: <a href="https://github.com/valor-software/ng2-file-upload/issues/1255?ref=engineering.cartona.com">https://github.com/valor-software/ng2-file-upload/issues/1255</a></li><li>In this issue, they suggest upgrading <code>ng2-file-upload</code> to version 5 because this version supports <strong>Angular 16.</strong></li></ul></li></ul><p></p><ul><li><strong>Another example is</strong>&#xA0;solving <code>ionic-native/core</code> outdated<ul><li>go to this npm link: <a href="https://www.npmjs.com/package/@ionic-native/core?ref=engineering.cartona.com">https://www.npmjs.com/package/@ionic-native/core</a></li><li>When you click the GitHub link, it will redirect you to <code>awesome-cordova-plugin</code> instead, and after some research, I found that <code>awesome-cordova-plugin</code> is the new version of <code>ionic-native/core</code> , but it doesn&#x2019;t have a <strong>datepicker.</strong> Check out this link: <a href="https://ionic.io/blog/a-new-chapter-for-ionic-native?ref=engineering.cartona.com">https://ionic.io/blog/a-new-chapter-for-ionic-native</a></li><li>When checking the <code>ionic-native/core</code>, I realized that the last version was published <strong>2 years ago,</strong> which means it is outdated. So I searched for a replacement for this package because we use the <strong>datepickers</strong> from it, and I found <code>capacitor-community/date-picker</code> which does the same work.</li></ul></li></ul><p></p><ul><li><strong>Another example is</strong> solving the&#xA0;<code>cordova-plugin-ionic</code> JIT error<ul><li>Go to this npm link: <a href="https://www.npmjs.com/package/cordova-plugin-ionic?ref=engineering.cartona.com">https://www.npmjs.com/package/cordova-plugin-ionic</a></li><li>I found that the last version was published a <strong>year ago,</strong> which means it doesn&#x2019;t support Angular 16. and we use Deploy from cordova-plugin-ionic for the live-updates (hot deploy).</li><li>cordova-plugin-ionic doesn&apos;t support AOT compilation (AHEAD OF TIME); it supports only JIT( JUST IN TIME).</li></ul></li></ul><p></p><ul><li>Here&#x2019;s the compatibility table for Ionic v7: <a href="https://github.com/ionic-team/ionic-framework/blob/main/BREAKING.md?ref=engineering.cartona.com#version-7x">https://github.com/ionic-team/ionic-framework/blob/main/BREAKING.md#version-7x</a></li><li>Here&#x2019;s the compatibility table for <code>ngx-translate/core</code> and <code>ngx-translate/http-loader</code> : <a href="https://github.com/ngx-translate/core?ref=engineering.cartona.com#installation">https://github.com/ngx-translate/core#installation</a></li></ul><p></p><hr><blockquote><strong> After we finished the tips and steps section &#x2705;,<br><br>We learned how to solve conflicts and issues, how to organize the upgrading process, and some useful commands.<br><br>We&apos;re ready to dive into updating our app &#x1F3C3;&#x1F3FB;.  </strong></blockquote><hr><p></p><h2 id="%F0%9F%92%BB-upgrading-angular-and-ionic-%F0%9F%9A%80">&#x1F4BB; Upgrading angular and Ionic &#x1F680;</h2><hr><h3 id="upgrading-angular-from-v12-to-v13">Upgrading Angular from v12 to v13</h3><p>When upgrading Angular to v13, I faced 3 issues: will discuss them and their solutions:</p><p><strong>Issue 1 :</strong> <code>&quot;@angular-devkit/build-angular&quot; and &quot;@angular/compiler-cli&quot; conflicts</code></p><ul><li><strong>Error Explanation:</strong> Your project is trying to use version 13.3.11 of <code>@angular-devkit/build-angular</code> but it has a peer dependency on <code>@angular/compiler-cli</code> version 13.0.0 or 13.3.0-rc.0.</li><li><strong>Solution:</strong><ul><li>remove <strong>node_modules</strong> and <strong>package-lock.json</strong> to reset all the dependencies and their peers: <code>rm -rf node_modules package-lock.json</code></li><li>then run <code>npm install</code> to install all dependencies again.</li></ul></li></ul><p><strong>Issue 2 : </strong><code>Mismatch between packages</code></p><ul><li><strong>Error Explanation:</strong> type mismatch between two versions of the <code>@sentry/types</code> package. One version is being used by your application <code>(@sentry/types/types/integration)</code> and another version is being used by the sentry-cordova package <code>(sentry-cordova/node_modules/@sentry/types/dist/integration)</code>.</li><li><strong>Solution:</strong><ul><li>To fix this issue, you should ensure that both your application and the <strong>sentry-cordova</strong> package are using the same version of the <code>@sentry/types</code> package. You can do this by updating your package.json file to specify the correct version of <code>@sentry/types</code>, and then running npm install to update your dependencies.</li><li>upgrade <code>@sentry/tracing</code> and <code>sentry/cordova</code> to be compatible with each other.</li><li><code>npm i @sentry/tracing@7.34.0</code></li><li><code>npm i sentry-cordova@1.2.0</code></li></ul></li></ul><p><strong>Issue 3 :</strong> <code>browser compitability</code></p><ul><li><strong>Error Explanation:</strong> When testing the app in the emulator, a blank screen appears instead of the app. and this is because of browser compatibility.</li><li><strong>Solution:</strong><ul><li>To fix this issue, you should specify Chrome version</li><li>Open <code>.browserslistrc</code> file and copy these versions supported by angular</li><li><code>Chrome &gt;=60 Firefox &gt;=63 Edge &gt;=79 Safari &gt;=13 iOS &gt;=13</code></li></ul></li></ul><p></p><h3 id="upgrading-angular-from-v13-to-v14">Upgrading Angular from v13 to v14</h3><hr><ul><li>When upgrading Angular to v14, I didn&#x2019;t face any issues, but there is a new approach introduced in Angular 14 regarding forms, which is <strong>typed forms</strong>.</li><li>After the migration from 13 to 14, all the import forms changed to <code>untyped/forms</code> in all the components that used forms to not break any existing forms.</li><li>I removed this auto migration and kept them as <code>forms,</code> not <code>untyped/forms</code> and tested our forms, and they worked well.</li><li>Typed Form explanation link: <a href="https://angular.io/guide/typed-forms?ref=engineering.cartona.com">https://angular.io/guide/typed-forms</a></li></ul><p></p><h3 id="upgrading-angular-from-v14-to-v15">Upgrading Angular from v14 to v15</h3><hr><p>When upgrading Angular to v15 related to dependencies issues, I didn&#x2019;t face any issues but did some upgrades:</p><ul><li>Some dependencies didn&#x2019;t update automatically, so I upgraded them manually by running <code>npm i @angular-devkit/schematics@15 @angular-devkit/core@15 @schematics/angular@15</code></li><li>Upgraded Ionic 6 to the latest version by running <code>npm install @ionic/angular@6</code></li><li>Upgraded capacitor 4 to the latest version by running <code>npm i -D @capacitor/cli@latest-4</code> and all its native packages by running <code>npm i @capacitor/android@4 @capacitor/app@4 @capacitor/core@4 @capacitor/haptics@4 @capacitor/keyboard@4 @capacitor/splash-screen@4 @capacitor/status-bar@4</code></li><li>Upgraded awesome-cordova-plugins 6 to the latest version by running <code>npm i awesome-cordova-plugins@6</code></li><li>Upgraded <code>ngx-translate/core</code> and <code>ngx-translate/http-loader</code><ul><li>ngx-translate/core 14.x and ngx-translate/http-loader 7.x support Angular 15.</li><li>ngx-translate/core 15.x and ngx-translate/http-loader 8.x support Angular 16+.</li><li><code>npm i ngx-translate/core@14 http-loader@7</code></li><li>compatibility table: <a href="https://github.com/ngx-translate/core?ref=engineering.cartona.com#installation">https://github.com/ngx-translate/core#installation</a></li></ul></li><li>Upgraded <code>ionic/angular-toolkit</code> and <code>ionic/cordova-builders</code> by running <code>npm i @ionic/angular-toolkit@9 @ionic/cordova-builders@9</code><ul><li>release notes guide: <a href="https://github.com/ionic-team/angular-toolkit/releases?ref=engineering.cartona.com">https://github.com/ionic-team/angular-toolkit/releases</a></li></ul></li><li>Upgraded RxJS 6 to the latest version by running <code>npm i rxjs@6</code></li><li>Run this command <code>npm outdate [package-name] </code>to check whether the package is outdated or not.<ul><li>When checking all Cordova plugins, I found that these needed to be updated: <code>cordova@^12 cordova-browser@7 cordova-plugin-android-permissions@1.1.5 cordova-plugin-geolocation@5 cordova-plugin-statusbar@4</code></li><li><code>npm i cordova@^12</code></li><li><code>npm i cordova-browser@7 cordova-plugin-android-permissions@1.1.5 cordova-plugin-geolocation@5 cordova-plugin-statusbar@4</code></li></ul></li><li>Upgraded capacitor to version 5 by following this guide: <a href="https://capacitorjs.com/docs/updating/5-0?ref=engineering.cartona.com">https://capacitorjs.com/docs/updating/5-0</a></li></ul><p></p><h3 id="upgrading-angular-from-v15-to-v16">Upgrading Angular from v15 to v16</h3><hr><p>When upgrading Angular to v16, I upgraded some dependencies and faced some issues:</p><ul><li>Upgraded zone.js needed to be version 0.13.x or later <code>npm i zones@0.13</code></li><li>Upgraded @schematics/angular@16 <code>npm i @schematics/angular@16</code></li></ul><h3 id="issue-1-ng2-file-upload-4-didn%E2%80%99t-support-angular-16"><strong>Issue 1:</strong> ng2-file-upload 4 didn&#x2019;t support angular 16</h3><ul><li><strong>solution:</strong> upgraded <code>ng2-file-upload</code> to v5 that supports v16 <code>npm i ng2-file-upload@5</code></li></ul><h3 id="issue-2-ionicstorage-doesn%E2%80%99t-support-angular-16-needed-to-use-ionicstorage-angular-instead"><strong>issue 2:</strong> <code>ionic/storage</code> doesn&#x2019;t support angular 16, needed to use <code>ionic/storage-angular</code> instead</h3><ul><li><strong>solution:</strong><ul><li>Installed ionic/storage-angular <code>npm i ionic/storage-angular</code></li><li>Applied the new implementation from the docs: <a href="https://github.com/ionic-team/ionic-storage?ref=engineering.cartona.com">https://github.com/ionic-team/ionic-storage</a></li></ul></li></ul><h3 id="issue-3-found-that-cordova-plugin-ionic-doesnt-support-aot-compilation-ahead-of-time-it-supports-only-jit-just-in-time-and-we-use-deploy-for-hot-deploys-from-cordova-plugin-ionic"><strong>issue 3:</strong> found that <code>cordova-plugin-ionic</code> doesn&apos;t support AOT compilation (AHEAD OF TIME), it supports only JIT (JUST IN TIME) and we use <code>Deploy</code> for hot deploys from <code>cordova-plugin-ionic</code></h3><ul><li><strong>solution:</strong> Implemented live-updates from capacitor docs: <a href="https://ionic.io/docs/appflow/deploy/setup/capacitor-sdk?ref=engineering.cartona.com">https://ionic.io/docs/appflow/deploy/setup/capacitor-sdk</a></li></ul><h3 id="issue-4-enhancement-the-canactivate-interface-is-deprecated-in-angular-16-so-they-suggest-implementing-it-as-a-normal-method-instead-of-a-module"><strong>Issue 4 (enhancement):</strong> <code>The canActivate interface is deprecated in Angular 16, so they suggest implementing it as a normal method instead of a module.</code></h3><h3 id></h3><blockquote><strong>code snapshot before the migration</strong></blockquote><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2024/02/Screenshot-from-2024-01-29-00-45-37.png" class="kg-image" alt="[Deep Guide] Upgrading Angular and Ionic App" loading="lazy" width="551" height="537"><figcaption><span style="white-space: pre-wrap;">Old implementation of Guard</span></figcaption></figure><blockquote><strong>code snapshot after the migration</strong></blockquote><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2024/02/Screenshot-from-2024-01-29-00-44-52.png" class="kg-image" alt="[Deep Guide] Upgrading Angular and Ionic App" loading="lazy" width="592" height="331"><figcaption><span style="white-space: pre-wrap;">New implementation of Guard</span></figcaption></figure><p></p><h3 id="upgrading-ionic-from-v6-to-v7">Upgrading Ionic from v6 to v7</h3><hr><p>When upgrading Ionic to v7, <code>angular-toolkit</code> needed to be at version 9, but we upgraded it before, so we didn&#x2019;t need this step. I found some issues, which will be discussed below.</p><h3 id="issue-1-rxjs-version-conflicts"><strong>Issue 1:</strong> <code>RxJS version conflicts</code></h3><ul><li><strong>Error Explanation:</strong> <code>RxJS</code> needed to upgrade to version <code>7.5.0</code> but we couldn&apos;t do this because the <code>ionic-native/core</code> and datepicker we use in the app need <code>RxJS 6.5</code>. <code>ionc-native/core</code> last release was 2 years ago, so it is outdated.</li><li><strong>Solution:</strong><ul><li>I upgraded <code>RxJS</code> anyway and replaced <code>ionc-native/core</code> with <code>capacitor-community/date-picker</code></li><li>GitHub link: <a href="https://github.com/capacitor-community/date-picker?ref=engineering.cartona.com">https://github.com/capacitor-community/date-picker</a></li><li>npm link: <a href="https://www.npmjs.com/package/@capacitor-community/date-picker?ref=engineering.cartona.com">https://www.npmjs.com/package/@capacitor-community/date-picker</a></li></ul></li></ul><h3 id="issue-2-browser-compatibility"><strong>Issue 2:</strong> <code>Browser Compatibility</code></h3><ul><li><strong>Error Explanation:</strong> When testing the app in the emulator, a blank screen appears instead of the app. and this is because of browser compatibility.</li><li><strong>Solution:</strong><ul><li>To fix this issue, you should specify the Chrome version as the documentation says: <a href="https://ionicframework.com/docs/updating/7-0?ref=engineering.cartona.com">https://ionicframework.com/docs/updating/7-0</a></li><li>Open <code>.browserslistrc</code> file and copy these versions supported by Angular.</li><li><code>Chrome &gt;=79 ChromeAndroid &gt;=79 Firefox &gt;=70 Edge &gt;=79 Safari &gt;=14 iOS &gt;=14</code></li></ul></li></ul><p></p><p>After fixing these issues, I followed the <code>Update your code section</code> of&#xA0;this guide: <a href="https://ionicframework.com/docs/updating/7-0?ref=engineering.cartona.com" rel="noopener noreferrer nofollow">https://ionicframework.com/docs/updating/7-0</a> to check whether I needed to change any code or not. This code may <a href="https://ionicframework.com/docs/updating/7-0?ref=engineering.cartona.com" rel="noopener noreferrer nofollow">be HTML tags or CSS attributes</a> related to Ionic components.</p><p></p><h2 id="%F0%9F%93%9Areferences">&#x1F4DA;References</h2><hr><ul><li>Angular Update Guide: <a href="https://update.angular.io/?ref=engineering.cartona.com">https://update.angular.io/</a></li><li>Ionic Update Guide: <a href="https://ionicframework.com/docs/updating/7-0?ref=engineering.cartona.com">https://ionicframework.com/docs/updating/7-0</a></li><li>Capacitor Update Guide: <a href="https://capacitorjs.com/docs/updating/5-0?ref=engineering.cartona.com">https://capacitorjs.com/docs/updating/5-0</a></li><li>Live update capacitor SDK:<br><a href="https://ionic.io/docs/appflow/deploy/setup/capacitor-sdk?ref=engineering.cartona.com">https://ionic.io/docs/appflow/deploy/setup/capacitor-sdk</a></li></ul>]]></content:encoded></item><item><title><![CDATA[Eliminating N+1 Queries in Your Rails Application]]></title><description><![CDATA[<h2 id="introduction">Introduction</h2><p>Our APIs were suffering from slow response time and this was affecting the user experience on our apps. One of those is an important API which is responsible for fetching products on sale. The problem was resulting from N+1 queries. In this post, we&#x2019;ll explain what</p>]]></description><link>https://engineering.cartona.com/eliminating-n-1-queries-in-rails-apps/</link><guid isPermaLink="false">636fb9d1929bb0000114e251</guid><dc:creator><![CDATA[Rami Khafagi]]></dc:creator><pubDate>Wed, 30 Nov 2022 10:13:56 GMT</pubDate><media:content url="https://engineering.cartona.com/content/images/2022/11/0-E85cStg-NTVqi0Sq.png" medium="image"/><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2><img src="https://engineering.cartona.com/content/images/2022/11/0-E85cStg-NTVqi0Sq.png" alt="Eliminating N+1 Queries in Your Rails Application"><p>Our APIs were suffering from slow response time and this was affecting the user experience on our apps. One of those is an important API which is responsible for fetching products on sale. The problem was resulting from N+1 queries. In this post, we&#x2019;ll explain what N+1 queries are, how to detect them in your rails application and how to debug in order to eliminate them.</p><h2 id="what-is-n1-query">What is N+1 Query</h2><p>The N+1 query problem is one of the common performance anti-patterns which happens when the data access layer executes N additional SQL statements to fetch the same data that could have been retrieved when executing the primary SQL query [<a href="https://dev.to/junko911/rails-n-1-queries-and-eager-loading-10eh?ref=engineering.cartona.com">1</a>].</p><h2 id="api-performance-before-fix">API Performance Before Fix</h2><p>We can see that the API in the screenshot below is suffering a lot, as reported by NewRelic, a performance monitoring tool, with very slow average response time and a low Apdex score (<em><strong>i.e:</strong></em> <em><strong>a ratio value of the number of satisfied and tolerating requests to the total requests made</strong></em>) [<a href="https://docs.newrelic.com/docs/apm/new-relic-apm/apdex/apdex-measure-user-satisfaction/?ref=engineering.cartona.com">2</a>].</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/11/Screenshot-2022-11-01-at-1.15.52-PM.png" class="kg-image" alt="Eliminating N+1 Queries in Your Rails Application" loading="lazy" width="2000" height="409" srcset="https://engineering.cartona.com/content/images/size/w600/2022/11/Screenshot-2022-11-01-at-1.15.52-PM.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/11/Screenshot-2022-11-01-at-1.15.52-PM.png 1000w, https://engineering.cartona.com/content/images/size/w1600/2022/11/Screenshot-2022-11-01-at-1.15.52-PM.png 1600w, https://engineering.cartona.com/content/images/size/w2400/2022/11/Screenshot-2022-11-01-at-1.15.52-PM.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>API metrics on NewRelic before the fix</figcaption></figure><p>Below is the segment breakdown for the API response time through 2 days before deploying the fix, we can see that it doesn&#x2019;t go below 500ms.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/11/Screenshot-2022-11-12-at-2.21.29-PM-1.png" class="kg-image" alt="Eliminating N+1 Queries in Your Rails Application" loading="lazy" width="2000" height="572" srcset="https://engineering.cartona.com/content/images/size/w600/2022/11/Screenshot-2022-11-12-at-2.21.29-PM-1.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/11/Screenshot-2022-11-12-at-2.21.29-PM-1.png 1000w, https://engineering.cartona.com/content/images/size/w1600/2022/11/Screenshot-2022-11-12-at-2.21.29-PM-1.png 1600w, https://engineering.cartona.com/content/images/size/w2400/2022/11/Screenshot-2022-11-12-at-2.21.29-PM-1.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Segment breakdown graph for the response time</figcaption></figure><h2 id="how-to-detect-n1-queries">How To Detect N+1 Queries</h2><h3 id="sentry">Sentry</h3><p>Sentry is an error tracking and performance monitoring platform which can be integrated with your rails application, I will leave resources below explaining how to integrate Sentry into your rails application [<a href="https://docs.sentry.io/platforms/ruby/guides/rails/?ref=engineering.cartona.com">3</a>, <a href="https://docs.sentry.io/platforms/ruby/guides/rails/perfromance?ref=engineering.cartona.com">4</a>].</p><p>Sentry reported that our API is suffering from an N+1 query issue, and pointed out the repeated query as shown in the screenshot below:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/11/Screenshot-2022-11-12-at-1.45.32-PM.png" class="kg-image" alt="Eliminating N+1 Queries in Your Rails Application" loading="lazy" width="1878" height="1292" srcset="https://engineering.cartona.com/content/images/size/w600/2022/11/Screenshot-2022-11-12-at-1.45.32-PM.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/11/Screenshot-2022-11-12-at-1.45.32-PM.png 1000w, https://engineering.cartona.com/content/images/size/w1600/2022/11/Screenshot-2022-11-12-at-1.45.32-PM.png 1600w, https://engineering.cartona.com/content/images/2022/11/Screenshot-2022-11-12-at-1.45.32-PM.png 1878w" sizes="(min-width: 720px) 720px"><figcaption>N+1 Query reported on Sentry for the API</figcaption></figure><h3 id="newrelic">NewRelic</h3><p>Click on <strong>APM &amp; Services</strong> from the left sidebar, then select the suspect API. Click on <strong>Transactions</strong> under the Monitor tab then select the API endpoint which appears to be on the top time consuming transactions. This will open a tab that contains transaction details about the selected API endpoint..</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/11/Screenshot-2022-11-12-at-2.37.40-PM.png" class="kg-image" alt="Eliminating N+1 Queries in Your Rails Application" loading="lazy" width="2000" height="710" srcset="https://engineering.cartona.com/content/images/size/w600/2022/11/Screenshot-2022-11-12-at-2.37.40-PM.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/11/Screenshot-2022-11-12-at-2.37.40-PM.png 1000w, https://engineering.cartona.com/content/images/size/w1600/2022/11/Screenshot-2022-11-12-at-2.37.40-PM.png 1600w, https://engineering.cartona.com/content/images/size/w2400/2022/11/Screenshot-2022-11-12-at-2.37.40-PM.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>NewRelic top time consuming transactions dashboard</figcaption></figure><p>By viewing the break down table for the database transactions in the API endpoint, it&#x2019;s obvious that the pointed out queries are having high <em><strong>Avg. Calls/Transaction</strong> </em>which is a symptom for N+1 queries.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/11/Screenshot-2022-11-12-at-2.34.10-PM-1.png" class="kg-image" alt="Eliminating N+1 Queries in Your Rails Application" loading="lazy" width="2000" height="386" srcset="https://engineering.cartona.com/content/images/size/w600/2022/11/Screenshot-2022-11-12-at-2.34.10-PM-1.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/11/Screenshot-2022-11-12-at-2.34.10-PM-1.png 1000w, https://engineering.cartona.com/content/images/size/w1600/2022/11/Screenshot-2022-11-12-at-2.34.10-PM-1.png 1600w, https://engineering.cartona.com/content/images/size/w2400/2022/11/Screenshot-2022-11-12-at-2.34.10-PM-1.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>N+1 queries</figcaption></figure><h2 id="debugging-n1-queries">Debugging N+1 Queries</h2><h3 id="bullet-gem">Bullet Gem</h3><p>To detect where exactly the N+1 query is happening in your code, bullet gem comes to the rescue [<a href="https://github.com/flyerhzm/bullet?ref=engineering.cartona.com">4</a>]. It&#x2019;s a gem that watches your queries while you develop your application and notifies you when you should add eager loading (<strong>i.e: eager loading is the process by which a query for one type of entity also loads related entities as part of the query, so that we don&apos;t need to execute a separate query for related entities</strong>), when you&apos;re using eager loading that isn&apos;t necessary. Integration with bullet gem is rather simple, the docs go through it very well so I&#x2019;m not going to cover that in this article.</p><p>By enabling bullet gem and allowing <strong><em>rails_logger</em> </strong>option, bullet will create a log file for you that records the stack traces for queries that suffer from N+1 query. After that, hitting the concerned API endpoint while enabling bullet gem, it logged the N+1 query with the stack trace as shown below.</p><pre><code class="language-ruby">2022-10-31 10:57:25[WARN] user: root
GET /api/v1/supplier_offer_products?supplier_id=9&amp;branch_id=13&amp;area_id=300&amp;app_version=3.3.1
USE eager loading detected
  CoreEngine::SupplierOfferProduct =&gt; [:product_supplier]
  Add to your query: .includes([:product_supplier])
Call stack
  /app/engines/core-engine/app/models/core_engine/concerns/can_override_product_prices.rb:87:in `gross_selling_price&apos;
  /app/engines/core-engine/app/services/core_engine/products/product_pricing_service.rb:118:in `each&apos;
  /app/engines/core-engine/app/services/core_engine/products/product_pricing_service.rb:118:in `min_by&apos;
  /app/engines/core-engine/app/services/core_engine/products/product_pricing_service.rb:118:in `block in load_and_merge_available_product_prices&apos;
  /app/engines/core-engine/app/services/core_engine/products/product_pricing_service.rb:118:in `transform_values!&apos;
  /app/engines/core-engine/app/services/core_engine/products/product_pricing_service.rb:118:in `load_and_merge_available_product_prices&apos;
  /app/engines/core-engine/app/services/core_engine/products/product_pricing_service.rb:38:in `load_available_prices&apos;
  /app/engines/core-engine/app/services/core_engine/products/available_products_and_prices_service.rb:41:in `load_available_prices&apos;
  /app/app/controllers/api/v1/supplier_offer_products_controller.rb:40:in `get_offer_products&apos;
  /app/app/controllers/api/v1/supplier_offer_products_controller.rb:20:in `index&apos;
  /app/app/controllers/application_controller.rb:43:in `set_time_zone&apos;
</code></pre><p>The log here shows the code trace that led to the N+1 query and which relation we need to include while fetching the resources <code>.includes([:product_supplier])</code>. We can see that there&#x2019;s a loop as identified in the code trace <code>transform_values!</code>, which is a map function that loops on the resources and performs a hit to the database to retrieve a <code>ProductSupplier</code> record.</p><pre><code class="language-ruby">@product_supplier_prices_hash.transform_values! { |priceable_list| priceable_list.min_by(&amp;:gross_selling_price) }
</code></pre><p>This line is sorting the prices based on <code>gross_selling_price</code> and the resources in &#xA0;<code>@product_supplier_prices_hash</code>doesn&#x2019;t include <code>ProductSupplier</code> relation and &#xA0;<code>gross_selling_price</code> is defined as follows:</p><pre><code class="language-ruby">def gross_selling_price
	product_supplier.gross_selling_price_for_price selling_price
end
</code></pre><p>So it performs a query to the database to retrieve the record and get <code>gross_selling_price</code> value from it. This happens on each product in the hash. If we check the method that retrieves <code>SupplierOfferProduct</code> records, we will find that it is missing <code>.includes([:product_supplier])</code> to eagerly load the <code>ProductSupplier</code> records and have them ready in memory instead of querying the database on each <code>SupplierOfferProduct</code>.</p><pre><code class="language-ruby">def load_offer_products
    CoreEngine::SupplierOfferProduct
    .is_published
    .of_product_supplier(@product_supplier_ids)
    .available_from_supplier_or_campaigns(
       @route_ids.join(&apos;,&apos;), @retailer.supplier_class_ids.join(&apos;,&apos;), @retailer.active_campaign_ids
    )     
end
</code></pre><p>Including the <code>ProductSupplier</code> relation by adding <code>.includes([:product_supplier])</code> &#xA0;to the method eliminated the N+1 queries on this relation.</p><h3 id="avoid-adding-logic-querying-database-in-serializer">Avoid Adding Logic Querying Database in Serializer</h3><p>For the other two queries, the problem was that we were returning the supplier info in the API response as shown below:</p><pre><code class="language-ruby">render  json: all_offer_products, current_retailer: @current_retailer,
        distribution_routes_mapping: @distribution_routes_mapping,
        include: [product supplier brand category]
</code></pre><p>It&#x2019;s very important to be careful with what you return in your API response, as that will affect two things:</p><ul><li>response payload size</li><li>response time</li></ul><p>The <code>supplier</code> attribute is added to each <code>SupplierOfferProduct</code> returned but it wasn&#x2019;t actually required &#xA0;in the API response as it gets called in the supplier page to list the products which are on sale by the supplier, so the supplier info is already fetched by another API call. However, let&#x2019;s see what caused the N+1 queries.</p><p>Checking the <code>SupplierSerializer</code> there&#x2019;s an attribute returned in the serialized object which is defined as follows:</p><pre><code class="language-ruby">attribute :cashback_info do
     ActiveModelSerializers::SerializableResource.new(
     CoreEngine::Suppliers::CashbackPercentageService.new(current_user.retailer, object).call,
         each_serializer: CashbackSerializer,
         root: &apos;cashback&apos;
     )
end
</code></pre><p>What happens here is that we&#x2019;re getting the <code>cashback_info</code> which is determined by the commission rate and the campaigns the supplier is subscribed to, those two relations weren&#x2019;t eagerly loaded. So, for each <code>SupplierOfferProduct</code> we&#x2019;re adding to it a serialized object of the <code>Supplier</code> hence the N+1 queries.</p><h2 id="conclusion">Conclusion</h2><p>After eagerly loading <code>ProductSupplier</code> records and removing <code>supplier</code> attribute from the API response, the N+1 queries were completely eliminated and the API performance has increased significantly.</p><p>Checking the API performance on NewRelic before and after the fix we can see that the avg. response time decreased by <strong>70%</strong> and Apdex score increased by <strong>22%</strong>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/11/Screenshot-2022-11-01-at-1.15.52-PM-1.png" class="kg-image" alt="Eliminating N+1 Queries in Your Rails Application" loading="lazy" width="2000" height="409" srcset="https://engineering.cartona.com/content/images/size/w600/2022/11/Screenshot-2022-11-01-at-1.15.52-PM-1.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/11/Screenshot-2022-11-01-at-1.15.52-PM-1.png 1000w, https://engineering.cartona.com/content/images/size/w1600/2022/11/Screenshot-2022-11-01-at-1.15.52-PM-1.png 1600w, https://engineering.cartona.com/content/images/size/w2400/2022/11/Screenshot-2022-11-01-at-1.15.52-PM-1.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>API metrics on NewRelic before the fix</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/11/after.png" class="kg-image" alt="Eliminating N+1 Queries in Your Rails Application" loading="lazy" width="2000" height="409" srcset="https://engineering.cartona.com/content/images/size/w600/2022/11/after.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/11/after.png 1000w, https://engineering.cartona.com/content/images/size/w1600/2022/11/after.png 1600w, https://engineering.cartona.com/content/images/size/w2400/2022/11/after.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>API metrics on NewRelic after the fix</figcaption></figure><p>Looking at the segment breakdown for the response time below, we can see the drop in the response time after deploying the fix on 28th of October.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/11/Screenshot-2022-11-12-at-5.12.20-PM.png" class="kg-image" alt="Eliminating N+1 Queries in Your Rails Application" loading="lazy" width="2000" height="638" srcset="https://engineering.cartona.com/content/images/size/w600/2022/11/Screenshot-2022-11-12-at-5.12.20-PM.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/11/Screenshot-2022-11-12-at-5.12.20-PM.png 1000w, https://engineering.cartona.com/content/images/size/w1600/2022/11/Screenshot-2022-11-12-at-5.12.20-PM.png 1600w, https://engineering.cartona.com/content/images/size/w2400/2022/11/Screenshot-2022-11-12-at-5.12.20-PM.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Segment breakdown graph for the response time after deploying the fix</figcaption></figure><h2 id="takeaways">Takeaways</h2><ul><li>It is very useful to have performance monitoring tools for your applications that will help you detect performance issues. By investigating and fixing these issues, your application performance can improve significantly.</li><li>Make sure to eagerly load records needed and keep them in memory to avoid hitting the database and adding unnecessary latency to your API requests.</li><li>Make sure to avoid adding unnecessary data to your API response.</li><li>Make sure to avoid as much as you can adding logic that queries the database in your serializer.</li></ul><h2 id="resources">Resources</h2><ul><li>[1]: <a href="https://dev.to/junko911/rails-n-1-queries-and-eager-loading-10eh?ref=engineering.cartona.com" rel="noopener noreferrer">https://dev.to/junko911/rails-n-1-queries-and-eager-loading-10eh</a></li><li>[2]: <a href="https://docs.newrelic.com/docs/apm/new-relic-apm/apdex/apdex-measure-user-satisfaction/?ref=engineering.cartona.com" rel="noopener noreferrer">https://docs.newrelic.com/docs/apm/new-relic-apm/apdex/apdex-measure-user-satisfaction/</a></li><li>[3]: <a href="https://docs.sentry.io/platforms/ruby/guides/rails/?ref=engineering.cartona.com" rel="noopener noreferrer">https://docs.sentry.io/platforms/ruby/guides/rails/</a></li><li>[4]: <a href="https://docs.sentry.io/platforms/ruby/guides/rails/performance/?ref=engineering.cartona.com" rel="noopener noreferrer">https://docs.sentry.io/platforms/ruby/guides/rails/performance/</a><br></li></ul>]]></content:encoded></item><item><title><![CDATA[Solving Memory Leaks Issues in Ionic Angular Part II]]></title><description><![CDATA[<h2 id="introduction">Introduction</h2><p>One of our mobile apps used by tens of thousands of retailers in Egypt is suffering in performance, mainly in the SupplierProducts page which lists the products sold by a specific supplier. Most of the complaints indicate that the app performance degrades while navigating through multiple suppliers. And eventually,</p>]]></description><link>https://engineering.cartona.com/solving-memory-leaks-issues-in-ionic-angular-part-ii-2/</link><guid isPermaLink="false">634d559c929bb0000114e127</guid><dc:creator><![CDATA[Rami Khafagi]]></dc:creator><pubDate>Mon, 31 Oct 2022 11:49:02 GMT</pubDate><media:content url="https://engineering.cartona.com/content/images/2022/10/1-9OnH6kCtd2Y8-fjWpvKFeg-1.jpeg" medium="image"/><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2><img src="https://engineering.cartona.com/content/images/2022/10/1-9OnH6kCtd2Y8-fjWpvKFeg-1.jpeg" alt="Solving Memory Leaks Issues in Ionic Angular Part II"><p>One of our mobile apps used by tens of thousands of retailers in Egypt is suffering in performance, mainly in the SupplierProducts page which lists the products sold by a specific supplier. Most of the complaints indicate that the app performance degrades while navigating through multiple suppliers. And eventually, the app crashes. Usually, this is a symptom for memory leaks [<a href="https://developer.chrome.com/docs/devtools/memory-problems/?ref=engineering.cartona.com#overview">1</a>].</p><h2 id="debugging-process">Debugging Process</h2><p>Under Chrome Developer tools, recording a heap allocation timeline and using the performance monitor provide strong insights about what is happening according to memory allocations.</p><h3 id="heap-allocation-timeline">Heap Allocation Timeline</h3><p>Before starting to record heap allocation, I made sure to take a note of the current heap size and it was <strong>19.5MB</strong>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-2.38.21-PM.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular Part II" loading="lazy" width="1827" height="823" srcset="https://engineering.cartona.com/content/images/size/w600/2022/10/Screenshot-2022-10-12-at-2.38.21-PM.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/10/Screenshot-2022-10-12-at-2.38.21-PM.png 1000w, https://engineering.cartona.com/content/images/size/w1600/2022/10/Screenshot-2022-10-12-at-2.38.21-PM.png 1600w, https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-2.38.21-PM.png 1827w" sizes="(min-width: 720px) 720px"><figcaption>initial heap size</figcaption></figure><p>After visiting multiple supplier products pages, I found that none of the visited pages are garbage collected (see the figure below). This means that there are some references to those objects which prevents the GC from collecting the allocated memory.</p><p>We can see that the <em><strong>retained size</strong> (i.e: the amount of memory that will be free once the object is garbage collected)</em> for <strong>SupplierProductsPage</strong> and <strong>SupplierProductCardComponent</strong> which is the product cards inside the page are almost <strong>1.72MB</strong>.</p><p>We can also see that the heap size jumped from <strong>19.5MB</strong> to <strong>57.6MB (38.1MB up, almost a 200% increase)</strong>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-2.44.06-PM.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular Part II" loading="lazy" width="1829" height="731" srcset="https://engineering.cartona.com/content/images/size/w600/2022/10/Screenshot-2022-10-12-at-2.44.06-PM.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/10/Screenshot-2022-10-12-at-2.44.06-PM.png 1000w, https://engineering.cartona.com/content/images/size/w1600/2022/10/Screenshot-2022-10-12-at-2.44.06-PM.png 1600w, https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-2.44.06-PM.png 1829w" sizes="(min-width: 720px) 720px"><figcaption>Number of SupplierProductsPage objects (Note: shallow and retained sizes are in bytes)</figcaption></figure><p>Objects taking a significant size and bloating the memory are <em><strong>detached elements</strong></em>, which are elements removed from the DOM tree but <em>are still referenced somewhere</em> so they don&#x2019;t get garbage collected.</p><p>We can see in the figure below by checking the <em><strong>retained size</strong></em> column for each detached element group that the retained size by each group is huge.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-2.59.45-PM.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular Part II" loading="lazy" width="1841" height="1458" srcset="https://engineering.cartona.com/content/images/size/w600/2022/10/Screenshot-2022-10-12-at-2.59.45-PM.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/10/Screenshot-2022-10-12-at-2.59.45-PM.png 1000w, https://engineering.cartona.com/content/images/size/w1600/2022/10/Screenshot-2022-10-12-at-2.59.45-PM.png 1600w, https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-2.59.45-PM.png 1841w" sizes="(min-width: 720px) 720px"><figcaption>Detached elements retained size</figcaption></figure><h3 id="performance-monitor">Performance Monitor</h3><p>The performance monitor gives you a real-time view of various aspects of run-time performance, it gives you graphs showing the total number of DOM nodes created, JS Heap size, and more.</p><p>Recording the DOM nodes creation and JS heap size while visiting multiple suppliers indicates that the total number of nodes created and JS heap size graphs are taking the shape of a ladder which means that they keep growing only, almost taking the shape of a straight line going up.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-2.55.09-PM.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular Part II" loading="lazy" width="1830" height="592" srcset="https://engineering.cartona.com/content/images/size/w600/2022/10/Screenshot-2022-10-12-at-2.55.09-PM.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/10/Screenshot-2022-10-12-at-2.55.09-PM.png 1000w, https://engineering.cartona.com/content/images/size/w1600/2022/10/Screenshot-2022-10-12-at-2.55.09-PM.png 1600w, https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-2.55.09-PM.png 1830w" sizes="(min-width: 720px) 720px"><figcaption>Number of DOM nodes and JS heap size in real-time before the fix approaching 40K after visiting 11 SupplierProductPages</figcaption></figure><h3 id="retainer-tree">Retainer Tree</h3><p>Retainer Tree represents the objects that have reference to the selected object.</p><p>Checking the retainer tree for each of the <strong>SupplierProductsPage</strong> objects indicates there&#x2019;s a common reference between them which is <code>el in Swiper</code> which is a <em><strong>Detached HTMElement</strong>.</em></p><p><code>Swiper</code> comes from <code>ion-slides</code> which is an ionic element and internally works using <strong>SwiperJS</strong>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-3.16.54-PM.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular Part II" loading="lazy" width="1835" height="1193" srcset="https://engineering.cartona.com/content/images/size/w600/2022/10/Screenshot-2022-10-12-at-3.16.54-PM.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/10/Screenshot-2022-10-12-at-3.16.54-PM.png 1000w, https://engineering.cartona.com/content/images/size/w1600/2022/10/Screenshot-2022-10-12-at-3.16.54-PM.png 1600w, https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-3.16.54-PM.png 1835w" sizes="(min-width: 720px) 720px"><figcaption>Element holding reference to the object</figcaption></figure><h3 id="digging-into-ion-slides">Digging into ion-slides</h3><p>Doing some search about <code>ion-slides</code>, I found out that it has been deprecated and Ionic suggests migrating to <strong>SwiperJS</strong> and using it directly for Angular [<a href="https://ionicframework.com/docs/api/slides?ref=engineering.cartona.com#migration">2</a>].</p><p>By checking the retainer tree for the swiper element, it turns out there is an event listener that is still alive which holds a reference to the element.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-17-at-3.01.18-AM.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular Part II" loading="lazy" width="1522" height="925" srcset="https://engineering.cartona.com/content/images/size/w600/2022/10/Screenshot-2022-10-17-at-3.01.18-AM.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/10/Screenshot-2022-10-17-at-3.01.18-AM.png 1000w, https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-17-at-3.01.18-AM.png 1522w" sizes="(min-width: 720px) 720px"><figcaption>Event listener holding reference to <code>swiper</code> element</figcaption></figure><p>Digging into the code for swiper in <code>@ionic/core</code>, I found two event listeners; one on <code>orientationChange</code>, and the other on window <code>resize</code> in order to resize the swiper element in both cases.</p><p>The problem was that the <code>destroy</code> method was never invoked to remove the listeners, as I tried adding two <code>console.log</code> one in <code>init</code> method and the other in <code>destroy</code> method and fount that <code>destroy</code> was not invoked on leaving the page, and that was causing a memory leak [<a href="https://github.com/ionic-team/ionic-framework/blob/7e5c03ae4096ada8bc7388975a83e51aab55cce5/core/src/components/slides/swiper/swiper.bundle.js?ref=engineering.cartona.com#L4376">3</a>].</p><figure class="kg-card kg-code-card"><pre><code class="language-jsx">var Resize = {
  name: &apos;resize&apos;,
  create() {
   // some logic
  },
  on: {
    init() {
      const swiper = this;
      // Emit resize
      win.addEventListener(&apos;resize&apos;, swiper.resize.resizeHandler);

      // Emit orientationchange
      win.addEventListener(&apos;orientationchange&apos;, swiper.resize.orientationChangeHandler);
    },
    destroy() {
      const swiper = this;
      win.removeEventListener(&apos;resize&apos;, swiper.resize.resizeHandler);
      win.removeEventListener(&apos;orientationchange&apos;, swiper.resize.orientationChangeHandler);
    },
  },
};
</code></pre><figcaption>Implementation of resize in <code>ion-slides</code></figcaption></figure><p>By commenting the line for adding the two event listeners and retry the same scenario found that there were no memory leaks.</p><h2 id="solution">Solution</h2><p>The solution to migrate to <strong>SwiperJS</strong> as advised and install the latest version because <code>@ionic/core</code> was using <strong>v5.4.1</strong>, released on May 2020, and is so outdated.</p><p>Adding <code>console.log</code> inside <code>destroy</code> method of <code>resize.js</code> file in the newer <strong>SwiperJS</strong>, I found that the destroy method was invoked and the event listeners were removed and no memory leaks occurred[<a href="https://github.com/nolimits4web/swiper/blob/0567641fbe4bdbea54b5de0bcc2c937dfb52e088/src/core/modules/resize/resize.js?ref=engineering.cartona.com#L62">4</a>].</p><figure class="kg-card kg-image-card"><img src="https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-17-at-3.29.50-AM.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular Part II" loading="lazy" width="1910" height="342" srcset="https://engineering.cartona.com/content/images/size/w600/2022/10/Screenshot-2022-10-17-at-3.29.50-AM.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/10/Screenshot-2022-10-17-at-3.29.50-AM.png 1000w, https://engineering.cartona.com/content/images/size/w1600/2022/10/Screenshot-2022-10-17-at-3.29.50-AM.png 1600w, https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-17-at-3.29.50-AM.png 1910w" sizes="(min-width: 720px) 720px"></figure><h2 id="results">Results</h2><p>Before starting recording the heap size was <strong>18.9MB</strong>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-3.40.58-PM.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular Part II" loading="lazy" width="1904" height="854" srcset="https://engineering.cartona.com/content/images/size/w600/2022/10/Screenshot-2022-10-12-at-3.40.58-PM.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/10/Screenshot-2022-10-12-at-3.40.58-PM.png 1000w, https://engineering.cartona.com/content/images/size/w1600/2022/10/Screenshot-2022-10-12-at-3.40.58-PM.png 1600w, https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-3.40.58-PM.png 1904w" sizes="(min-width: 720px) 720px"><figcaption>initial heap size</figcaption></figure><p>After migrating to <strong>SwiperJS</strong> and visiting the same number of suppliers while recording the heap allocation timeline, all the objects were garbage collected and there was no trace for <strong>SupplierProductsPage</strong> or <strong>SupplierProductCardComponent</strong> objects.</p><p>We can see that the heap size is <strong>23.1MB (4.2MB increase)</strong>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-4.23.50-PM.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular Part II" loading="lazy" width="1906" height="493" srcset="https://engineering.cartona.com/content/images/size/w600/2022/10/Screenshot-2022-10-12-at-4.23.50-PM.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/10/Screenshot-2022-10-12-at-4.23.50-PM.png 1000w, https://engineering.cartona.com/content/images/size/w1600/2022/10/Screenshot-2022-10-12-at-4.23.50-PM.png 1600w, https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-4.23.50-PM.png 1906w" sizes="(min-width: 720px) 720px"><figcaption>No leaked SupplierProductsPage objects</figcaption></figure><p>We can also see that there are no traces of the huge amount of <em><strong>detached elements</strong>.</em></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-4.23.58-PM.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular Part II" loading="lazy" width="1904" height="576" srcset="https://engineering.cartona.com/content/images/size/w600/2022/10/Screenshot-2022-10-12-at-4.23.58-PM.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/10/Screenshot-2022-10-12-at-4.23.58-PM.png 1000w, https://engineering.cartona.com/content/images/size/w1600/2022/10/Screenshot-2022-10-12-at-4.23.58-PM.png 1600w, https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-4.23.58-PM.png 1904w" sizes="(min-width: 720px) 720px"><figcaption>No huge amount of detached elements from SupplierProductsPage</figcaption></figure><p>Also by checking the performance monitor for DOM Nodes and JS Heap size we can see that the timeline is taking the shape of the healthy <strong>saw tooth wave</strong> which indicates that memory gets allocated and then deallocated.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-4.27.45-PM.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular Part II" loading="lazy" width="1904" height="782" srcset="https://engineering.cartona.com/content/images/size/w600/2022/10/Screenshot-2022-10-12-at-4.27.45-PM.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/10/Screenshot-2022-10-12-at-4.27.45-PM.png 1000w, https://engineering.cartona.com/content/images/size/w1600/2022/10/Screenshot-2022-10-12-at-4.27.45-PM.png 1600w, https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-4.27.45-PM.png 1904w" sizes="(min-width: 720px) 720px"><figcaption>Number of DOM nodes and JS heap size in real-time after the fix.</figcaption></figure><p>Compare that to the corresponding chart prior to the fix and you can see a 400% improvement in Dom nodes count, and a much healthier JS heap size chart!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-2.55.09-PM-1.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular Part II" loading="lazy" width="1830" height="592" srcset="https://engineering.cartona.com/content/images/size/w600/2022/10/Screenshot-2022-10-12-at-2.55.09-PM-1.png 600w, https://engineering.cartona.com/content/images/size/w1000/2022/10/Screenshot-2022-10-12-at-2.55.09-PM-1.png 1000w, https://engineering.cartona.com/content/images/size/w1600/2022/10/Screenshot-2022-10-12-at-2.55.09-PM-1.png 1600w, https://engineering.cartona.com/content/images/2022/10/Screenshot-2022-10-12-at-2.55.09-PM-1.png 1830w" sizes="(min-width: 720px) 720px"><figcaption>Number of DOM nodes and JS heap size in real-time before the fix approaching 40K after visiting 11 SupplierProductPages</figcaption></figure><h2 id="conclusions">Conclusions</h2><ul><li>Checking for memory leaks should be done on a regular basis because any new change, even if not by your engineers, can cause memory leaks..</li><li>You should always keep up to date with the recommendations of the tools and frameworks you&#x2019;re using.</li><li>Event listeners should always be removed once not needed to avoid memory leaks [<a href="https://www.ditdot.hr/en/causes-of-memory-leaks-in-javascript-and-how-to-avoid-them?ref=engineering.cartona.com#event-listeners">5</a>].</li></ul><h2 id="references">References</h2><ul><li>[1]: <a href="https://developer.chrome.com/docs/devtools/memory-problems/?ref=engineering.cartona.com#overview" rel="noopener noreferrer">https://developer.chrome.com/docs/devtools/memory-problems/#overview</a></li><li>[2]: <a href="https://ionicframework.com/docs/api/slides?ref=engineering.cartona.com#migration" rel="noopener noreferrer">https://ionicframework.com/docs/api/slides#migration</a></li><li>[3]: <a href="https://github.com/ionic-team/ionic-framework/blob/7e5c03ae4096ada8bc7388975a83e51aab55cce5/core/src/components/slides/swiper/swiper.bundle.js?ref=engineering.cartona.com#L4376" rel="noopener noreferrer">https://github.com/ionic-team/ionic-framework/blob/7e5c03ae4096ada8bc7388975a83e51aab55cce5/core/src/components/slides/swiper/swiper.bundle.js#L4376</a></li><li>[4]:<a href="https://github.com/nolimits4web/swiper/blob/0567641fbe4bdbea54b5de0bcc2c937dfb52e088/src/core/modules/resize/resize.js?ref=engineering.cartona.com#L62" rel="noopener noreferrer">https://github.com/nolimits4web/swiper/blob/0567641fbe4bdbea54b5de0bcc2c937dfb52e088/src/core/modules/resize/resize.js#L62</a></li><li>[5]: <a href="https://www.ditdot.hr/en/causes-of-memory-leaks-in-javascript-and-how-to-avoid-them?ref=engineering.cartona.com#event-listeners" rel="noopener noreferrer">https://www.ditdot.hr/en/causes-of-memory-leaks-in-javascript-and-how-to-avoid-them#event-listeners</a><br></li></ul>]]></content:encoded></item><item><title><![CDATA[Solving Memory Leaks Issues in Ionic Angular]]></title><description><![CDATA[ionic memory leaks]]></description><link>https://engineering.cartona.com/solving-memory-leaks-issues-in-ionic-angular/</link><guid isPermaLink="false">61e7de2cb743510001a94a76</guid><category><![CDATA[ionic]]></category><category><![CDATA[angular]]></category><category><![CDATA[memory leaks]]></category><dc:creator><![CDATA[Rami Khafagi]]></dc:creator><pubDate>Thu, 20 Jan 2022 11:47:56 GMT</pubDate><media:content url="https://engineering.cartona.com/content/images/2022/01/UvUUs1WXEBgwWcMbhbQ_JB5tScafJWbz95oNsfYnIyQQW.width-1616.jpg" medium="image"/><content:encoded><![CDATA[<h3 id="introduction">Introduction</h3><hr><img src="https://engineering.cartona.com/content/images/2022/01/UvUUs1WXEBgwWcMbhbQ_JB5tScafJWbz95oNsfYnIyQQW.width-1616.jpg" alt="Solving Memory Leaks Issues in Ionic Angular"><p>In this article, we will walk you through how to detect memory leaks in your web application and how to investigate the reasons behind those memory leaks and solve them using a real-world example. We will have a journey together to get from <strong>snapshot 1</strong> to <strong>snapshot 2</strong>.</p><p><strong>Note</strong>: In this article, we are using <strong>Ionic v5</strong> and <strong>Angular v9.1.12</strong>.<br></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/01/before.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular" loading="lazy"><figcaption>memory snapshot before solving memory leaks</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/01/after.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular" loading="lazy"><figcaption>memory snapshot after solving memory leaks</figcaption></figure><p>Memory leaks are one of the most challenging optimization issues to tackle, they can have multiple sources and usually, you will need to find the pattern that causes a memory leak. That&apos;s why it&apos;s crucial to learn how to measure memory performance and get insights from memory allocation timelines.</p><h3 id="what-is-a-memory-leak">What Is A Memory Leak?</h3><hr><p>In computer science, a memory leak is a type of resource leak that occurs when a computer program incorrectly manages memory allocations in a way that memory that is no longer needed is not released <sup><a href="https://en.wikipedia.org/wiki/Memory_leak?ref=engineering.cartona.com">1</a></sup>. In short, a memory leak can be defined as some memory allocated that is not required anymore but not released, <strong>i.e</strong>, &#xA0;not garbage collected.</p><p>Most modern programming languages manage memory automatically. The memory lifecycle usually consists of three steps:<br><strong>1</strong>. Allocate memory needed<br><strong>2</strong>. Read from and write to allocated memory<br><strong>3</strong>. Release the memory allocated as soon as it&apos;s not needed anymore</p><p>Angular does a great job at memory management but in some cases that results from bad practices in implementation leads to memory leaks and that has a very bad impact on the user experience.</p><h3 id="debugging-process">Debugging Process</h3><hr><p>Because the performance of our application degrades with time throughout the user session this is a strong indication of the existence of some memory leaks. To study that we needed to monitor the memory use in real time.</p><h4 id="step-1-monitor-memory-allocation-in-real-time">Step 1: Monitor Memory Allocation In Real-Time</h4><p>Chrome DevTools has two very helpful memory debugging tools for monitor memory allocation in real-time:</p><p><strong>1.</strong> Performance Monitor<br><strong>2.</strong> Record Memory Allocation Timeline Heap Snapshot</p><p>Let&apos;s start with the performance monitor.</p><h5 id="performance-monitor">Performance monitor</h5><p>If we open the performance monitor it starts recording the heap size throughout the session. We can see in the graph below that it has the shape of a <strong>sawtooth</strong>, the heap size is getting bigger over time and that indicates that there are some memory allocations that have not been garbage collected.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/01/before-peroframnce-monitor.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular" loading="lazy"><figcaption>performance monitor recording for JS heap size</figcaption></figure><p>Now we need to get our hands on the objects that are not garbage collected. To find that we will take a memory allocation timeline heap snapshot</p><h5 id="recording-memory-allocation-timeline-heap-snapshot">Recording Memory Allocation Timeline Heap Snapshot</h5><p>To record a memory allocation timeline make sure to refresh the page and click on the collect garbage icon, to clean the memory from any previous session leftover objects.</p><p><strong>Note</strong>: in an Angular web application make sure to set the <strong>optimization</strong> flag in <code>angular.json</code> file to <code>false</code> in order to be able to search for the components by their names.</p><p>In the memory allocation timeline below those blue bars represent new memory allocations, and grey bars represent garbage-collected memory. We can see that on each store&apos;s page we enter that memory gets allocated but when we leave the page the memory is not freed and that means we have some memory leaks.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/01/allocation-timeline-gif-1.gif" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular" loading="lazy"><figcaption>memory allocation timeline showing the memory allocations</figcaption></figure><p>Before we dive into finding the reason for the memory leaks, let&apos;s talk briefly about how garbage collection works in JavaScript. In Javascript, resources get garbage collected when there are no references to it. That means that even if an object is not needed anymore but still referenced and accessible from another place in our application, it will not be garbage collected. This issue can&apos;t be handled by the garbage collector, it&apos;s up to the developer to clean up the references to the objects.</p><h4 id="step-2-investigating-the-reasons-for-memory-leaks">Step 2: Investigating The Reasons For Memory Leaks</h4><p>After stopping the recording and you click on an object you can filter the objects by their names. On this page, we have two components <strong>SupplierProductsPage</strong> and <strong>SupplierProductCard</strong> which are rendered inside the former page as a child component.</p><p>If we filter the objects by name we will find that we have 3x <strong>SupplierProductsPage</strong> objects and 60x <strong>SupplierProductCard</strong> objects as we show 20x products on entering the page. Also, we can see some other objects created by Angular Ivy which have a very large <strong>Retained Size</strong> -- i.e the size of memory that is freed once the object itself is deleted along with its dependent objects that were made unreachable from GC roots.</p><figure class="kg-card kg-image-card"><img src="https://engineering.cartona.com/content/images/2022/01/before-angular-9.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular" loading="lazy"></figure><p>If you click on an object you will find a window get shown called <strong>object&apos;s retaining tree</strong>. This tree shows us the objects that have reference to the selected object. After going through the object&apos;s retaining tree of &#xA0;<strong>SupplierProductsPage</strong> object and <strong>SupplierProductCard</strong> object we found that there are subscriptions to RxJS <strong>Observables</strong> that are still alive and causing the memory leaks.</p><p>RxJS is a library for composing asynchronous and event-based programs by using observable sequences. It combines the Observer pattern with the Iterator pattern and functional programming with collections to fill the need for an ideal way of managing sequences of events <a href="https://rxjs-dev.firebaseapp.com/guide/overview?ref=engineering.cartona.com"><sup>2</sup></a>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/01/subscriptions.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular" loading="lazy"><figcaption>subscriptions that have references for SupplierProductsPage</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/01/card-subscription.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular" loading="lazy"><figcaption>subscriptions that have references for SupplierProductCard</figcaption></figure><p>In RxJS it is very important to remember to unsubscribe from an observable for two reasons:</p><p><strong>1.</strong> A subscription has a reference to the component they are created in. So, when the component gets destroyed it will not be garbage collected and that will result in a memory leak.<br><strong>2.</strong> The code will continue to execute in the background even if the component gets destroyed, and that can result in unpredictable errors and side effects.</p><p>The solution to this issue is quite simple yet can go unnoticed very easily. We need to hook into <code>ngOnDestroy</code> component lifecycle to clean up our subscriptions in both components before the component gets destroyed.</p><h4 id="step-3-applying-solutions">Step 3: Applying Solutions</h4><h5 id="upgrading-to-angular-10">Upgrading To Angular 10</h5><p>We found that in Angular 9, Ivy is leaking memory and it doesn&apos;t deallocate memory of destroyed components correctly. In this <a href="https://github.com/angular/angular/issues/35148?ref=engineering.cartona.com">issue</a> you can find people complaining about it. So we tried upgrading to Angular 10 to see if this issue is resolved in the newer version and we found that it did get resolved.</p><p>In the heap snapshot below we can see that the <strong>LEmbeddedView_</strong> and <strong>LComponentView_</strong> don&apos;t exist anymore.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/01/before-supplier-objects-count.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular" loading="lazy"><figcaption>memory snapshot after upgrading to Angular v10</figcaption></figure><h5 id="result-of-unsubscribing-from-observables">Result Of Unsubscribing From Observables</h5><p>The subscriptions have been cleaned up, however, after taking a new record for the memory allocation timeline the object&apos;s did not get garbage collected.<br></p><figure class="kg-card kg-image-card"><img src="https://engineering.cartona.com/content/images/2022/01/nothing.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular" loading="lazy"></figure><p>To find the reason why the objects didn&apos;t get garbage collected event after unsubscribing it took some time because I found that the problem was not in our implementation but it&apos;s a bug in an <strong>Ionic</strong> component which is <code>ion-img</code>, this component lazily loads images.</p><h5 id="describing-the-bug-in-ion-img-component">Describing The Bug In ion-img Component</h5><p>We came across this <a href="https://github.com/ionic-team/ionic-framework/issues/19242?ref=engineering.cartona.com">issue</a>, in the comments we found that someone has reported that <code>ion-img</code> component implemented by Ionic didn&apos;t clean up its <strong>IntersectionObserver</strong> references properly. If you checkout the code for the <a href="https://github.com/ionic-team/ionic-framework/blob/master/core/src/components/img/img.tsx?ref=engineering.cartona.com">implementation</a>, you will find that the IntersectionObserver doesn&apos;t get disconnected on the component destroy.</p><p>To confirm that we tried to use a native <code>img</code> tag and see if the components are garbage collected. We recorded a new memory allocation timeline to check the number of objects for each component.</p><p>In the recording below, we can see that each time we visit a store&apos;s page the garbage collector runs and the previous memory allocations get garbage collected, and that proves that the component is the problem.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/01/result.gif" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular" loading="lazy"><figcaption>memory allocation timiline after using native &lt;img&gt; element</figcaption></figure><p>We can see here that the number of <strong>SupplierProductsPage</strong> object is only x1 and the number of <strong>SupplierProductCard</strong> object is only x20 which is the number of product cards we render on entering the page.</p><figure class="kg-card kg-image-card"><img src="https://engineering.cartona.com/content/images/2022/01/result.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular" loading="lazy"></figure><p>Surprisingly, when we tried to use <code>loading=&quot;lazy&quot;</code> attribute on the native <code>img</code> tag, the issue still persists and the components don&apos;t get garbage collected <a href="https://stackoverflow.com/a/61867237?ref=engineering.cartona.com" rel="nofollow"><sup>3</sup></a>.</p><h5 id="implementing-lazy-loading">Implementing Lazy Loading</h5><p>Since we can&apos;t eagerly load images on all pages to avoid performance issues, we decided to implement lazy loading ourselves to make sure we disconnect from the <strong>IntersectionObserver</strong> on component destroy.</p><p>To avoid reinventing the wheel, we tried to look if someone has created a directive for images lazy loading and we came across this amazing <a href="https://www.bennadel.com/blog/3366-lazy-loading-images-with-the-intersectionobserver-api-in-angular-5-0-0.htm?ref=engineering.cartona.com">implementation</a> by <a href="https://www.bennadel.com/about/about-ben-nadel.htm?ref=engineering.cartona.com">Ben Nadel</a>. The implementation needed to get updated because it was implemented for Angular 5 and we use Angular 10.</p><p>After Implementing lazy loading as a directive added it to the native <code>img</code> tag using IntersectionObserverAPI we had the same result as using the native <code>img</code> tag, and that&apos;s because we made sure in the implementation that we disconnect from the IntersectionObserver when the component gets destroyed.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/01/result2.gif" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular" loading="lazy"><figcaption>memory allocation timeline after implementing lazy loading</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/01/result2.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular" loading="lazy"><figcaption>memory snapshot after implementing lazy loading</figcaption></figure><h4 id="results">Results</h4><p>After applying the above solutions:</p><ul><li>Upgrading to Angular 10</li><li>Unsubscribing from observables on components destroy</li><li>Removing <code>ion-img</code> component from all components and implement lazy loading</li></ul><p>To compare the heap snapshots we tried to perform the same actions on each session, we found that the heap size in <strong>production</strong> mode has decreased significantly from <strong>18.9mb</strong> to <strong>11.6mb</strong>. Also, we can see that the performance monitor graph has improved and it shows that there are multiple drops in heap size throughout the sessions.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/01/after-1.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular" loading="lazy"><figcaption>memory snapshot in production mode</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://engineering.cartona.com/content/images/2022/01/after-performance-monitor.png" class="kg-image" alt="Solving Memory Leaks Issues in Ionic Angular" loading="lazy"><figcaption>performance monitor recording for JS heap size in production mode</figcaption></figure>]]></content:encoded></item></channel></rss>