<?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"><channel><title><![CDATA[Parham codes]]></title><description><![CDATA[Hi, I am Parham 👋 
I am a full-stack web developer. I build web and mobile apps.
I like sharing my experience & knowledge.
Follow me for content on topics like JS, Angular, React, Ionic and UI/UX.]]></description><link>https://pazel.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1681561974558/7yAUxnJAD.png</url><title>Parham codes</title><link>https://pazel.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 15:24:30 GMT</lastBuildDate><atom:link href="https://pazel.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[What It Means to Be a Senior Developer]]></title><description><![CDATA[In the world of software development, the title "Senior Developer" is often seen as a milestone in a developer's career. But what does it truly mean to be a senior developer? It's more than just years of experience or advanced coding skills. Being a ...]]></description><link>https://pazel.dev/what-it-means-to-be-a-senior-developer</link><guid isPermaLink="true">https://pazel.dev/what-it-means-to-be-a-senior-developer</guid><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Sat, 03 Aug 2024 23:21:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Q8IgAlmHAUA/upload/5d4da178e52d8f94ed6de41b8154cb30.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the world of software development, the title "Senior Developer" is often seen as a milestone in a developer's career. But what does it truly mean to be a senior developer? It's more than just years of experience or advanced coding skills. Being a senior developer encompasses a blend of technical expertise, leadership, and a deeper understanding of the development process and its impact on the business. Here’s a closer look at the key attributes and responsibilities that define a senior developer.</p>
<h3 id="heading-technical-proficiency">Technical Proficiency</h3>
<h4 id="heading-mastery-of-technology">Mastery of Technology</h4>
<p>A senior developer has a deep understanding of the technology stack they work with. This includes not only proficiency in multiple programming languages but also a thorough understanding of frameworks, libraries, and tools. They can solve complex problems efficiently and are adept at debugging and optimising code.</p>
<h4 id="heading-architectural-knowledge">Architectural Knowledge</h4>
<p>Beyond writing code, senior developers have a strong grasp of software architecture. They can design scalable, maintainable, and robust systems. They understand the principles of good software design and can apply patterns and best practices to ensure high-quality codebases.</p>
<h3 id="heading-leadership-and-mentorship">Leadership and Mentorship</h3>
<h4 id="heading-guiding-junior-developers">Guiding Junior Developers</h4>
<p>One of the critical roles of a senior developer is to mentor junior developers.<br />This involves not only sharing knowledge and best practices but also providing constructive feedback and guidance.<br />Senior developers help shape the growth and development of their peers, fostering a collaborative and learning-oriented environment.</p>
<h4 id="heading-leading-by-example">Leading by Example</h4>
<p>Senior developers lead by example. They demonstrate professionalism, dedication, and a strong work ethic. Their behaviour sets the standard for the team, influencing the culture and productivity of the development team.</p>
<h3 id="heading-problem-solving-and-decision-making">Problem-Solving and Decision Making</h3>
<h4 id="heading-strategic-thinking">Strategic Thinking</h4>
<p>Senior developers are strategic thinkers. They understand the bigger picture and how their work impacts the business objectives. They can prioritise tasks effectively, balancing short-term goals with long-term vision. Their decisions are informed by both technical and business considerations.</p>
<h4 id="heading-proactive-problem-solving">Proactive Problem Solving</h4>
<p>Rather than just reacting to issues, senior developers anticipate potential problems and address them proactively. They can navigate complex challenges, identify root causes, and implement effective solutions that prevent future issues.</p>
<h3 id="heading-communication-and-collaboration">Communication and Collaboration</h3>
<h4 id="heading-effective-communication">Effective Communication</h4>
<p>Communication is a key skill for senior developers. They can articulate technical concepts to non-technical stakeholders, ensuring everyone understands the implications and benefits of their work. They are also excellent listeners, able to comprehend and address the concerns of their team and other departments.</p>
<h4 id="heading-cross-functional-collaboration">Cross-Functional Collaboration</h4>
<p>Senior developers often work closely with other teams, such as product management, design, and operations. They understand the importance of collaboration and can work effectively in cross-functional teams to deliver high-quality products.</p>
<h3 id="heading-continuous-learning-and-improvement">Continuous Learning and Improvement</h3>
<h4 id="heading-staying-current">Staying Current</h4>
<p>The tech industry is constantly evolving, and senior developers stay current with the latest trends and advancements. They are committed to continuous learning, whether through formal education, attending conferences, or experimenting with new technologies.</p>
<h4 id="heading-reflecting-and-adapting">Reflecting and Adapting</h4>
<p>Senior developers regularly reflect on their work and seek ways to improve. They are open to feedback and willing to adapt their methods to enhance efficiency and effectiveness.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Being a senior developer is a multifaceted role that goes beyond coding. It requires a combination of technical expertise, leadership, strategic thinking, effective communication, and a commitment to continuous learning. Senior developers play a crucial role in driving the success of their teams and the overall business, making them invaluable assets to any organization. Whether you're aspiring to become a senior developer or looking to understand the role better, these attributes and responsibilities provide a comprehensive overview of what it truly means to be a senior developer.</p>
]]></content:encoded></item><item><title><![CDATA[Use JSON to localize(translate) Auth0 email templates based on user language]]></title><description><![CDATA[Background
Answering questions in Auth0 community forum is one of the ways I give back to the community and also learn more about common problems or confusions Auth0 users face.
Recently, I came across a question asking how to customize the Auth0 ema...]]></description><link>https://pazel.dev/use-json-to-localizetranslate-auth0-email-templates-based-on-user-language</link><guid isPermaLink="true">https://pazel.dev/use-json-to-localizetranslate-auth0-email-templates-based-on-user-language</guid><category><![CDATA[Auth0]]></category><category><![CDATA[email templates]]></category><category><![CDATA[authentication]]></category><category><![CDATA[internationalization]]></category><category><![CDATA[localization]]></category><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Sun, 03 Sep 2023 14:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1663851060974/isjtrnnhS.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-background">Background</h2>
<p>Answering questions in <a target="_blank" href="https://community.auth0.com/">Auth0 community forum</a> is one of the ways I give back to the community and also learn more about common problems or confusions Auth0 users face.</p>
<p>Recently, I came across a question asking <a target="_blank" href="https://community.auth0.com/t/localization-and-saving-the-user-language/90515">how to customize the Auth0 email templates</a> in a more maintainable way.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663842774610/1R1BMi5aV.png" alt="Screen Shot 2022-09-22 at 8.30.24 pm.png" /></p>
<p>Link: https://community.auth0.com/t/localization-and-saving-the-user-language/90515</p>
<p>Auth0 supports email template customization for various emails that is sent to a user as part of different flows, such as verification email, password reset email, and so on. You can use a combination of Liquid and HTML to customize the email templates.</p>
<p>On top of that, Auth0 exposes some variables that you can use in the email templates. Things like the user's name, email, and other information.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663842864321/O-tgXiWHP.png" alt="Screen Shot 2022-09-16 at 8.28.11 am.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663843054486/nawP31j_P.png" alt="Screen Shot 2022-09-22 at 8.37.18 pm.png" /></p>
<p>Here is an example of the variables you can use in the email templates.</p>
<pre><code class="lang-plaintext">&lt;html&gt;
  &lt;head&gt;
    ...
  &lt;/head&gt;
  &lt;body&gt;
    &lt;center&gt;
      &lt;p&gt;
        {% if user.user_metadata.lang == 'es' %}
          &lt;b&gt;Hola {{ user.name }}, ...&lt;/b&gt;
        {% elsif user.user_metadata.lang == 'it' %}
        &lt;b&gt;Ciao {{ user.name }}, ...&lt;/b&gt;
        {% else %}
          &lt;b&gt;Hi {{user.name}} ...&lt;/b&gt;
        {% endif %}
      &lt;/p&gt;
      &lt;table ...&gt;
        &lt;tr&gt;
          &lt;td&gt;
          ...
</code></pre>
<p>You can see that the email template is using Liquid to check the user's language and display the appropriate greeting.</p>
<p>This is a great feature and it allows you to customize the email templates to fit your needs.</p>
<p>Note: You need to set the user's language in the user's metadata. You can do this in different ways which is out of the scope of this article.</p>
<h2 id="heading-what-is-liquid">What is Liquid?</h2>
<p><a target="_blank" href="https://shopify.github.io/liquid/">Liquid</a> is a template language that uses a combination of objects, tags, and filters inside template files to display dynamic content. Auth0 uses Liquid to customize the email template.</p>
<h2 id="heading-problem">Problem</h2>
<p>If your application supports multiple languages, you might want to customize the email templates to display the appropriate message based on the user's language.</p>
<p>In a scenario like this, you might end up with a lot of if-else statements in the email template. This can make the email template hard to maintain.</p>
<p>Depending on the number of languages you need to support and the complexity of the email template, this can become a maintenance nightmare.</p>
<p>Add to that, the fact that most users manage the email templates in the Auth0 dashboard, which means:</p>
<ul>
<li><p>The code is not version controlled</p>
</li>
<li><p>Editor experience is limited</p>
</li>
<li><p>You can't use any of the tools you use to write code</p>
</li>
<li><p>This code is not part of your test and build pipeline which means you can't test it and changes can break the email template</p>
</li>
</ul>
<h2 id="heading-solution">Solution</h2>
<p>My solution to the problem is:</p>
<p>1- Keep these email templates as part of our codebase. This way, we can use all the tools we use to manage our code. We can use version control, we can use our editor, we can use our build pipeline, and so on.</p>
<p>2- Use code to generate the email template. This way, we can write reusable, modular code that is easy to maintain.</p>
<p>3- Use JSON to maintain different language translations. This gives us the flexibility to add new languages without having to change the code.</p>
<h2 id="heading-how-to-do-it">How to do it?</h2>
<p>We can use any programming language to generate the email template. In this example, I will be using Node.js and TypeScript.</p>
<p>I have created a <a target="_blank" href="https://github.com/pazel-io/auth0-i18n-liquid-template-generator">GitHub repository</a> that can be used as a starting point for this solution.</p>
<p>The repository contains a simple Node.js application that can be used to generate the email template.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<ul>
<li><p><a target="_blank" href="https://nodejs.org/en/">Node.js</a> installed on your machine.</p>
</li>
<li><p><a target="_blank" href="https://auth0.com/signup">Auth0 account</a> with a <a target="_blank" href="https://auth0.com/docs/get-started/the-basics#account-and-tenants">tenant</a> created.</p>
</li>
</ul>
<h3 id="heading-project-setup">Project setup</h3>
<p>Project code is under the <code>src</code> folder. There are 3 folders inside the <code>src</code> folder:</p>
<ul>
<li><p><code>templates</code> folder contains the email templates. These are the templates that will be used to generate the final email template. These templates are written in TypeScript so we can easily use variables and functions inside the them.</p>
</li>
<li><p><code>languages</code> folder contains the language translations. These are JSON files that contain the translations for each language.</p>
</li>
<li><p><code>scripts</code> folder contains the scripts that will be used to generate the final email template.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663844745369/8zThPnxgW.png" alt="Screen Shot 2022-09-22 at 9.04.48 pm.png" /></p>
<h3 id="heading-what-is-inside-the-templates-folder">What is inside the <code>templates</code> folder?</h3>
<p>This folder contains a file for each email template. File name are the same as the template name in Auth0 for easy reference.</p>
<p>As an example, I have included a file called <code>verification-email.ts</code> that contains the email template for the verification email.</p>
<p>I have copied the original Auth0 email template and stored it in a variable called <code>html</code>. I am treating the original template as a string. Note that I am using backticks to define the string. This is because I want to use multi-line strings, be able to use variables and also call functions inside the string.</p>
<pre><code class="lang-plaintext">const html = `
...
    &lt;h1&gt;${localizeMessage("welcome")}&lt;/h1&gt;
        &lt;p&gt;${localizeMessage("thankYou")}&lt;/p&gt;
        &lt;p&gt;&lt;a href="{{ url }}"&gt;${localizeMessage("confirm")}&lt;/a&gt;&lt;/p&gt;
         &lt;p&gt;
            ${localizeMessage("needHelp")}
         &lt;/p&gt;
...
`;
</code></pre>
<p>The <code>localizeMessage</code> function is a helper function that will be used to localize the message. It takes a key as an argument and returns the appropriate Liquid if/else statement to support the translation for all available languages.</p>
<pre><code class="lang-plaintext">    {% if user.user_metadata.lang == 'en' %}
        Hello {{application.user}} Welcome to the site


    {% elsif user.user_metadata.lang == 'fr' %}
        Bonjour {{application.user}} Bienvenue sur le site


    {% elsif user.user_metadata.lang == 'jp' %}
        こんにちは{{application.user}}さん。サイトへようこそ。


    {% else %}
        Hello {{application.user}} Welcome to the site
    {% endif %}
</code></pre>
<p>So anywhere in the email template I need to localize a message, I can call the <code>localizeMessage</code> function and pass the key as an argument.</p>
<p>There is also an <code>index.ts</code> file that exports an array which contains all the email template names. We will use this variable later in code to decide which templates to include in our build.</p>
<pre><code class="lang-plaintext">  export const templates = ["verification-email"];
</code></pre>
<h3 id="heading-what-is-inside-the-languages-folder">What is inside the <code>languages</code> folder?</h3>
<p>As an example I have included the translation for English, French and Japanese represented by the files <code>en.json</code>, <code>fr.json</code>, and <code>jp.json</code> respectively.</p>
<p>Each json file is a map of message keys to the translated message. For example, the fr.json file might look like this:</p>
<pre><code class="lang-plaintext">{
    "welcome": "Bonjour {{application.user}} Bienvenue sur le site",
    "thankYou": "Merci de vous être inscrit. Veuillez vérifier votre adresse e-mail en cliquant sur le lien suivant:",
    "confirm": "Confirmer mon compte",
    "needHelp": "Si vous rencontrez des problèmes avec votre compte, n'hésitez pas à nous contacter en répondant à ce mail.",
    "regards": "Cordialement",
    "footer": "Si vous n'avez pas fait cette demande, veuillez nous contacter en répondant à ce mail."
}
</code></pre>
<p>For this to work properly, the message keys should be the same as the message keys in all language files.</p>
<p>There is also an <code>index.ts</code> file that exports all the language codes as a single array. This is the array that will be used when deciding which language translation are available and should be included in the generated email template.</p>
<h3 id="heading-what-is-inside-the-scripts-folder">What is inside the <code>scripts</code> folder?</h3>
<p>This folder contains two main functions:</p>
<ul>
<li><p><code>localizeMessage</code> which I described above. This function uses the array of language codes to decide which translations should be included in the generated email template.</p>
</li>
<li><p><code>writeFile</code> which is used to write the generated email template to a file.</p>
</li>
</ul>
<p>and an <code>index.ts</code> file that contains the main function. This function loops over the list of templates exported from <code>templates</code> folder and generates the localized email template for each one.</p>
<pre><code class="lang-plaintext">
const generateTemplates = async () =&gt; {
    for (const template of templates) {
        const templateContent = require(`../templates/${template}`).default;
        writeFile(template, templateContent);
    }
};
</code></pre>
<h3 id="heading-how-to-run-the-project">How to run the project?</h3>
<p>To run the project, we need to install the dependencies first. You can do that by running <code>npm install</code> in the root folder of the project.</p>
<p>Next we need to build the project. You can do that by running <code>npm run build</code> in the root folder of the project.</p>
<p>Build command will compile the TypeScript code. You can see the compiled JavaScript code in the <code>build</code> folder.</p>
<p>Finally, we need to generate the email templates. You can do that by running <code>npm run generate</code> in the root folder of the project.</p>
<p>This command will generate the email templates and store them in the <code>output</code> folder.</p>
<p>Generated email templates are html files. You can open them in your browser to see the final result. There is a <code>serve</code> script that can be used to serve the generated email templates. You can run <code>npm run serve</code> to start the server.</p>
<p>Note: Build and Output folders are ignored by git. You can change this in the <code>.gitignore</code> file.</p>
<h3 id="heading-how-to-use-the-generated-email-templates">How to use the generated email templates?</h3>
<p>After you have generated the email templates, you can use them in your Auth0 tenant. You can find the instructions on how to do that in the <a target="_blank" href="https://auth0.com/docs/email/templates#customizing-email-templates">Auth0 documentation</a>.</p>
<p>Simply open the generated email template in your code editor and copy the content of the file. Then go to the Auth0 dashboard and open the email template you want to customize. Paste the content of the generated email template in the editor and save the changes.</p>
<h3 id="heading-how-to-add-a-new-language">How to add a new language?</h3>
<p>To add a new language, you need to add a new JSON file in the <code>languages</code> folder. The file name should be the language code. For example, if you want to add a new language called <code>Spanish</code>, you need to create a file called <code>es.json</code> in the <code>languages</code> folder.</p>
<p>You need to also add the language code to the <code>languages</code> array in the <code>index.ts</code> file in the <code>languages</code> folder.</p>
<h3 id="heading-how-to-add-a-new-email-template">How to add a new email template?</h3>
<p>To add a new email template, you need to add a new file in the <code>templates</code> folder. The file name should be the same as the template name in Auth0. For example, if you want to add a new email template called <code>change-password</code>, you need to create a file called <code>my-template.ts</code> in the <code>templates</code> folder.</p>
<p>Then copy the content of the original email template from Auth0 and paste it in the <code>my-template.ts</code> file and customize it as you like.</p>
<p>Don't forget to add the template name to the <code>templates</code> array in the <code>index.ts</code> file in the <code>templates</code> folder.</p>
<h2 id="heading-future-improvements">Future improvements</h2>
<ul>
<li><p>Add validation for generated HTML files.</p>
</li>
<li><p>Integrate the project build and template generation with your project CI/CD pipeline.</p>
</li>
<li><p>Use Auth0 Management API to update an email template as part CI/CD pipeline.</p>
</li>
<li><p>Add test for templates as part of the CI/CD pipeline. We can automate sending a test email so we can make sure it works as expected before deploying it to production.</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, we tested the idea of using code and json language files to localize email templates. We created a simple project that can be used to generate localized email templates for Auth0. This project can be used as a starting point for your own project. You can add more functions to help you process the email templates and make them more dynamic.</p>
<p>For example, you might want to pull some data from other sources like external APIs or databases and use it in your email templates.</p>
<p>You can find the source code for this project on <a target="_blank" href="https://github.com/pazel-io/auth0-i18n-liquid-template-generator">GitHub</a></p>
<h2 id="heading-reference">Reference</h2>
<ul>
<li><a target="_blank" href="https://auth0.com/docs/customize/email/email-templates">Auth0 Email Templates</a></li>
</ul>
<p>Thanks for reading this article. Please reach out here or on <a target="_blank" href="https://twitter.com/_pazel">Twitter</a> if you have any feedback or questions.</p>
]]></content:encoded></item><item><title><![CDATA[How to Share Knowledge in Software Teams?]]></title><description><![CDATA[In the dynamic world of software development, effective knowledge sharing is key to the success and growth of any team. With rapidly evolving technologies and complex projects, it's crucial to have strategies and tools in place to facilitate seamless...]]></description><link>https://pazel.dev/how-to-share-knowledge-in-software-teams</link><guid isPermaLink="true">https://pazel.dev/how-to-share-knowledge-in-software-teams</guid><category><![CDATA[software development]]></category><category><![CDATA[mentorship]]></category><category><![CDATA[Pull Requests]]></category><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Sun, 13 Nov 2022 13:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/vbxyFxlgpjM/upload/49c1a4b90a658f1a85e971d06556cd33.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the dynamic world of software development, effective knowledge sharing is key to the success and growth of any team. With rapidly evolving technologies and complex projects, it's crucial to have strategies and tools in place to facilitate seamless knowledge exchange. This blog explores practical ways to share knowledge within software teams and highlights some essential tools that can enhance this process.</p>
<h2 id="heading-1-tech-talk-sessions">1. Tech Talk Sessions</h2>
<h3 id="heading-overview">Overview</h3>
<p>Tech talk sessions are structured presentations or discussions where team members share their knowledge on specific topics. These sessions can cover a wide range of subjects, from new technologies and best practices to lessons learned from recent projects.</p>
<h3 id="heading-key-features">Key Features</h3>
<ul>
<li><p><strong>Focused Discussions</strong>: Each session focuses on a specific topic, allowing for in-depth exploration.</p>
</li>
<li><p><strong>Interactive</strong>: Encourage questions and discussions to deepen understanding.</p>
</li>
<li><p><strong>Regular Scheduling</strong>: Can be held on a regular basis, such as weekly or monthly.</p>
</li>
</ul>
<h3 id="heading-benefits">Benefits</h3>
<ul>
<li><p>Promotes knowledge sharing and continuous learning within the team.</p>
</li>
<li><p>Provides a platform for team members to showcase their expertise.</p>
</li>
<li><p>Encourages cross-functional knowledge exchange.</p>
</li>
</ul>
<h2 id="heading-2-workshops">2. Workshops</h2>
<h3 id="heading-overview-1">Overview</h3>
<p>Workshops are hands-on training sessions where team members collaboratively work on specific tasks or projects. These sessions are designed to enhance practical skills and foster teamwork.</p>
<h3 id="heading-key-features-1">Key Features</h3>
<ul>
<li><p><strong>Interactive Learning</strong>: Participants actively engage in exercises and problem-solving activities.</p>
</li>
<li><p><strong>Collaborative Environment</strong>: Team members work together, sharing knowledge and techniques.</p>
</li>
<li><p><strong>Real-World Applications</strong>: Focus on practical skills that can be applied to ongoing projects.</p>
</li>
</ul>
<h3 id="heading-benefits-1">Benefits</h3>
<ul>
<li><p>Enhances practical skills and technical proficiency.</p>
</li>
<li><p>Fosters teamwork and collaboration.</p>
</li>
<li><p>Provides immediate feedback and learning opportunities.</p>
</li>
</ul>
<h2 id="heading-3-pair-programming">3. Pair Programming</h2>
<h3 id="heading-overview-2">Overview</h3>
<p>Pair programming is a practice where two developers work together at one workstation, with one writing code (the driver) and the other reviewing (the observer or navigator). This technique is highly effective for knowledge sharing and improving code quality.</p>
<h3 id="heading-key-features-2">Key Features</h3>
<ul>
<li><p><strong>Real-Time Collaboration</strong>: Both developers work together, sharing ideas and solutions.</p>
</li>
<li><p><strong>Continuous Code Review</strong>: The observer provides immediate feedback, leading to better code quality.</p>
</li>
<li><p><strong>Skill Development</strong>: Less experienced developers learn from more experienced colleagues.</p>
</li>
</ul>
<h3 id="heading-benefits-2">Benefits</h3>
<ul>
<li><p>Enhances code quality and reduces bugs.</p>
</li>
<li><p>Fosters a culture of collaboration and learning.</p>
</li>
<li><p>Helps team members share knowledge and develop skills.</p>
</li>
</ul>
<h2 id="heading-4-pull-requests">4. Pull Requests</h2>
<h3 id="heading-overview-3">Overview</h3>
<p>Pull requests are a fundamental part of many version control systems, such as Git. They allow developers to review and discuss code changes before merging them into the main codebase.</p>
<h3 id="heading-key-features-3">Key Features</h3>
<ul>
<li><p><strong>Code Review</strong>: Team members review and provide feedback on code changes.</p>
</li>
<li><p><strong>Discussion and Collaboration</strong>: Pull requests serve as a platform for discussing improvements and best practices.</p>
</li>
<li><p><strong>Version Control</strong>: Ensures that all changes are tracked and can be reverted if necessary.</p>
</li>
</ul>
<h3 id="heading-benefits-3">Benefits</h3>
<ul>
<li><p>Ensures high code quality through peer reviews.</p>
</li>
<li><p>Facilitates knowledge sharing and learning from feedback.</p>
</li>
<li><p>Promotes a collaborative development process.</p>
</li>
</ul>
<h2 id="heading-5-project-documentation-using-readme-files">5. Project Documentation Using README Files</h2>
<h3 id="heading-overview-4">Overview</h3>
<p>README files are essential for project documentation, providing a quick reference for understanding the purpose, setup, usage, and contributions to a project. They are typically the first point of contact for anyone new to the project.</p>
<h3 id="heading-key-features-4">Key Features</h3>
<ul>
<li><p><strong>Comprehensive Overview</strong>: Includes project goals, setup instructions, usage guidelines, and contribution policies.</p>
</li>
<li><p><strong>Markdown Format</strong>: Easy to format and read, supporting links, images, and code snippets.</p>
</li>
<li><p><strong>Version Control Integration</strong>: Often part of the project repository, making it easy to access and update.</p>
</li>
</ul>
<h3 id="heading-benefits-4">Benefits</h3>
<ul>
<li><p>Provides clear and concise documentation for new and existing team members.</p>
</li>
<li><p>Enhances project understanding and onboarding processes.</p>
</li>
<li><p>Encourages consistency and standardization across projects.</p>
</li>
</ul>
<h2 id="heading-6-knowledge-base-solutions-wiki">6. Knowledge Base Solutions (Wiki)</h2>
<h3 id="heading-overview-5">Overview</h3>
<p>A knowledge base, often implemented as a wiki, is a centralised repository of information where team members can document and share knowledge. It serves as a reference point for everyone on the team.</p>
<h3 id="heading-key-features-5">Key Features</h3>
<ul>
<li><p><strong>Centralised Information</strong>: All relevant information is stored in one place.</p>
</li>
<li><p><strong>Searchable Content</strong>: Easy to find specific information through search functionality.</p>
</li>
<li><p><strong>Collaborative Editing</strong>: Team members can contribute and update content collaboratively.</p>
</li>
</ul>
<h3 id="heading-benefits-5">Benefits</h3>
<ul>
<li><p>Provides a single source of truth for the team.</p>
</li>
<li><p>Facilitates easy access to information and reduces duplication of effort.</p>
</li>
<li><p>Encourages continuous documentation and updating of knowledge.</p>
</li>
</ul>
<h2 id="heading-7-async-communications-like-slack">7. Async communications like Slack</h2>
<h3 id="heading-overview-6">Overview</h3>
<p>Slack is a popular messaging and collaboration platform that facilitates real-time communication and information sharing within teams. It can be used for quick discussions, file sharing, and integrating with other tools.</p>
<h3 id="heading-key-features-6">Key Features</h3>
<ul>
<li><p><strong>Channels</strong>: Organise conversations into specific channels for projects, teams, or topics.</p>
</li>
<li><p><strong>Integration</strong>: Connects with various tools like GitHub, Google Drive, and Jira.</p>
</li>
<li><p><strong>Searchable Archives</strong>: Allows easy retrieval of past conversations and shared files.</p>
</li>
</ul>
<h3 id="heading-benefits-6">Benefits</h3>
<ul>
<li><p>Enhances real-time communication and quick information sharing.</p>
</li>
<li><p>Centralises team communication and reduces email clutter.</p>
</li>
<li><p>Supports integrations with a wide range of productivity tools.</p>
</li>
</ul>
<h2 id="heading-8-technical-blogs">8. Technical Blogs</h2>
<h3 id="heading-overview-7">Overview</h3>
<p>Technical blogs are written articles where team members share their insights, experiences, and expertise on various topics. These blogs can be internal or external and serve as a valuable resource for both current and future team members.</p>
<h3 id="heading-key-features-7">Key Features</h3>
<ul>
<li><p><strong>Detailed Articles</strong>: In-depth explanations of concepts, technologies, or project experiences.</p>
</li>
<li><p><strong>Knowledge Sharing</strong>: Provides a platform for team members to share their expertise.</p>
</li>
<li><p><strong>Accessible Resource</strong>: Serves as a long-term reference for the team.</p>
</li>
</ul>
<h3 id="heading-benefits-7">Benefits</h3>
<ul>
<li><p>Encourages team members to articulate and share their knowledge.</p>
</li>
<li><p>Provides a valuable resource for onboarding new team members.</p>
</li>
<li><p>Enhances the team's visibility and reputation within the wider tech community.</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Sharing knowledge effectively is the cornerstone of a successful software team. By implementing practices like tech talk sessions, workshops, pair programming, pull requests, and leveraging tools like knowledge base solutions, Slack, and technical blogs, teams can create an environment of continuous learning and collaboration. These strategies not only improve productivity and code quality but also ensure that valuable knowledge is preserved and accessible, driving the team toward greater innovation and success.</p>
]]></content:encoded></item><item><title><![CDATA[Teach me PKCE (Proof Key for Code Exchange) in 5 minutes]]></title><description><![CDATA[What is PKCE?
PKCE (Proof Key for Code Exchange) is an extension to the OAuth 2.0 protocol that prevents authorization code interception attacks. It is a simple, lightweight mechanism that can be implemented in any application that requests an author...]]></description><link>https://pazel.dev/teach-me-pkce-proof-key-for-code-exchange-in-5-minutes</link><guid isPermaLink="true">https://pazel.dev/teach-me-pkce-proof-key-for-code-exchange-in-5-minutes</guid><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Sun, 11 Sep 2022 09:28:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1662888392363/i39nG7x7N.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-is-pkce">What is PKCE?</h2>
<p>PKCE (Proof Key for Code Exchange) is an extension to the OAuth 2.0 protocol that prevents authorization code interception attacks. It is a simple, lightweight mechanism that can be implemented in any application that requests an authorization code.</p>
<h2 id="heading-why-do-i-need-pkce-in-my-mobile-or-web-app">Why do I need PKCE in my mobile or web app?</h2>
<p>It all started with the <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">OAuth 2.0 Authorization Code Grant</a>. This grant type is used by native and web applications to obtain an access token by exchanging an authorization code for a token. The authorization code is obtained by redirecting the user to the authorization server's authorization endpoint. The authorization endpoint redirects the user back to the application with an authorization code.</p>
<p>The authorization code is a temporary code that the client will exchange for an access token. The authorization code is a single-use code, meaning that it can only be used once. If an attacker manages to intercept the authorization code, they can exchange it for an access token. This is a serious security issue that can result in the attacker being able to access protected resources on behalf of the user.</p>
<p><a target="_blank" href="https://mermaid.live/edit#pako:eNqVUctqwzAQ_JVlz8kP6BAozTG30Jsui7yxhe2VK61C25B_j1RjaIxL6U3M7DzQ3NCFhtGglcTvmcXx0VMbabRiZaKo3vmJROEtcVxjr4Nn0TX6krUL0X-R-iBQZNdZWR32h8MsMlXsehhC66WyM1z4LblZmcbaNX0nb50Xlxpm4FTdYaKWfxTYDjgtRX4xXGo_0_X3_t2-ioA_XEcyF_sr0jlOCTT0LLjDkeNIvimj3awAWNSOR7ZoyrOh2Nsy5r3cUdZw_hSHRmPmHeapIV3mRXOhIfH9ATHzwBk"><img src="https://mermaid.ink/img/pako:eNqVUctqwzAQ_JVlz8kP6BAozTG30Jsui7yxhe2VK61C25B_j1RjaIxL6U3M7DzQ3NCFhtGglcTvmcXx0VMbabRiZaKo3vmJROEtcVxjr4Nn0TX6krUL0X-R-iBQZNdZWR32h8MsMlXsehhC66WyM1z4LblZmcbaNX0nb50Xlxpm4FTdYaKWfxTYDjgtRX4xXGo_0_X3_t2-ioA_XEcyF_sr0jlOCTT0LLjDkeNIvimj3awAWNSOR7ZoyrOh2Nsy5r3cUdZw_hSHRmPmHeapIV3mRXOhIfH9ATHzwBk" alt /></a></p>
<p>This attack is known as the <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-1.1">authorization code interception attack</a> and is described in <a target="_blank" href="https://tools.ietf.org/html/rfc7636">RFC 7636</a>. The diagram below shows the authorization code interception attack.</p>
<p><a target="_blank" href="https://mermaid.live/edit#pako:eNqNkTFuwzAMRa9CcE4voCFA0I7Zgm5aCJm1BduUK1FF2yB3rxTXQOA4aDeB_Hz8XzyjCw2jwcTvmcXxi6c20mjFykRRvfMTicJr4riuPQ-eRdfVgyq5_l59yNqF6L9JfRAouI9ZU8lP-_0MMxXqehhC66V253Lpb42bFTTWDOnqaEteKHWZgWOlw0Qt3xjYXnBcjDwALmnXVuqvXsd--_9MUMeAP11HMpv7e61znBJo6FlwhyPHkXxTDnq2AmBROx7ZoinPhmJv0cql6ChrOH2JQ6Mx8w7z1JAux0fzRkPiyw_uVMtB"><img src="https://mermaid.ink/img/pako:eNqNkTFuwzAMRa9CcE4voCFA0I7Zgm5aCJm1BduUK1FF2yB3rxTXQOA4aDeB_Hz8XzyjCw2jwcTvmcXxi6c20mjFykRRvfMTicJr4riuPQ-eRdfVgyq5_l59yNqF6L9JfRAouI9ZU8lP-_0MMxXqehhC66V253Lpb42bFTTWDOnqaEteKHWZgWOlw0Qt3xjYXnBcjDwALmnXVuqvXsd--_9MUMeAP11HMpv7e61znBJo6FlwhyPHkXxTDnq2AmBROx7ZoinPhmJv0cql6ChrOH2JQ6Mx8w7z1JAux0fzRkPiyw_uVMtB" alt /></a></p>
<p>PKCE is an extension to the authorization code grant that prevents this type of attack. It does this by including a code challenge and code verifier in the authorization request. The authorization server will then perform a challenge-response validation to ensure that the code verifier matches the code challenge. If the validation fails, the authorization server will reject the request.</p>
<h2 id="heading-how-does-pkce-work">How does PKCE work?</h2>
<p>PKCE is a simple extension to the authorization code grant. It adds two extra parameters to the authorization code grant: <code>code_challenge</code> and <code>code_verifier</code>. The <code>code_challenge</code> is a Base64-encoded SHA-256 hash of the <code>code_verifier</code>. The <code>code_verifier</code> is a cryptographically random string. Both <code>code_verifier</code> and the <code>code_challenge</code> is generated by the client.</p>
<p>The <code>code_challenge</code> and <code>code_verifier</code> are used to perform a challenge-response validation. The authorization server will generate a <code>code_challenge</code> from the <code>code_verifier</code> and compare it to the <code>code_challenge</code> included in the authorization request. If the two values match, the authorization server will issue an authorization token. If the two values do not match, the authorization server will reject the request.</p>
<p><a target="_blank" href="https://mermaid.live/edit#pako:eNqVkk1qxDAMha8itJ5ewIuB0i5nV7ozFOFoEpFETm2lf8PcvTZpoA0ppTsj6T19z_YFQ2wYHWZ-nlkD3wu1iUavXidKJkEmUoPHzGlbuxuE1bbV29m6mOSDTKJCkb0syupwczwuIlfFoYchtqK1u5RLf0_uNqapsmaDV7EOKv9T6GgYWFuuXnsWxbkCODjVjTDRMvoFtb_0tML9YrhG-dmuPP9OVEXAbyVGyfAtVxmVsywX-BdFCJwzWOxZ8YAjp5GkKS978Qrg0Toe2aMrx4ZS79HrtczRbPHhXQM6SzMfcJ4asvUXoDvTkPn6CYUGzjo"><img src="https://mermaid.ink/img/pako:eNqVkk1qxDAMha8itJ5ewIuB0i5nV7ozFOFoEpFETm2lf8PcvTZpoA0ppTsj6T19z_YFQ2wYHWZ-nlkD3wu1iUavXidKJkEmUoPHzGlbuxuE1bbV29m6mOSDTKJCkb0syupwczwuIlfFoYchtqK1u5RLf0_uNqapsmaDV7EOKv9T6GgYWFuuXnsWxbkCODjVjTDRMvoFtb_0tML9YrhG-dmuPP9OVEXAbyVGyfAtVxmVsywX-BdFCJwzWOxZ8YAjp5GkKS978Qrg0Toe2aMrx4ZS79HrtczRbPHhXQM6SzMfcJ4asvUXoDvTkPn6CYUGzjo" alt /></a></p>
<p>If an attacker intercepts the authorization code, they will not be able to exchange it for an access token because they do not have the <code>code_verifier</code>. The diagram below shows the authorization code interception attack with PKCE.</p>
<p><a target="_blank" href="https://mermaid.live/edit#pako:eNqNksFOwzAMhl_FynnjAXKYNI3jxAW4RUJW6rWhaVJSFzamvTsOaYVUisQtsn9_-X_LV2VjRUqrgd5GCpbuHdYJOxNM6DGxs67HwPA8UFrWDt5R4GV1z4y2_a3ej9zE5D6RXQwguPeiyeTtbldgOkNtCz7WLuRuKUt_bVwvoClnGBg-HDeQc73YBr2nUFNmrSGEnA1oOOYfoccinUytf3qczf0BnDewtJcdfY9N_X-mymNAZ4kiOUq2h1jiidqdXNnjqpnt-cfMU0OAKyJBei-7eyXLwCKa1ninNqqj1KGr5D6uJgAYJf2OjNLyrDC1RplwE51w4-MlWKU5jbRRY18hz7ek9An9QLcvbprpVQ"><img src="https://mermaid.ink/img/pako:eNqNksFOwzAMhl_FynnjAXKYNI3jxAW4RUJW6rWhaVJSFzamvTsOaYVUisQtsn9_-X_LV2VjRUqrgd5GCpbuHdYJOxNM6DGxs67HwPA8UFrWDt5R4GV1z4y2_a3ej9zE5D6RXQwguPeiyeTtbldgOkNtCz7WLuRuKUt_bVwvoClnGBg-HDeQc73YBr2nUFNmrSGEnA1oOOYfoccinUytf3qczf0BnDewtJcdfY9N_X-mymNAZ4kiOUq2h1jiidqdXNnjqpnt-cfMU0OAKyJBei-7eyXLwCKa1ninNqqj1KGr5D6uJgAYJf2OjNLyrDC1RplwE51w4-MlWKU5jbRRY18hz7ek9An9QLcvbprpVQ" alt /></a></p>
<h2 id="heading-how-do-i-implement-pkce">How do I implement PKCE?</h2>
<p>Implementing PKCE is simple. You just need to generate a cryptographically random string and hash it using SHA-256. You can use any programming language to generate the <code>code_verifier</code> and <code>code_challenge</code>. The following example uses Node.js to generate the <code>code_verifier</code> and <code>code_challenge</code>.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> crypto = <span class="hljs-built_in">require</span>(<span class="hljs-string">'crypto'</span>);

<span class="hljs-keyword">const</span> codeVerifier = crypto.randomBytes(<span class="hljs-number">32</span>).toString(<span class="hljs-string">'base64'</span>).replace(<span class="hljs-regexp">/=/g</span>, <span class="hljs-string">''</span>).replace(<span class="hljs-regexp">/\+/g</span>, <span class="hljs-string">'-'</span>).replace(<span class="hljs-regexp">/\//g</span>, <span class="hljs-string">'_'</span>);
<span class="hljs-keyword">const</span> codeChallenge = crypto.createHash(<span class="hljs-string">'sha256'</span>).update(codeVerifier).digest(<span class="hljs-string">'base64'</span>).replace(<span class="hljs-regexp">/=/g</span>, <span class="hljs-string">''</span>).replace(<span class="hljs-regexp">/\+/g</span>, <span class="hljs-string">'-'</span>).replace(<span class="hljs-regexp">/\//g</span>, <span class="hljs-string">'_'</span>);
</code></pre>
<p>You can then include the <code>code_verifier</code> in the authorization request. The authorization server will then perform a challenge-response validation to ensure that the <code>code_verifier</code> matches the <code>code_challenge</code>. If the validation fails, the authorization server will reject the request.</p>
<h2 id="heading-what-is-the-difference-between-s256-and-plain">What is the difference between S256 and plain?</h2>
<p>The <code>code_challenge_method</code> parameter can be set to either <code>S256</code> or <code>plain</code>. The <code>S256</code> method uses SHA-256 to generate the <code>code_challenge</code> from the <code>code_verifier</code>. The <code>plain</code> method uses the <code>code_verifier</code> directly as the <code>code_challenge</code>. The <code>S256</code> method is recommended because it provides a higher level of security.</p>
<h2 id="heading-how-do-i-use-pkce-with-auth0">How do I use PKCE with Auth0?</h2>
<p>Auth0 supports PKCE by default. You can use PKCE with any of the <a target="_blank" href="https://auth0.com/docs/libraries">Auth0 SDKs</a> or with any OAuth 2.0 library.</p>
<h2 id="heading-how-other-oauth-providers-implement-pkce">How other OAuth Providers Implement PKCE?</h2>
<p>Google, Facebook, Microsoft, GitHub, Twitter and other OAuth providers support PKCE. You can find more information about how they implement PKCE in the following articles:</p>
<ul>
<li><a target="_blank" href="https://developers.google.com/identity/protocols/OAuth2InstalledApp#step-2:-send-a-request-to-googles-oauth-20-server">Google</a></li>
<li><a target="_blank" href="https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#confirm">Facebook</a></li>
<li><a target="_blank" href="https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#request-an-authorization-code">Microsoft</a></li>
<li><a target="_blank" href="https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/#1-request-a-users-github-identity">GitHub</a></li>
<li><a target="_blank" href="https://developer.twitter.com/en/docs/basics/authentication/api-reference/request_token">Twitter</a></li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>PKCE is a simple extension to the OAuth 2.0 authorization code grant that prevents authorization code interception attacks. It is a lightweight mechanism that can be implemented in any application that requests an authorization code.</p>
<p>Although PKCE was originally designed for native and web applications, it is recommended that you use PKCE with all applications that request an authorization code. This includes mobile applications, desktop applications, single-page applications, and server-side applications.</p>
<h2 id="heading-further-reading">Further Reading</h2>
<ul>
<li><a target="_blank" href="https://tools.ietf.org/html/rfc7636">RFC 7636</a></li>
<li><a target="_blank" href="https://auth0.com/blog/oauth-2-best-practices-for-native-apps/">OAuth 2.0 for Native Apps</a></li>
<li><a target="_blank" href="https://auth0.com/blog/oauth-2-best-practices-for-browser-based-apps/">OAuth 2.0 for Browser-Based Apps</a></li>
<li><a target="_blank" href="https://auth0.com/blog/oauth-2-best-practices-for-mobile-apps/">OAuth 2.0 for Mobile Apps</a></li>
<li><a target="_blank" href="https://auth0.com/blog/oauth-2-best-practices-for-server-to-server-apps/">OAuth 2.0 for Server-to-Server Applications</a></li>
<li><a target="_blank" href="https://auth0.com/blog/oauth-2-best-practices-for-spas/">OAuth 2.0 for Single-Page Applications</a></li>
<li><a target="_blank" href="https://auth0.com/blog/oauth-2-best-practices-for-native-apps/">OAuth 2.0 for Regular Web Apps</a></li>
<li><a target="_blank" href="https://auth0.com/blog/oauth-2-best-practices-for-desktop-apps/">OAuth 2.0 for Desktop Apps</a></li>
<li><a target="_blank" href="https://auth0.com/blog/oauth-2-best-practices-for-iot/">OAuth 2.0 for IoT Apps</a></li>
<li><a target="_blank" href="https://auth0.com/blog/oauth-2-best-practices-for-m2m-and-b2b/">OAuth 2.0 for Machine-to-Machine Apps</a></li>
<li><a target="_blank" href="https://auth0.com/blog/oauth-2-best-practices-for-multi-tenant/">OAuth 2.0 for Multi-Tenant Apps</a></li>
<li><a target="_blank" href="https://auth0.com/blog/oauth-2-best-practices-for-sso/">OAuth 2.0 for Single Sign-On</a></li>
<li><a target="_blank" href="https://auth0.com/blog/oauth-2-best-practices-for-api-auth/">OAuth 2.0 for APIs</a></li>
</ul>
<p>Thanks for reading this article. Please reach out here or on <a target="_blank" href="https://twitter.com/_pazel">Twitter</a> if you have any feedback or questions.</p>
]]></content:encoded></item><item><title><![CDATA[Risk-based Authentication using Auth0 Actions and "have i been pwned" APIs]]></title><description><![CDATA[In this article, we look at how to improve the security of a system by implementing Risk-based Authentication. To achieve this we will integrate have i been pwned APIs into our login flow using Auth0 Actions.
What is Risk-based Authentication?
To put...]]></description><link>https://pazel.dev/risk-based-authentication-using-auth0-actions-and-have-i-been-pwned-apis</link><guid isPermaLink="true">https://pazel.dev/risk-based-authentication-using-auth0-actions-and-have-i-been-pwned-apis</guid><category><![CDATA[Auth0]]></category><category><![CDATA[Security]]></category><category><![CDATA[authentication]]></category><category><![CDATA[authorization]]></category><category><![CDATA[Auth ]]></category><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Thu, 09 Jun 2022 14:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1642941541909/T_9C7yjEP.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article, we look at how to improve the security of a system by implementing Risk-based Authentication. To achieve this we will integrate <a target="_blank" href="https://haveibeenpwned.com/"><strong><em>have i been pwned</em></strong></a> APIs into our login flow using Auth0 Actions.</p>
<h2 id="heading-what-is-risk-based-authentication">What is Risk-based Authentication?</h2>
<p>To put it simply, Risk-based Authentication or RBA uses real-time intelligence about a user to decide if their activity is normal (low-risk) or suspicious (high-risk).</p>
<p>RBA estimates a risk score based on login behaviour. The calculation of risk score typically happens for any given access attempt in real-time, based on a predefined set of rules. Users are then presented with authentication options appropriate to that risk level.</p>
<p>Many factors are considered when assessing the risk of login activity. For example:</p>
<ul>
<li><p>Location of the user</p>
</li>
<li><p>Device/browser</p>
</li>
<li><p>IP and network information</p>
</li>
<li><p>Number of attempts to log in</p>
</li>
</ul>
<p>Or factors related to a user and their context. For example, a login attempt to the company's HR system at 2 am can be considered suspicious (high risk).</p>
<p>And many more factors that might make sense in a particular context.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642942998913/OeRQUg1w0.png" alt="d1.png" /></p>
<p>Most Authentication providers support RBA.</p>
<p>Here are links to few examples, <a target="_blank" href="https://auth0.com/docs/secure/multi-factor-authentication/adaptive-mfa">Auth0</a>, <a target="_blank" href="https://help.okta.com/en/prod/Content/Topics/Security/behavior-detection/faq-behavior-detection.htm">Okta</a>, <a target="_blank" href="https://docs.microsoft.com/en-us/azure/active-directory/authentication/tutorial-risk-based-sspr-mfa">Azure AD</a>, <a target="_blank" href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-adaptive-authentication.html">AWS Cognito</a> and <a target="_blank" href="https://support.google.com/a/answer/6002699?hl=en">Google</a> support RBA.</p>
<p>Using RBA you do not need to always enforce MFA and can only ask for additional authentication if it's required based on the risk score. This helps to improve the user experience of the login flow and make it easier for low-risk logins to happen with less friction.</p>
<h3 id="heading-built-in-auth0-features-for-risk-based-authentication">Built-in Auth0 features for Risk-based Authentication</h3>
<p>Luckily, Auth0 has some features to support you here</p>
<ul>
<li><a target="_blank" href="https://auth0.com/docs/secure/multi-factor-authentication/adaptive-mfa">Adaptive MFA</a> - this feature triggers MFA and asks the user to complete an MFA Challenge when the confidence is low (activity is high-risk).</li>
</ul>
<p>Auth0 also provides a range of <a target="_blank" href="https://auth0.com/docs/secure/attack-protection">Attack Protection</a> features which goes hand in hand with the RBA.</p>
<ul>
<li><p><a target="_blank" href="https://auth0.com/docs/secure/attack-protection/breached-password-detection">Breached Password Detection</a> - Auth0 tracks large security breaches that are happening on major third-party sites. If a user password is leaked in a data breach, they will be notified and also they might get blocked from logging in.</p>
</li>
<li><p><a target="_blank" href="https://auth0.com/docs/secure/attack-protection/bot-detection">Bot Detection</a> - Bot detection mitigates scripted attacks by detecting when a request is likely to be coming from a bot and presenting a Captcha challenge that blocks the Bot attack.</p>
</li>
</ul>
<p>These features support most of the typical scenarios for Risk-based Authentication and you can further customise them to best match your usage.</p>
<h2 id="heading-what-is-have-i-been-pwnedhttpshaveibeenpwnedcom-and-how-can-it-help">What is <a target="_blank" href="https://haveibeenpwned.com/"><strong><em>have i been pwned</em></strong></a> and how can it help?</h2>
<p>Created by security expert <a target="_blank" href="https://en.wikipedia.org/wiki/Troy_Hunt">Troy Hunt</a>, <a target="_blank" href="https://haveibeenpwned.com/"><em>have i been pwned</em></a> is a website that allows Internet users to check whether their data has been compromised by data breaches.</p>
<p>As a user, you can go to this website, enter your email and get a list of data breaches that your email address has been involved in.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642939816209/ntsVyZ5Dv.png" alt="Screen Shot 2022-01-23 at 11.09.49 pm.png" /></p>
<p><a target="_blank" href="https://haveibeenpwned.com/"><em>have i been pwned</em></a> also offers REST APIs that you can use to make the same queries through code.</p>
<p>For example, this is the response for an API that returns the list of data breaches for an email address.</p>
<p>Request</p>
<pre><code class="lang-plaintext">https://haveibeenpwned.com/api/v3/breachedaccount/{account}?truncateResponse=false
</code></pre>
<p>Response:</p>
<pre><code class="lang-plaintext">[
    {
        "Name": "XYZ",
        "Title": "XYZ",
        "Domain": "xyz.com",
        "BreachDate": "2020-03-22",
        "AddedDate": "2020-11-15T00:59:50Z",
        "ModifiedDate": "2020-11-15T01:07:10Z",
        "Description": "In March 2020, the stock photo site .....",
        "LogoPath": ".../Images/PwnedLogos/xyz.png",
        "DataClasses": [
            "Email addresses",
            "IP addresses",
            "Names",
            "Passwords",
            "Phone numbers",
            "Physical addresses",
            "Usernames"
        ],
        "IsVerified": true,
        "IsFabricated": false,
        "IsSensitive": false,
        "IsRetired": false,
        "IsSpamList": false,
        "IsMalware": false
    },
...
]
</code></pre>
<p>I think you can guess where I am going with all this by now. I want to use <a target="_blank" href="https://haveibeenpwned.com/"><em>have i been pwned</em></a> REST APIs as part of my login flow to detect if a username (email) has been listed in a data breach and react appropriately based on the risk factor.</p>
<p>For example, if the user (email address) is listed in a recent data breach, I will send the user a change (reset) password email and ask them to complete an MFA challenge. I will further describe the login flow in the rest of this article.</p>
<p>To use the APIs you will need to purchase an API key. Please read <a target="_blank" href="https://haveibeenpwned.com/API/Key">these instructions</a> to get your HIBP API key.</p>
<h2 id="heading-use-auth0-actions-to-extend-the-risk-based-authentication">Use Auth0 Actions to extend the Risk-based Authentication</h2>
<p>As mentioned Auth0 built-in features already provide a solid ground for Risk-based Authentication but based on your business requirement you might need to consider more factors as part of your risk assessment.</p>
<p>This is where Auth0 Actions shine and let you extend the capabilities of Auth0 beyond what is defined.</p>
<p><a target="_blank" href="https://haveibeenpwned.com/"><em>have i been pwned</em></a> provides data for <a target="_blank" href="https://haveibeenpwned.com/FAQs">data breaches</a> and also <a target="_blank" href="https://haveibeenpwned.com/Pastes"><strong>Pastes</strong></a> and usually has the data before other sources. So it can be a good accompanying source to secure your system.</p>
<p>However, using the <a target="_blank" href="https://haveibeenpwned.com/"><em>have i been pwned</em></a> REST APIs is just an example to demonstrate how you can use other data sources to enhance your risk assessment. So please do your research before adopting this data source.</p>
<h3 id="heading-what-are-auth0-actions">What are Auth0 Actions?</h3>
<blockquote>
<p>Auth0 Actions are secure, tenant-specific, versioned functions written in Node.js that execute at certain points during the Auth0 runtime. Actions are used to customize and extend Auth0’s capabilities with custom logic. auth0.com/docs/customize/actions</p>
</blockquote>
<p>So the idea for this integration is to use a Login Action and write a Node.js function to:</p>
<ul>
<li><p>Call <a target="_blank" href="https://haveibeenpwned.com/"><em>have i been pwned</em></a> REST APIs to figure out if the email address (username) is listed in a data breach.</p>
</li>
<li><p><a target="_blank" href="https://haveibeenpwned.com/"><em>have i been pwned</em></a> response will give me a list of data breaches for a given email address.</p>
</li>
<li><p>I will pick the most recent breach.</p>
</li>
<li><p>Then I check to see if the user has changed their password after the most recent data breach.</p>
</li>
<li><p>If so user can login without extra authentication.</p>
</li>
<li><p>If <strong>Not</strong>, the user needs to complete an MFA challenge and they receive an email to change(reset) their password.</p>
</li>
</ul>
<p>This flow chart shows how the new login flow looks like this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642943111886/Urm0gWCVB.png" alt="d2.png" /></p>
<blockquote>
<p>Note: Users signing in with <a target="_blank" href="https://auth0.com/docs/authenticate/identity-providers/social-identity-providers">social</a> or <a target="_blank" href="https://auth0.com/docs/authenticate/identity-providers/enterprise-identity-providers">enterprise</a> connections must reset their passwords with the identity provider (such as Google or Facebook).</p>
</blockquote>
<p>In the case of social users, you might want to limit their access to sensitive data in the system or warn them about the breach. So they can take any required actions. (not in the scope of this example)</p>
<p>Examples of other things you can do are:</p>
<ul>
<li>Completely block user access if you are dealing with a high-risk activity. For example, if the leaked account belongs to a company's Finance department staff that can do high valued monetary transactions. In this case, you might take more drastic measures involving in-person account recovery or using <a target="_blank" href="https://en.wikipedia.org/wiki/Know_your_customer">KYC</a> services like <a target="_blank" href="https://onfido.com/">Onfido</a> to make sure of the user identity.</li>
</ul>
<p>For sake of this blog, we will stick with a simpler REST API integration example.</p>
<h3 id="heading-create-your-first-actionhttpsauth0comdocscustomizeactionswrite-your-first-action"><a target="_blank" href="https://auth0.com/docs/customize/actions/write-your-first-action">Create your first Action</a></h3>
<p>To start you need an Auth0 account. If you don’t already have one, follow <a target="_blank" href="https://auth0.com/docs/get-started">these instructions</a> to set up one.</p>
<p>After you log in to your Auth0 account click on the <strong>Actions &gt; Flows &gt; Login</strong> to create the login Action.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642591481018/ksP1lyD4T.png" alt="Screen Shot 2022-01-15 at 10.34.41 am 1.png" /></p>
<p>This creates a <a target="_blank" href="https://auth0.com/docs/customize/actions/triggers/post-login"><code>post-login</code></a> trigger that runs as part of the Login flow. It is executed after a user logs in and when a Refresh Token is requested.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642412041556/VtBZmeuZF.png?auto=compress,format&amp;format=webp" alt="flow.png" /></p>
<p>Click on the + button in Add Action and select Build Custom. Call it <strong><em>have I been pwned</em></strong> and leave the rest as is.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642626463034/_3yboMSji.png" alt="Screen Shot 2022-01-20 at 7.58.59 am.png" /></p>
<p>After Creation, you come to the edit screen. Here you can write the code for your Action, test it and deploy it.</p>
<p>Next, we write the logic to call <a target="_blank" href="https://haveibeenpwned.com/"><strong><em>have i been pwned</em></strong></a> APIs to check if the user email address has been involved in a data breach.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642917129958/IVSiFHGyc.png" alt="Screen Shot 2022-01-20 at 8.11.10 am.png" /></p>
<p>We will use the <strong><em>onExecutePostLogin</em></strong> function which is a handler that will be called during the execution of a <a target="_blank" href="https://auth0.com/docs/customize/actions/triggers/post-login">PostLogin flow</a>.</p>
<p><strong><em>onExecutePostLogin</em></strong> function recieves two parameters, <strong><em>api</em></strong> (of type <a target="_blank" href="https://auth0.com/docs/customize/actions/triggers/post-login/api-object">PostLoginApi</a>) and <strong><em>event</em></strong> (of type <a target="_blank" href="https://auth0.com/docs/customize/actions/triggers/post-login/event-object">Event</a>)</p>
<p>For this example, we will use the <code>event.user</code> to find the user's email address. <code>event.user</code> contains information on the user on whose behalf the current transaction was initiated, like <code>email</code>, <code>identities</code> and more.</p>
<p>We also use the <code>event.client</code> to get the client id. Knowing the client id we can send a reset password email to the user.</p>
<p>You can read more on the full capabilities of the <code>api</code> and <code>event</code> in the <a target="_blank" href="https://auth0.com/docs/customize/actions/triggers/post-login">Auth0 documentation</a>.</p>
<p>We will use two APIs from <a target="_blank" href="https://haveibeenpwned.com/"><strong><em>have i been pwned</em></strong></a>:</p>
<ul>
<li><p>Get the list of data breaches for an email address. This will return an array of breach names that are sorted by date already. So the first item on the list is the most recent breach.</p>
</li>
<li><p>Get the details of a particular breach. We will use the name of the first breach from the previous list to get the details of it. This includes the <code>BreachDate</code>.</p>
</li>
</ul>
<p>You can read about the capabilities of <strong><em>have i been pwned</em></strong> APIs <a target="_blank" href="https://haveibeenpwned.com/API/v3">here</a></p>
<h3 id="heading-code-for-login-action">Code for Login Action</h3>
<p>%[https://gist.github.com/pazel-io/27ba1ab177c14c172f5b2b9f4232a50e]</p>
<h3 id="heading-test-your-action">Test your action</h3>
<p>On the left side of the Actions editor, you have 3 more options. The one that looks like a play button is Test. If you click on it give you an example login payload and once you hit run it will simulate a call to the action with the same body that you would get on a Login / Post Login flow.</p>
<p>You can edit the payload as you like. For example, I changed the email address to <code>account-exists@hibp-integration-tests.com</code>.</p>
<p>This is a test email address provided in <a target="_blank" href="https://haveibeenpwned.com/"><strong><em>have i been pwned</em></strong></a> API documentation for the scenario that the user has data breaches.</p>
<p>After you run the action you can see the results in the bottom right panel.</p>
<p>Here is an example from my test.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642942038583/ZuBkAfZdE.png" alt="Screen Shot 2022-01-23 at 11.46.26 pm.png" /></p>
<p>You can use <code>console.log</code> to see the result of each step in your function call.</p>
<p>Actions editor saves your code automatically and once you are happy you can click the deploy button.</p>
<p>Now when a user tries to log in they will go through our login Action and need to successfully get through our enhanced Risk-based Authentication before they can successfully log in.</p>
<h3 id="heading-other-features-of-actions">Other features of Actions</h3>
<p>Actions have more to offer. You get a secret manager, a package manager (for npm packages) and the version history of the code built-in.</p>
<p>This makes up for a better developer experience.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642940207964/g1lTWcbl1.png" alt="Screen Shot 2022-01-23 at 11.14.11 pm.png" /></p>
<h3 id="heading-actions-limitations">Actions limitations</h3>
<p>Actions give you an excellent way to customise and extend Auth0 capabilities but there are some limitations. For example, each execution of a flow must complete in 10 seconds or less or the processing will end in an error.</p>
<p>Some of these limitations are in place to ensure that the user experience stays smooth as you add more code and capabilities.</p>
<p>Make sure to check these <a target="_blank" href="https://auth0.com/docs/customize/actions/limitations">limitations</a> before you start.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Auth0 Actions provide an easy way for development teams to implement authentication and authorisation based on their unique needs and open the door to many possibilities.</p>
<p>We only scratched the surface with this Login Action example. Actions offer more hooks and there are plenty of examples for each. The same goes for our risk calculation function. For example, we can make use of the type of breach (e.g: email, password, ...) and more data that is available in <a target="_blank" href="https://haveibeenpwned.com/API/v3"><strong><em>have i been pwned</em></strong></a> response to have a more meaningful risk calculation.</p>
<p>If this looks like something you have been looking for, I encourage you to read the Actions <a target="_blank" href="https://auth0.com/docs/customize/actions">documentation</a> to learn more about the capabilities of different Actions and find more examples.</p>
<p>Thanks for reading this article. Please reach out here or on <a target="_blank" href="https://twitter.com/_pazel">Twitter</a> if you have any feedback or questions.</p>
<h3 id="heading-references">References</h3>
<ul>
<li><p><a target="_blank" href="https://auth0.com/docs/customize/actions">Auth0 Action docs</a></p>
</li>
<li><p><a target="_blank" href="https://haveibeenpwned.com/API/v3">have i been pwned</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[The Art of Programming: Why Learning a Programming Language Transcends Trends]]></title><description><![CDATA[Introduction:
In the dynamic realm of technology, the allure of the next shiny programming language can be irresistible. However, true mastery in the field of software development extends beyond fleeting trends. Learning a programming language is aki...]]></description><link>https://pazel.dev/the-art-of-programming-why-learning-a-programming-language-transcends-trends</link><guid isPermaLink="true">https://pazel.dev/the-art-of-programming-why-learning-a-programming-language-transcends-trends</guid><category><![CDATA[programming languages]]></category><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Sun, 03 Apr 2022 14:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/XWDMmk-yW7Q/upload/99314e89ead4a28bd07d058d79bf34ac.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-introduction">Introduction:</h3>
<p>In the dynamic realm of technology, the allure of the next shiny programming language can be irresistible. However, true mastery in the field of software development extends beyond fleeting trends. Learning a programming language is akin to expanding one's vocabulary—it's about acquiring a new lens through which to view and solve problems. In this blog post, we'll explore the profound benefits of learning multiple programming languages, emphasising that it's not just about keeping up with the latest fad, but about embracing diverse perspectives and enhancing problem-solving skills.</p>
<p><strong>Diverse Problem-Solving Approaches:</strong></p>
<p>Each programming language is designed with a unique set of principles and features, shaping the way developers approach problem-solving. For instance, the elegance of Python's syntax contrasts with the precision demanded by languages like C++. Learning various languages exposes developers to different paradigms—procedural, object-oriented, functional—and equips them with a versatile toolkit to tackle diverse challenges.</p>
<p><strong>Expanding Cognitive Horizons:</strong></p>
<p>Much like learning a new language broadens your cultural understanding, learning a programming language expands your cognitive horizons. Each language embodies a distinct philosophy and methodology, influencing the way developers think about software design, data structures, and algorithms. The cognitive diversity gained through exposure to multiple languages enriches problem-solving skills and fosters a more holistic understanding of software development.</p>
<p><strong>The Colour Categorisation Analogy:</strong></p>
<p>Consider the analogy of color categorisation in language. Just as languages influence how we perceive and categorise colours, programming languages influence how we conceptualize and structure code. Learning a new language introduces developers to different ways of expressing ideas, leading to a more nuanced and flexible mental model for approaching programming challenges.</p>
<p><strong>Adaptability in a Rapidly Changing Landscape:</strong></p>
<p>The tech landscape is in a constant state of evolution, with new languages and frameworks emerging regularly. While it may be tempting to chase the latest trends, the ability to adapt and learn quickly is a valuable skill in itself. Developers who have experience with diverse languages are better equipped to navigate this ever-changing terrain, as they have a strong foundation in fundamental concepts that transcends the specifics of any one language.</p>
<p><strong>Professional Growth and Versatility:</strong></p>
<p>From web development to machine learning, different domains demand different tools. Learning a variety of programming languages enhances a developer's versatility, making them well-suited for a range of projects and roles. It also opens doors to new opportunities, as employers often value candidates who bring a diverse skill set to the table.</p>
<h3 id="heading-conclusion">Conclusion:</h3>
<p>In the world of programming, the journey of learning languages is not just about staying on trend; it's about cultivating a rich and adaptable mind. Like the expansion of colour categories in language, embracing diverse programming languages allows developers to perceive and solve problems in ways they might never have imagined. So, let's celebrate the art of programming for what it truly is—a journey of continuous learning, exploration, and the mastery of the ever-evolving languages that shape our digital landscape.</p>
]]></content:encoded></item><item><title><![CDATA[How to capture and verify user’s phone number using Auth0 Actions]]></title><description><![CDATA[For various scenarios, a business might need to record & verify a user’s phone number before they can provide a service.
This example demonstrates how you can capture and verify the user’s phone number as part of Sign up (First login) using Auth0 Act...]]></description><link>https://pazel.dev/how-to-capture-and-verify-users-phone-number-using-auth0-actions</link><guid isPermaLink="true">https://pazel.dev/how-to-capture-and-verify-users-phone-number-using-auth0-actions</guid><category><![CDATA[Auth0]]></category><category><![CDATA[authentication]]></category><category><![CDATA[twilio]]></category><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Mon, 17 Jan 2022 11:15:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1642558154479/60ILUUfhV.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>For various scenarios, a business might need to record &amp; verify a user’s phone number before they can provide a service.</p>
<p>This example demonstrates how you can capture and verify the user’s phone number as part of Sign up (First login) using Auth0 Actions.</p>
<blockquote>
<p>Auth0 Actions are secure, tenant-specific, versioned functions written in Node.js that execute at certain points during the Auth0 runtime.
Actions are used to customize and extend Auth0’s capabilities with custom logic.
https://auth0.com/docs/customize/actions</p>
</blockquote>
<p>Here is a diagram showing how the solution works.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642411094188/AypC6rIgN.png" alt="image.png" /></p>
<h3 id="heading-disclaimer">Disclaimer</h3>
<p>If you are looking for  <a target="_blank" href="https://auth0.com/docs/secure/multi-factor-authentication/multi-factor-authentication-factors/configure-sms-voice-notifications-mfa">Multi-Factor Authentication using SMS</a> , it’s supported out of the box with Auth0.
This solution is just a suggestion to get around the mobile verification issue.
I found some users on the Auth0 forum asking for this feature so I decided to see if I can use Auth0 Actions to provide a solution.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642411126265/saAYyjFFd.png" alt="image.png" />
 <a target="_blank" href="https://community.auth0.com/t/mobile-verification/65206">https://community.auth0.com/t/mobile-verification/65206</a></p>
<h2 id="heading-the-solution-brekdown">The solution brekdown</h2>
<p>We need the following pieces to make this flow work:</p>
<ul>
<li><p>A Twilio account. Twillio provides the APIs that we need to send the verification SMS and also to verify the code after the user submits it.</p>
</li>
<li><p>Custom signup form with an extra field to record the user’s phone number.</p>
</li>
<li><p>Auth0 Action that will execute on login and make use of the recorded phone number to send a verification code to the user’s device using SMS.
This Action is also responsible for the redirect to the code verification form.</p>
</li>
<li><p>A simple web application that will host the form to get the verification code from the user and redirect the user along with the code to Auth0 to finish the login.</p>
</li>
</ul>
<h3 id="heading-1-create-a-twilio-account-and-the-verification-service">1- Create a Twilio account and the verification service</h3>
<p>To create an account go to  <a target="_blank" href="https://www.twilio.com/try-twilio">https://www.twilio.com/try-twilio</a> and create a free account.</p>
<p>After login, click on the verify from the side menu. This is where we can add a verification service. This service will help us to send an SMS to the user phone number and use the code in that SMS for verifying the number.</p>
<p><strong>Click Verify -&gt; Try it out</strong> and create a new service.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642411330514/TpBUkfozQ.png" alt="image.png" /></p>
<p>In the second and third steps, you get a chance to test your newly created service in the console. This helps you to verify the URL you need to call and the parameters you need to pass for the payload.
After you have tested your new service and you are happy that it all works, we are done with the Twillio setup.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642411354668/zIkTC4HKB.png" alt="image.png" /></p>
<p>If you need to see your service id at any later stage you can come back to verify and it will be listed under services.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642411374492/yExTEdNCX.png" alt="image.png" /></p>
<h3 id="heading-2-create-a-signup-form-with-an-extra-field-to-record-phone-number">2- Create a signup form with an extra field to record phone number</h3>
<p>There are multiple ways you can customise your login/signup form.
In this example, I am using the Auth0 lock and passing the configuration for extra fields.</p>
<p>To learn more about other ways you can customise the signup experience check  <a target="_blank" href="https://auth0.com/docs/libraries/custom-signup">this page</a>.</p>
<p>This is the configuration you can pass to the Auth0 lock to add an extra field.</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="c0f9167afcebe3a573c2571881cbeada"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/pazel-io/c0f9167afcebe3a573c2571881cbeada" class="embed-card">https://gist.github.com/pazel-io/c0f9167afcebe3a573c2571881cbeada</a></div><p>And You will get a signup form like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642411585321/1j-XHdAtj.png" alt="image.png" /></p>
<p>Now after the user signs up, the phone number will be added to the user profile under the <code>user_metadata</code> .
At this stage, we have not verified the phone number yet.</p>
<h3 id="heading-3-create-a-login-action-to-verify-the-phone-number">3- Create a Login Action to verify the phone number</h3>
<p>Next step we need a login Action to read the phone number from the user profile and send a verification SMS to the user’s phone number.</p>
<p>Among the Auth0 Action triggers Login is the only one that supports redirect which is what we need for this flow. Since the phone number verification happens only once users will only go through it the first time they log in.</p>
<p>For the subsequent logins, the Action will check if the phone number exists and is verified for a particular user and if so it will just ignore the rest.</p>
<p>To start you need an Auth0 account. If you don’t already have one, follow  <a target="_blank" href="https://auth0.com/docs/get-started">these instructions</a> for setting up one.</p>
<p>To create the login Action Click on the <strong>Actions &gt; Flows &gt; Login</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642412016412/WjPTNfihR.png" alt="image.png" /></p>
<p>This creates a <code>post-login</code> trigger that runs as part of the Login flow.
It is executed after a user logs in and when a Refresh Token is requested.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642412041556/VtBZmeuZF.png" alt="image.png" /></p>
<p>Click on the <code>+</code> button in Add Action and select Build Custom. Call it <strong>Verify phone number</strong> and leave the rest as is.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642412070495/3CrkNHQrA.png" alt="image.png" /></p>
<p>After Creation, you come to the edit screen.
Here you can edit the code and write the logic to call Twilio APIs to send a verification code and verify it.</p>
<p>To do this we need to write two separate functions.</p>
<ul>
<li><p>onExecutePostLogin — Handler that will be called during the execution of a PostLogin flow.</p>
</li>
<li><p>onContinuePostLogin — Handler that will be invoked when this Action is resuming after an external redirect.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642412116110/bjZWRpQzj.png" alt="image.png" /></p>
<p>1- In <code>onExecutePostLogin</code> function Code is simply reading the phone number from the user <code>user_metadata</code> and sending an SMS to the phone number using Twilio REST API.</p>
<p>After sending the SMS successfully, this function will redirect the user to the webform to enter the verification code we just sent them.
This webform is simply a web application that you need to create and host yourself.</p>
<p>It doesn't need to be anything fancy, as long as it can get the verification code and redirect the user back to Auth0.</p>
<p>2- The <code>onContinuePostLogin</code> comes into play after the user is redirected back to Auth0. At this point, you will have the verification code sent to the function from the webform using a redirect.</p>
<p>So we can use the code and call another REST API from Twilio to verify it.
If all goes well and you get a successful response from Twillio the phone number is verified and we will add this information to the user profile but this time under the <code>app_metadata</code> .</p>
<p>Tip: <code>user_metadata</code> can be edited by the logged-in user but the user cannot edit app_metadata.</p>
<p>Here is the code for the Action.</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="dc62d784605d97a88969c6d2cc090f87"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/pazel-io/dc62d784605d97a88969c6d2cc090f87" class="embed-card">https://gist.github.com/pazel-io/dc62d784605d97a88969c6d2cc090f87</a></div><h3 id="heading-4-webform-to-get-verification-code-andamp-perform-redirect">4- Webform to get verification code &amp; perform redirect</h3>
<p>The code verification form is a basic HTML page with a small bit of JavaScript to handle the form submission. (based on  <a target="_blank" href="https://github.com/dylanswartz/actions-consent-form-example">Dylan Swartz example</a> for consent form)</p>
<p>In this example, we will use  <a target="_blank" href="https://vercel.com/">Vercel</a> to host the form but you use any hosting provider.
If you haven’t done so already, sign up for a Vercel account and install the  <a target="_blank" href="https://github.com/vercel/vercel/tree/main/packages/cli">Vercel CLI</a> with <code>npm i -g vercel</code>.</p>
<p>While you’re in the same directory as the <code>index.html</code>, simply run the following command to spin it up in Vercel:</p>
<pre><code>$ vercel
</code></pre><p>Press enter for each prompt the <code>vercel</code> command gives you to select the defaults. The output of this command will contain the URL of your newly hosted code verification form.</p>
<ol>
<li>In your Action’s code be sure to delete the existing VERIFY_FORM_URL and replace it with the value that is the URL that was output in the previous step.</li>
</ol>
<ol>
<li>If you want to make changes to the code verification form, you can upload a new version simply by running the same vercel command you did before.</li>
</ol>
<p>Here is the code for this web application.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/pazel-io/auth0-twilio-phone-verification">https://github.com/pazel-io/auth0-twilio-phone-verification</a></div>
<p>There you have an end-to-end solution to verify phone numbers as part of your Auth0 login and account creation.</p>
<h2 id="heading-final-notes">Final notes</h2>
<p>There are more things to consider to make this a production-ready solution.</p>
<ul>
<li><p><strong>Error handling</strong>
You need to consider and handle different errors that can happen.
The example Action code makes use of <code>api.access.deny()</code> to reject the login in case an error happens.</p>
</li>
<li><p><strong>Setting up different environments(tenants) for different stages</strong>
When you set up Auth0 tenant or Twilio service you need to separate your dev and prod environments.</p>
</li>
<li><p><strong>Keeping your Secret(API) keys safe</strong>
All the secrets used in this solution only exist on the server-side of the code (action.js and verify.js) which makes it easier to keep them safe.
Make sure you do not expose the sensitive keys in your client-side code or commit them to your repo.</p>
</li>
</ul>
<p>Actions have secret management built-in to keep your secrets safe and provide a convenient way to access them in your action code.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642455071716/_D01AYNhN.png" alt="Screen Shot 2022-01-18 at 8.26.27 am.png" /></p>
<p>and you can access them like this</p>
<pre><code><span class="hljs-selector-tag">event</span><span class="hljs-selector-class">.secrets</span><span class="hljs-selector-class">.SESSION_TOKEN_SECRET</span>
</code></pre><ul>
<li><p><strong>Trusted Callback URL</strong> (quoting from  <a target="_blank" href="https://github.com/dylanswartz/actions-consent-form-example">Dylan Swartz example</a> )
Our sample Action and consent form makes one security compromise for the sake of convenience: the rule passes the Auth0 domain (i.e. <code>your-tenant.auth0.com</code>) to the form website and the form uses that to construct a callback URL (i.e. <code>https://your-tenant.auth0.com/continue</code>) for returning to the rule. This is essentially an open redirect and should not be used in production scenarios.
You can lock this down by configuring your form website implementation to only return to a specific URL (i.e. just your Auth0 tenant) instead of one that’s generated from a query param. You can then simplify the rule too so it no longer passes the Auth0 domain.</p>
</li>
<li><p><strong>Data Integrity</strong> (quoting from  <a target="_blank" href="https://github.com/dylanswartz/actions-consent-form-example">Dylan Swartz example</a> )
As stated, this is a very basic example of using an Action Redirect to invoke a consent form. That said, at Auth0 we take security very seriously. The <code>confirm</code> field (which has the value of <code>yes</code>) that is being passed back to auth0 as a signed token using a shared <code>SESSION_TOKEN_SECRET</code>.
In production scenarios where you need assurances of the integrity of the data being returned by the external website (in this case in our hosted code verification form). For example, if you want to be sure that the data truly came from a trusted source, then it should be signed. If the data is sensitive, then it should be encrypted. A good mechanism for doing this is to use a  <a target="_blank" href="http://jwt.io/">JWT</a>  (JSON Web Token). You can build a JWT with claims (that you can optionally encrypt) and then sign it with either a secret shared with your Auth0 Action or with a private key, whose public key is known by the action. The Action can then verify that the claims are legit and decrypt them, if necessary.</p>
</li>
<li><p><strong>Social logins</strong>
This solution needs some adjusting if you want to offer social logins/signup.
In the case of social login, users will not use our signup form, so we need to capture the phone number through a separate form in our application.</p>
</li>
</ul>
<p>Thanks for reading this article.
Please reach out here or on  <a target="_blank" href="https://twitter.com/_pazel">Twitter</a>  if you have any feedback or questions.</p>
<p>Here is the link to my example code repository:
 <a target="_blank" href="https://github.com/pazel-io/auth0-twilio-phone-verification">https://github.com/pazel-io/auth0-twilio-phone-verification</a> </p>
<h2 id="heading-references">References</h2>
<ul>
<li><a target="_blank" href="https://auth0.com/docs/customize/actions">Auth0 Action docs</a> </li>
<li><a target="_blank" href="https://auth0.com/docs/customize/actions/triggers/post-login/redirect-with-actions">Redirect with Actions</a> </li>
<li><a target="_blank" href="https://github.com/dylanswartz/actions-consent-form-example">Dylan Swartz example for consent form</a> </li>
<li><a target="_blank" href="https://www.twilio.com/docs/verify">Twilio verify service</a> </li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to keep your secrets from your source code in an Angular project ?! 🤫]]></title><description><![CDATA[Let's face it, we all have secrets, but that's not what I want to talk about.
This article shows you a technique to easily use Angular's environment.ts file to access sensitive data without revealing your secrets.
What is a secret?
Secret refers to a...]]></description><link>https://pazel.dev/how-to-keep-your-secrets-from-your-source-code-in-an-angular-project</link><guid isPermaLink="true">https://pazel.dev/how-to-keep-your-secrets-from-your-source-code-in-an-angular-project</guid><category><![CDATA[ci-cd]]></category><category><![CDATA[Angular]]></category><category><![CDATA[Build tool]]></category><category><![CDATA[Git]]></category><category><![CDATA[github-actions]]></category><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Sun, 26 Sep 2021 09:58:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1632650185465/Z_SzV4Rvm.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Let's face it, we all have secrets, but that's not what I want to talk about.</p>
<p>This article shows you a technique to easily use Angular's <code>environment.ts</code> file to access sensitive data without revealing your secrets.</p>
<h3 id="what-is-a-secret">What is a secret?</h3>
<p>Secret refers to any sensitive values that are not supposed to be pushed as part of the source code. This can be things like API keys, OAuth tokens, certificates and passwords.</p>
<p>These secrets are often required to integrate with other providers or services as part of the build or deployments.</p>
<p>Secrets can be different based on deployment environments as well.</p>
<p>For example, you might use a test account for development with a different API key to your production.</p>
<h3 id="why-should-you-care-about-secrets">Why should you care about secrets?</h3>
<p>Many projects utilise cloud-based Git and CI/CD tools to manage the source code, build and deploy.
So keeping secrets from leaking into your source code is essential.</p>
<p>You typically deal with two types of secrets in client-side projects.</p>
<h4 id="1-secrets-that-your-client-side-app-needs-to-use-when-talking-to-a-provider">1. Secrets that your client-side app needs to use when talking to a provider.</h4>
<p>These secrets are still visible in the JS bundle or API calls.
These are secrets like your Google API key that needs to be sent in an HTTP request to Google, for example.</p>
<p>For these types of secret usually, you need accompanying security measures. For example, Google lets you limit an API key to a domain or limit it to specific Google services.</p>
<p>Even for this type, it is a good practice not to hardcode in the source.
hardcoding them makes the secret management based on the environment difficult.</p>
<p>Let's say you have a Test and Production environment which use different Google API keys.</p>
<p>If secret is part of the source code you need to remember to change the code each time you want to change it from Test API key to Production one.</p>
<p>In the same way, updating your secret to rotate it can be more challenging. </p>
<p>Now let's add the fact that source code will spread across branches and devices.</p>
<p>I guess you get the picture. This is important and for that reason there are best practices and tools to help you.</p>
<p>For example, Google has <a target="_blank" href="https://support.google.com/googleapi/answer/6310037?hl=en">"Best practices for securely using API keys."</a>
Which suggest not to store API keys in source code.</p>
<p>Or Github will send you a warning email if you push your Google API key.
They have this <a target="_blank" href="https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning">Automatic secret scanning</a> going on.</p>
<h4 id="2-secrets-that-are-just-required-as-part-of-the-build-but-not-after-that">2. Secrets that are just required as part of the build but not after that.</h4>
<p>For example, if my build process can query my content repository(headless CMS) to pre-render some pages to generate static pages.</p>
<p>In that case, I do not need the content repository's API key after the build is done. I will only take the results (static pages) to deploy them.
So I can genuinely secure my key by not putting in the source code.</p>
<p>If you are not convinced, read <a target="_blank" href="https://blog.gitguardian.com/secrets-credentials-api-git/">Why secrets in git are such a problem - Secrets in source code</a>.</p>
<h3 id="utilise-angular-environmentts-file-to-access-secrets">Utilise Angular <code>environment.ts</code> file to access secrets</h3>
<p>The Angular official way to manage the environment configurations is to use the provided <a target="_blank" href="https://angular.io/guide/build">environment.ts file</a>.</p>
<p>You can create a file for each environment.</p>
<pre><code>└──myProject<span class="hljs-regexp">/src/environments/</span>
                   └──environment.ts
                   └──environment.prod.ts
                   └──environment.stage.ts
</code></pre><p>and provide the configs of each environment like this</p>
<pre><code><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> environment = {
  production: <span class="hljs-keyword">true</span>,
  apiUrl: <span class="hljs-string">'http://my-prod-url'</span>
};
</code></pre><p>You can easily access this configuration in your code.</p>
<pre><code>  <span class="hljs-keyword">import</span> { environment } from <span class="hljs-string">'../environments/environment'</span>;

  <span class="hljs-keyword">public</span> getBooks() {
    <span class="hljs-keyword">const</span> url = `${environment.apiUrl}/books`;
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.http.<span class="hljs-keyword">get</span>&lt;Book&gt;(url);
  }
</code></pre><p>To get environment specific configs, you can use the env name in the build.</p>
<p><code>ng build --configuration=stage</code> or use angular.json to define the same thing based on the environment.</p>
<pre><code><span class="hljs-string">"serve"</span>: {
  <span class="hljs-string">"builder"</span>: <span class="hljs-string">"@angular-devkit/build-angular:dev-server"</span>,
  <span class="hljs-string">"options"</span>: {
    <span class="hljs-string">"browserTarget"</span>: <span class="hljs-string">"your-project-name:build"</span>
  },
  <span class="hljs-string">"configurations"</span>: {
    <span class="hljs-string">"production"</span>: {
      <span class="hljs-string">"browserTarget"</span>: <span class="hljs-string">"your-project-name:build:production"</span>
    },
    <span class="hljs-string">"staging"</span>: {
      <span class="hljs-string">"browserTarget"</span>: <span class="hljs-string">"your-project-name:build:staging"</span>
    }
  }
},
</code></pre><p>These configs are pushed to your source code to support the build. So anyone who has access to source code can see these configs in plain text.</p>
<p>This technique is completely fine for typical configs like the apiUrl but not good enough for secrets.</p>
<h3 id="solution">Solution</h3>
<p>We can provide secrets through the <code>environemnt.ts</code> files without pushing it to source code using the <a target="_blank" href="https://en.wikipedia.org/wiki/Environment_variable#:~:text=An%20environment%20variable%20is%20a,in%20which%20a%20process%20runs">Environment variable</a>.</p>
<p>An environment variable is a dynamic-named value that can affect how running processes will behave on a computer. They are part of the environment in which a process runs. For example, a running process can query the value of the TEMP environment variable to discover a suitable location to store temporary files, or the HOME or USERPROFILE variable to find the directory structure owned by the user running the process.</p>
<p>These <code>Environment variables</code> are set up on the build machine which your Angular build runs.</p>
<p>There are different ways to set up <code>Environment variables</code>, which can differ based on the context.</p>
<p>We don't want to set up these variables manually, so we use a <a target="_blank" href="https://www.freecodecamp.org/news/nodejs-custom-env-files-in-your-apps-fa7b3e67abe1/">.env file</a>.
You can easily set multiple environment variables and values in one go.</p>
<p>Later you can use the <code>.env</code> file to read these variables based on the environment. The content of .env looks like this.</p>
<pre><code class="lang-dotenv">GOOGLE_API_KEY=34232423423423234233fsajgsaagdda
AUTH0_CLIENT_ID=54235nfgde24gbdf235432
PASSWORD=adjkvknh29827;nbv_
</code></pre>
<p>So the idea is that you load the <code>.env</code> file related to an environment (Test, Production) and somehow provide them to the Angular <code>environment.ts</code> file.</p>
<h4 id="how-is-this-approach-more-secure">How is this approach more secure?</h4>
<p>This approach is more secure because simply the <code>.env</code> file, which contains the sensitive values, only exists on your build machine (private server).</p>
<p>So only the person who has access to the build server can read the file. Sensitive values are no longer part of the source code.</p>
<h4 id="what-about-the-cloud-based-cicd">What about the cloud-based CI/CD?</h4>
<p>All the cloud-based CI/CDs already provide a way to define and manage <code>Environment variables</code>.</p>
<p>Once you set them up, the <code>Environment variables</code> will be available at runtime when the build is running, and we can access them the same way as if it's our server. Plus, the <code>Environment variables</code> will be secure since they are only available through the cloud-based CI/CD admin dashboard.</p>
<p>Here are some examples from some of the popular cloud-based CI/CDs.</p>
<p><a target="_blank" href="https://vercel.com/docs/concepts/projects/environment-variables">Vercel</a>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1632638260063/6Y5tAuPT7.png" alt="Screen Shot 2021-09-26 at 4.20.35 pm.png" /></p>
<p><a target="_blank" href="https://docs.github.com/en/actions/learn-github-actions/environment-variables">Github Actions</a>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1632638695871/Gc-JvBZYr.png" alt="Screen Shot 2021-09-26 at 4.43.44 pm.png" /></p>
<p><a target="_blank" href="https://docs.aws.amazon.com/amplify/latest/userguide/environment-variables.html">AWS Amplify</a>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1632638347934/jeuM_Quu7.png" alt="amplify.png" /></p>
<h3 id="how-to-access-the-environment-variable-in-angular-environmentts-files">How to access the Environment variable in Angular environment.ts files?</h3>
<p>Angular build already utilises Node.js at runtime to do the build, and we can add an npm script to generate the <code>environement.ts</code> as part of the build.</p>
<p>This script will read the environment variables and generate the <code>environement.ts</code> file.
This script is simply JavaScript, so write any logic you need to generate the output.</p>
<p>Here is the  code for the script.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> setEnv = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>);
  <span class="hljs-keyword">const</span> writeFile = fs.writeFile;
<span class="hljs-comment">// Configure Angular `environment.ts` file path</span>
  <span class="hljs-keyword">const</span> targetPath = <span class="hljs-string">'./src/environments/environment.ts'</span>;
<span class="hljs-comment">// Load node modules</span>
  <span class="hljs-keyword">const</span> colors = <span class="hljs-built_in">require</span>(<span class="hljs-string">'colors'</span>);
  <span class="hljs-keyword">const</span> appVersion = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../../package.json'</span>).version;
  <span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>).config({
    path: <span class="hljs-string">'src/environments/.env'</span>
  });
<span class="hljs-comment">// `environment.ts` file structure</span>
  <span class="hljs-keyword">const</span> envConfigFile = <span class="hljs-string">`export const environment = {
  googleApiKey: '<span class="hljs-subst">${process.env.GOOGLE_API_KEY}</span>',
  auth0ClientId: '<span class="hljs-subst">${process.env.AUTH0_CLIENT_ID}</span>',
  appVersion: '<span class="hljs-subst">${appVersion}</span>',
  production: true,
};
`</span>;
  <span class="hljs-built_in">console</span>.log(colors.magenta(<span class="hljs-string">'The file `environment.ts` will be written with the following content: \n'</span>));
  writeFile(targetPath, envConfigFile, <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (err) {
      <span class="hljs-built_in">console</span>.error(err);
      <span class="hljs-keyword">throw</span> err;
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.log(colors.magenta(<span class="hljs-string">`Angular environment.ts file generated correctly at <span class="hljs-subst">${targetPath}</span> \n`</span>));
    }
  });
};

setEnv();
</code></pre>
<p>Let's see what is happening here.</p>
<ul>
<li><code>process.env</code> provides access to the Environment variable in Node.js. So any variable that exists on the build machine will be available through it.</li>
<li><code>fs.writeFile</code> let us create a file and write content to it. In this case, to generate the environment.ts file.</li>
<li>As a bonus, I am reading the <code>package.json</code> so I can get its version. This way, we can have a version number to display in our UI. 
This technique is convenient when for providing versioning as part of a release cycle.</li>
<li><code>targetPath</code> is pointing to where the target file should exist.</li>
<li><code>envConfigFile</code> constant is a template literal that provides the template and content of the <code>environment.ts</code> file. 
You can specify the variables you want to have in your <code>environment.ts</code> here.</li>
<li><code>console.log</code> are there to make it easy to see the output of the file generation for debugging. 
Make sure not to log sensitive data here. Logs usually stay around long after the build is complete.</li>
<li><code>dotenv</code> package can load a <code>.env</code> file so you can access the variable you have defined in that file.
This trick helps manage a private build server or even building on your local development machine. 
The script won't fail if <code>dotenv</code> does not find the file in the specified location. So you can leave it there to support both local machine builds and cloud-based CI/CDs.</li>
</ul>
<h3 id="automate-it">Automate it</h3>
<p>To glue things together, we need to do two more things.</p>
<ul>
<li>Firstly, we need to add an npm script that can run our newly added code.
Here the new <code>config</code> npm script will run our code. We also update the npm script for <code>build</code> to run the <code>config</code> before build.<pre><code class="lang-json">{
<span class="hljs-attr">"name"</span>: <span class="hljs-string">"ionic-angular-cesium-3d-map"</span>,
<span class="hljs-attr">"version"</span>: <span class="hljs-string">"0.0.1"</span>,
<span class="hljs-attr">"author"</span>: <span class="hljs-string">"Ionic Framework"</span>,
<span class="hljs-attr">"homepage"</span>: <span class="hljs-string">"https://ionicframework.com/"</span>,
<span class="hljs-attr">"scripts"</span>: {
  <span class="hljs-attr">"ng"</span>: <span class="hljs-string">"ng"</span>,
  <span class="hljs-attr">"start"</span>: <span class="hljs-string">"ng serve"</span>,
  <span class="hljs-attr">"build"</span>: <span class="hljs-string">"npm run config &amp;&amp; ng build"</span>,
  <span class="hljs-attr">"config"</span>: <span class="hljs-string">"ts-node src/environments/set-env.ts"</span>,
  <span class="hljs-attr">"test"</span>: <span class="hljs-string">"ng test"</span>,
  <span class="hljs-attr">"lint"</span>: <span class="hljs-string">"ng lint"</span>,
  <span class="hljs-attr">"e2e"</span>: <span class="hljs-string">"ng e2e"</span>
}
}
</code></pre>
</li>
<li>Last but not least, we need to ignore the generated <code>environment.ts</code> file and the <code>.env</code> file from the source code.
Update your <code>.gitignore</code> to include these files.<pre><code class="lang-gitignore">src/environments/environment.ts
src/environments/.env
</code></pre>
</li>
</ul>
<p>After following these steps, when you run the <code>build</code> or <code>config</code> npm scripts, it will generate an <code>environment.ts</code> and place it in the <code>environment</code> folder.
File content will be like this.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> environment = {
    googleApiKey: <span class="hljs-string">'YOUR_ACTUAL_GOOGLE_API_KEY_FROM_ENV_VARIABLES'</span>,
    auth0ClientId: <span class="hljs-string">'YOUR_ACTUAL_AUTH0_CLIENT_ID_FROM_ENV_VARIABLES'</span>,
    appVersion: <span class="hljs-string">'1.0.0'</span>,
    production: <span class="hljs-literal">true</span>,
};
</code></pre>
<p>That's it. Now you access these environment configs in your Angular project.</p>
<h3 id="conclusion">Conclusion</h3>
<p>We learned how we can integrate the operating level <code>Environment variables</code> with Angular <code>environment.ts</code> to get the best of both worlds and secure our secrets.</p>
<p>There are more ways to make keeping and sharing secrets more convenient for secrets that might need to be shared on different build machines. What we read in this article is just the first step.</p>
<p>Thanks for reading. As usual, if you have any questions, please leave me a comment here or DM me on Twitter.</p>
<h3 id="resources">Resources</h3>
<p>if you need an example project that utilises this solution please refer to my ionic &amp; Cesium 3D map example.</p>
<p>This project uses the same technique to hide the <code>cesiumAccessToken</code> which is used in the code from the source code.</p>
<p>This project builds on Vercel and uses the Vercel Environment variable to provide the cesiumAccessToken.</p>
<p>Here is the code repository: <a target="_blank" href="https://github.com/pazel-io/ionic-angular-cesium-3d-map">ionic-angular-cesium-3d-map</a></p>
<h3 id="contact">Contact</h3>
<p>Twitter: _pazel</p>
]]></content:encoded></item><item><title><![CDATA[3D map for your Ionic app]]></title><description><![CDATA[Hi friends 👋
Parham here, with another step-by-step guide.
In this article, we will create an Ionic/Angular app that uses CesiumJS to render a 3D map and render some 3D layers on top of it to show terrain and buildings.
3D maps are excellent 😎 and ...]]></description><link>https://pazel.dev/3d-map-for-your-ionic-app</link><guid isPermaLink="true">https://pazel.dev/3d-map-for-your-ionic-app</guid><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Sat, 25 Sep 2021 04:22:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1632543466268/0Vytecm3t.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi friends 👋</p>
<p>Parham here, with another step-by-step guide.</p>
<p>In this article, we will create an Ionic/Angular app that uses CesiumJS to render a 3D map and render some 3D layers on top of it to show terrain and buildings.</p>
<p>3D maps are excellent 😎 and visualising your data in 3D can take it to the next level. 🤭</p>
<p>What if you can have a 3D map rendered in your browser with just writing one line of code.</p>
<p>That's right, using CesiumJS, you can easily have a 3D map in your app.</p>
<p>A quick preview of what is the final results:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/Y2ItrXnEitc">https://youtu.be/Y2ItrXnEitc</a></div>
<h3 id="what-is-cesiumjs">What is CesiumJS?</h3>
<blockquote>
<p>CesiumJS is an open-source JavaScript library for creating world-class 3D globes and maps with the best possible performance, precision, visual quality, and ease of use.</p>
</blockquote>
<p>To get an idea of CesiumJS Capabilities, check some of their <a target="_blank" href="https://sandcastle.cesium.com">examples</a>.</p>
<p>Here is what you can expect to learn from this tutorial and the difficulty level of each part:</p>
<ol>
<li><h4 id="create-a-blank-app-using-ionic-cli">Create a blank app using Ionic CLI.</h4>
<p>You will learn how to use Ionic CLI to create an Ionic/Angular app. (Difficulty level: simple)</p>
</li>
<li><h4 id="use-cesiumjs-to-render-a-map">Use CesiumJS to render a map</h4>
<p>You will learn how to implement a basic map integration with Ionic, Cesium &amp; Angular. (Difficulty level: Intermediate)</p>
</li>
<li><h4 id="use-cesium-camera">Use Cesium Camera</h4>
<p>You will learn how to use the Cesium Camera flyTo method to fly to a destination. (Difficulty level: Intermediate)</p>
</li>
</ol>
<p>Here is the final demo link
https://ionic-angular-cesium-3d-map.vercel.app/</p>
<p>And here is the Git repo:
https://github.com/pazel-io/ionic-angular-cesium-3d-map</p>
<h2 id="lets-start">Let's start</h2>
<h3 id="1-create-the-base-app-using-ionic-cli">1. Create the base app using Ionic CLI</h3>
<p>If you have not installed Ionic CLI before, please head to https://ionicframework.com/docs/intro/cli and follow the instructions to install the CLI.</p>
<p>Next, let's create an app using CLI by running <code>ionic start ionic-angular-cesium-3d-map</code>. CLI should prompt you to choose the front end tech and more options.</p>
<p>I am going with Angular and the blank starter project.</p>
<p>Ionic CLI asks if you like Capacitor integration, which is not required for this tutorial.
After selecting the options, CLI will download all required npm packages. (this might take few minutes)
You will eventually see some logs indicating that the setup is done.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1632535914864/8dJZoKiemN.png" alt="Screen Shot 2021-09-25 at 12.09.56 pm.png" /></p>
<p>Let's cd to the new project we just created and run <code>ionic serve</code>.
This command will run a local web server and open the app in your default browser. (by default port 8100)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626508059761/ZkYSS_NaF.png" alt="Screen Shot 2021-07-17 at 4.40.38 pm.png" /></p>
<h3 id="2-use-cesiumjs-to-render-a-map">2. Use CesiumJS to render a map</h3>
<p>There are three pieces involved here to make it work.</p>
<ul>
<li>Cesium JavaScript file</li>
<li>Cesium CSS file</li>
<li>Cesium assets file</li>
</ul>
<p>Start by adding the Cesium npm package
<code>npm install --save cesium</code></p>
<h4 id="configure-cesium-in-angular">Configure Cesium in Angular</h4>
<p>Now that we have installed Cesium, we need to include its files (CSS and necessary assets) in our project.
We can use angular.json located at the root directory of the project to add these files.
Open angular.json and add the CSS file in the styles section and the whole build Cesium folder in the assets array.</p>
<pre><code class="lang-json"><span class="hljs-string">"assets"</span>: [
{
<span class="hljs-attr">"glob"</span>: <span class="hljs-string">"**/*"</span>,
<span class="hljs-attr">"input"</span>: <span class="hljs-string">"node_modules/cesium/Build/Cesium"</span>,
<span class="hljs-attr">"output"</span>: <span class="hljs-string">"./assets/cesium"</span>
},
],
<span class="hljs-string">"styles"</span>: [
<span class="hljs-string">"node_modules/cesium/Build/Cesium/Widgets/widgets.css"</span>,
<span class="hljs-string">"styles.css"</span>
]
</code></pre>
<h4 id="display-the-viewer">Display the Viewer</h4>
<p>We will use the home component to host the Cesium viewer.
All Cesium needs is to have a reference to a DOM element that is designated to render the map.</p>
<p>The easiest way to do this in Angular is to use the <code>@ViewChild</code> property decorator that configures a view query.
View queries are set before the <code>ngAfterViewInit</code> callback is called.</p>
<p>This what you need to add to the <code>home.component.html</code></p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"my-cesium"</span> #<span class="hljs-attr">myCesium</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>and this is the code for accessing the <code>myCesium</code> element in <code>home.component.ts</code>.</p>
<pre><code class="lang-typescript">    <span class="hljs-meta">@ViewChild</span>(<span class="hljs-string">'myCesium'</span>)
<span class="hljs-keyword">public</span> myCesium: ElementRef;
</code></pre>
<p>The next thing is to initialise the Cesium viewer.
We use the Angular <code>AfterViewInit</code> hook for this to make sure our ElementRef is initialised first.</p>
<p>Next, you need to pass the DOM reference to the <code>Cesium.Viewer</code> constructor.</p>
<pre><code class="lang-typescript">    <span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> Cesium <span class="hljs-keyword">from</span> <span class="hljs-string">'cesium'</span>;

<span class="hljs-keyword">public</span> ngAfterViewInit(): <span class="hljs-built_in">void</span> {
    <span class="hljs-keyword">const</span> viewer = <span class="hljs-keyword">new</span> Cesium.Viewer(<span class="hljs-built_in">this</span>.myCesium.nativeElement);
}
</code></pre>
<p>Congrats! Now you should see a 3D globe rendered on your home page with some default Cesium controls.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1632530752801/rz4mxu1gs.png" alt="Screen Shot 2021-09-25 at 10.44.52 am.png" /></p>
<p>We can pass some more configurations to customise the Cesium viewer. Check <a target="_blank" href="https://cesium.com/learn/ion-sdk/ref-doc/Viewer.html#.ConstructorOptions">Cesium docs</a> for a full list of options.</p>
<p>To simply my view for mobile I will disable some of these default options. Here is the updated code.</p>
<pre><code class="lang-typescript">    <span class="hljs-keyword">public</span> ngAfterViewInit(): <span class="hljs-built_in">void</span> {
    <span class="hljs-keyword">if</span> (environment.cesiumAccessToken) {
    Cesium.Ion.defaultAccessToken = environment.cesiumAccessToken;
}
<span class="hljs-built_in">this</span>.viewer = <span class="hljs-keyword">new</span> Cesium.Viewer(<span class="hljs-built_in">this</span>.myCesium.nativeElement, {
    <span class="hljs-comment">//Use Cesium World Terrain</span>
    terrainProvider: Cesium.createWorldTerrain(),
    <span class="hljs-comment">//Hide the base layer picker</span>
    baseLayerPicker: <span class="hljs-literal">false</span>,
    <span class="hljs-comment">// homeButton: false,</span>
    geocoder: <span class="hljs-literal">false</span>,
    timeline: <span class="hljs-literal">false</span>,
    animation: <span class="hljs-literal">false</span>,
    fullscreenButton: <span class="hljs-literal">false</span>,
});
}
</code></pre>
<p>Couple of things to notice</p>
<ul>
<li><code>terrainProvider</code> adds terrain data to your globe so you can see these terrains when you look at the surface. Pretty neat.</li>
<li><code>defaultAccessToken</code> remove the message that is displayed when you use the default access token.</li>
</ul>
<p>You can create an account and <a target="_blank" href="https://cesium.com/ion/tokens">get the access token for free</a> for non-commercial use.</p>
<p>Here is the result
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1632531263464/E9PMCp_VF.png" alt="Screen Shot 2021-09-25 at 10.53.41 am.png" /></p>
<h3 id="3-use-cesium-camera">3. Use Cesium Camera</h3>
<p>Next stop, we will add some 3D building data to the globe and fly from the initial location to the buildings.</p>
<p>When we created a Cesium Viewer, we implicitly built a <a target="_blank" href="https://cesium.com/learn/ion-sdk/ref-doc/Scene.html">Cesium.Scene</a>.</p>
<p><code>Cesium.Scene</code> is the container for all 3D graphical objects and state in a Cesium virtual scene, and it has a reference to the <a target="_blank" href="https://cesium.com/learn/ion-sdk/ref-doc/Camera.html">Cesium.Camera</a></p>
<p><code>Cesium.Camera</code> let us to flyTo a location.</p>
<pre><code class="lang-typescript">   <span class="hljs-built_in">this</span>.viewer.scene.camera.flyTo({
    destination: Cesium.Cartesian3.fromDegrees(<span class="hljs-number">-74.019</span>, <span class="hljs-number">40.6912</span>, <span class="hljs-number">750</span>),
    orientation: {
        heading: Cesium.Math.toRadians(<span class="hljs-number">20</span>),
        pitch: Cesium.Math.toRadians(<span class="hljs-number">-20</span>),
    },
})
</code></pre>
<p>In the code above, we are telling Cesium to fly from the current location to the destination.
In this example, the destination is provided in Degrees (longitude, latitude, height). We are also setting the orientation of the camera.</p>
<p>Next, let's add some 3D building data. Cesium already has a built-in layer for this, and you can add your data as well.</p>
<p>The built-in one is called the <code>Cesium OSM Buildings</code> layer, a global base layer with over 350 million buildings derived from OpenStreetMap data. It's served as 3D Tiles, an open standard created by Cesium.</p>
<pre><code class="lang-typescript">   <span class="hljs-built_in">this</span>.viewer.scene.primitives.add(Cesium.createOsmBuildings());
</code></pre>
<p>Next we add an Ionic Fab button to fly us to the destination.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ion-content</span> [<span class="hljs-attr">fullscreen</span>]=<span class="hljs-string">"true"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"my-cesium"</span> #<span class="hljs-attr">myCesium</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ion-fab</span> <span class="hljs-attr">vertical</span>=<span class="hljs-string">"bottom"</span> <span class="hljs-attr">horizontal</span>=<span class="hljs-string">"end"</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"fixed"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">ion-fab-button</span> (<span class="hljs-attr">click</span>)=<span class="hljs-string">"letsGo()"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">ion-icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"airplane"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ion-icon</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">ion-fab-button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ion-fab</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ion-content</span>&gt;</span>
</code></pre>
<p>and here is the code for the component</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">public</span> letsGo() {
    <span class="hljs-keyword">const</span> slow$ = <span class="hljs-keyword">of</span>(<span class="hljs-built_in">this</span>.viewer);
    slow$
        .pipe(
            take(<span class="hljs-number">1</span>),
            tap(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">this</span>.viewer.scene.camera.flyTo({
                destination: Cesium.Cartesian3.fromDegrees(<span class="hljs-number">-74.019</span>, <span class="hljs-number">40.6912</span>, <span class="hljs-number">750</span>),
                orientation: {
                    heading: Cesium.Math.toRadians(<span class="hljs-number">20</span>),
                    pitch: Cesium.Math.toRadians(<span class="hljs-number">-20</span>),
                },
            })),
            delay(<span class="hljs-number">2000</span>),
            tap(<span class="hljs-function">() =&gt;</span> {
                <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.osmBuildingInit) {
                    <span class="hljs-built_in">this</span>.viewer.scene.primitives.add(Cesium.createOsmBuildings());
                }
            }),
            tap(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">this</span>.osmBuildingInit = <span class="hljs-literal">true</span>),
        )
        .subscribe();
}
</code></pre>
<p>I am using RxJS to add some delays between flying and adding building data, but you can do them at once.</p>
<p>Now, if you click the fab button, you will fly to the destination and see some building data in 3D.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1632534594018/_sH-NOBDT.png" alt="Screen Shot 2021-09-25 at 11.49.13 am.png" /></p>
<p>If you are interested in adding your 3D data, refer to this Cesium article <a target="_blank" href="https://cesium.com/learn/cesiumjs-learn/cesiumjs-interactive-building/">Visualise a Proposed Building in a 3D City</a></p>
<h3 id="conclusion">Conclusion</h3>
<p>CesiumJS is one of the most advanced 3D libraries when it comes to maps and Geo-Spatial data.</p>
<p>We learned how we could use it to integrate a 3D map experience into our Ionic application easily.</p>
<p>This example uses Angular, but as you can see, all the heavy lifting is done by Cesium, so what JS framework you use with your Ionic is your preference.</p>
<p>Thanks for reading. As usual, if you have any questions, please leave me a comment here or DM me on Twitter.</p>
<h3 id="resources">Resources</h3>
<p>Here is the demo: <a target="_blank" href="https://ionic-angular-cesium-3d-map.vercel.app/">demo deployed using Vercel</a></p>
<p>Here is the code repository: <a target="_blank" href="https://github.com/pazel-io/ionic-angular-cesium-3d-map">ionic-angular-cesium-3d-map</a></p>
<h3 id="contact">Contact</h3>
<p>Twitter: <a target="_blank" href="https://twitter.com/_pazel">_pazel</a></p>
]]></content:encoded></item><item><title><![CDATA[How to Improve UI/UX by improving the visibility of the system status?]]></title><description><![CDATA[Hello friends 👋, in this article, we will have a quick look at the first rule from 10 Usability Heuristics for User Interface and see some examples.
Motivation
The cover photo is from inside of a Melbourne tram. There is a clear sign inside that tel...]]></description><link>https://pazel.dev/improve-visibility-of-the-system-status-7f10b7492b0b</link><guid isPermaLink="true">https://pazel.dev/improve-visibility-of-the-system-status-7f10b7492b0b</guid><category><![CDATA[UI]]></category><category><![CDATA[Mobile Development]]></category><category><![CDATA[UX]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Wed, 11 Aug 2021 12:04:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469373980/2Y5X_pVP0.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello friends 👋, in this article, we will have a quick look at the first rule from 10 Usability Heuristics for User Interface and see some examples.</p>
<h3 id="motivation">Motivation</h3>
<p>The cover photo is from inside of a Melbourne tram. There is a clear sign inside that tells passengers what the next stop is (The yellow text) and that someone has requested to stop at the next station (red text, otherwise the tram does not always stop for all stations).</p>
<p><a target="_blank" href="https://www.pexels.com/@rishiraj-singh-parmar-1394610?utm_content=attributionCopyText&amp;utm_medium=referral&amp;utm_source=pexels">(Photo by Rishiraj Singh Parmar from Pexels)</a></p>
<p>You can see the same type of feedback or visibility in many everyday items. </p>
<p>Like the notification from your phone on low battery, your TV showing an animation when loading a movie on Netflix, or the green camera LED light on your MacBook to show that camera is recording.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628746218668/Kc3kNlTgx.jpeg" alt="macbook-air-camera-indicator-light.jpg" /></p>
<p>This is what your users expect when presented with a website or web application you design. To know where they are and what is happening.</p>
<h3 id="lets-see-how-this-applies-to-uiux">Let's see how this applies to UI/UX</h3>
<p><a target="_blank" href="https://www.nngroup.com/articles/author/jakob-nielsen/">Jakob Nielsen</a> wrote an article in 1994 about "10 Usability Heuristics for User Interface". </p>
<p>They are called “heuristics” because they are broad rules of thumb and not specific usability guidelines.</p>
<p>Many UI/UX designers indeed rely on good examples and their gut feeling when they design. This is good and, in many cases, does the trick for designing a solid UI/UX.</p>
<p>In my opinion, the next level would be to understand the common patterns and the science behind them.</p>
<p>Most UI/UX design techniques have scientific reasons, resulting from human psychology studies and human-computer interaction research.
It’s good to know whys as well as good examples.</p>
<p>Let's look at the first rule,</p>
<h2 id="rule-1-visibility-of-system-status"><strong>Rule 1: Visibility of system status</strong></h2>
<blockquote>
<p>The system should always keep users informed about what is going on, through appropriate feedback within a reasonable time.</p>
</blockquote>
<p>Read the full article on the <a target="_blank" href="https://www.nngroup.com/articles/visibility-system-status/">visibility of system status</a> and watch 3 min <a target="_blank" href="https://www.nngroup.com/videos/usability-heuristic-system-status/">video on the visibility heuristic</a>.</p>
<p>As you can see, the definition is very high-level, so that you can bring it to life in many ways. Visual feedback can exist in the form of colour, state changes (e.g. success or error messages), progress indicators and many more UI/UX components.</p>
<p>Let's look at some of the examples of how to achieve <strong>Visibility of system status</strong></p>
<h3 id="1-use-an-empty-state-message-to-improve-the-visibility-of-your-system-status">1. Use an Empty state message to improve the visibility of your system status.</h3>
<p>Empty states can display a wide variety of content. For example, they can include a list without items, or a search that returns no results. Although these states aren’t typical, they should be designed to prevent confusion.</p>
<p>You can find the definition of this pattern in different design systems. Like <a target="_blank" href="https://material.io/design/communication/empty-states.html#content">Material design</a> from google.</p>
<p>Or <a target="_blank" href="https://www.carbondesignsystem.com/patterns/empty-states-pattern">Carbon design</a> from IBM</p>
<p>Here is a couple of examples from the PMST tool that uses the empty state to communicate to the user about the next step.</p>
<p>Asking the user to add items to the map to use the "My Features" panel.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469366759/XWW7Z02mJ.png" alt="PMST tool - when My Features is empty" /></p>
<p>Asking the user to use a wider screen. Alternatively, they can collapse the sidebar or rotate the phone to landscape, giving the app enough space to show the map.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469369725/tQQ5KrxGB.png" alt="PMST tool - when the screen is too narrow and we cannot show content" /></p>
<h3 id="2-you-can-use-skeleton-loading-to-improve-the-visibility-of-your-system">2. You can use Skeleton loading to improve the visibility of your system</h3>
<p><strong>Based on the carbon design system
[</strong>https://www.carbondesignsystem.com/patterns/loading-pattern#skeleton-states](https://www.carbondesignsystem.com/patterns/loading-pattern#skeleton-states)</p>
<blockquote>
<p><strong>Skeleton loading </strong>or<strong> Placeholder</strong> <strong>loading </strong>is used when information takes an extended amount of time to process and appear on-screen.</p>
<p>Skeleton states are simplified versions of components used on an initial page load to indicate that the information on the page has not fully loaded yet.</p>
<p>They should only appear for a few seconds, disappearing once components and content populate the page. Skeleton states use motion to convey that the page is not stuck and that data is still being loaded. </p>
<p>This can help to <a target="_blank" href="https://www.nngroup.com/articles/progress-indicators/">reduce user uncertainty</a>. Only use skeleton states on container-based components like tiles and structured lists or data-based components like data tables and cards. In most cases, action components (e.g. buttons, input fields, checkboxes, toggles) do not need a skeleton state.</p>
<p>Never represent toast notifications, overflow menus, dropdown items, modals, and loaders with skeleton states. Elements inside a modal may have a skeleton state, but the modal itself should not.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469371954/VNPREazTd.gif" alt /></p>
<p>You can implement the skeleton loading using </p>
<ul>
<li><p><a target="_blank" href="https://codepen.io/Kaladan/pen/yjgzEo">custom CSS</a> </p>
</li>
<li><p><a target="_blank" href="https://css-tricks.com/building-skeleton-screens-css-custom-properties/">https://css-tricks.com/building-skeleton-screens-css-custom-properties/</a></p>
</li>
</ul>
<p>Or use a library to help you.</p>
<ul>
<li><p><a target="_blank" href="https://zalog.ro/placeholder-loading/">Without a framework</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/ngneat/content-loader">With Angular</a></p>
</li>
<li><p><a target="_blank" href="https://material-ui.com/components/skeleton/">With React</a></p>
</li>
<li><p><a target="_blank" href="https://ionicframework.com/docs/api/skeleton-text">With Ionic</a></p>
</li>
</ul>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/mhc1nNE7okA">https://youtu.be/mhc1nNE7okA</a></div>
<h3 id="3-use-a-progress-tracker-to-show-where-the-user-is-in-a-workflow">3. Use a progress tracker to show where the user is in a workflow</h3>
<p>I love making progress and love a good progress tracker to show me where I am in a workflow. One of the most common examples is online shopping. This also encourages the user to continue and finish by knowing that there is, for example, one step left.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628730535143/y15JQQYzF.png" alt="shgot9.png" /></p>
<p>Here is the same concept used in an app that let you report frog sightings. You can record the frog's sound, take a photo of it and fill out more details to complete a report.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628730341729/pWdAR-dyq.jpeg" alt="Screenshot_20210812-110432_Frog Census.jpg" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628730665788/SMU9x2HNU.jpeg" alt="Screenshot_20210812-110819_Frog Census.jpg" /></p>
<p>Another example is from YouTube video upload flow.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628742579627/GExc8uVjs.png" alt="Screen Shot 2021-08-12 at 2.24.42 pm.png" /></p>
<p>Angular Material has a <a target="_blank" href="https://material.angular.io/components/stepper/overview">stepper component</a> that can help with this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628731297936/DJoulQAp_.png" alt="Screen Shot 2021-08-12 at 10.04.16 am.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628731306197/aeR6C0oKw.png" alt="Screen Shot 2021-08-12 at 10.04.06 am.png" /></p>
<h3 id="4-make-it-easy-to-understand-where-the-user-is-in-the-navigation-flow">4. Make it easy to understand where the user is in the navigation flow.</h3>
<p>You can use a breadcrumb for this. The following example shows what is the user selection using a breadcrumb.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628735968690/Jq6Y76K7U.png" alt="Screen Shot 2021-08-12 at 12.37.49 pm.png" /></p>
<p>Also, using a column browser makes it easy to see the hierarchy of data and select a category that matches the breadcrumb that users see to remind them how they got to this selection.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628736083748/W6dB6aNq6.png" alt="Screen Shot 2021-08-12 at 12.39.45 pm.png" /></p>
<p>You can use the same concept on the phone. Here is a demo of Ionic 6 breadcrumb.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/NRWFyhcPUeo">https://youtu.be/NRWFyhcPUeo</a></div>
<p>Similarly, you can highlight the selected item in the main navigation or tabbed navigation to clarify where the user is.</p>
<p>Look at how Twitter uses the blue highlight on the left side menu and top tabbed navigation to tell me I am on the "Explore" tab and looking at the "Entertainment" list.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628738958631/OpHBnRTXq.png" alt="Screen Shot 2021-08-12 at 1.27.08 pm.png" /></p>
<p>Another example from <a target="_blank" href="https://vicmaptopo.land.vic.gov.au/#/discover-map">Vicmap Topographic Maps</a> highlighting the menu (Digital maps) and the selected scale (100K).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628739147565/BELk4ccyW.png" alt="Screen Shot 2021-08-12 at 1.30.49 pm.png" /></p>
<h3 id="5-show-the-system-is-busy-using-a-loading-indicator">5. Show the system is busy using a loading indicator</h3>
<p>This is an obvious one, but I have seen devs overlook it many times. The main reason is that when you develop on your local machine, you are literally using a server, so the connection is fast, and you never need to wait for the response. </p>
<p>I have some news for you that not all users are on a fast connection, and they will appreciate knowing the application is doing something behind the scenes. 😀 </p>
<p>This can be a simple loading indicator or progress bar.</p>
<p>Almost all UI components libraries have a component for this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628742856957/Ztv-Y7KT2.png" alt="Screen Shot 2021-08-12 at 1.52.27 pm.png" /></p>
<p>The material design calls it <a target="_blank" href="https://material.io/components/progress-indicators#circular-progress-indicators">Progress indicators</a>.</p>
<p>Ionic calls it <a target="_blank" href="https://ionicframework.com/docs/api/spinner">Spinner</a> and <a target="_blank" href="https://ionicframework.com/docs/api/progress-bar">Progress bar</a></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/WLeZJcHSXZM">https://youtu.be/WLeZJcHSXZM</a></div>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/PnoiEFeS-nY">https://youtu.be/PnoiEFeS-nY</a></div>
<p>Another example from pull to refresh in Ionic to show app is fetching the list behind the scene.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/LVovCwT91sY">https://youtu.be/LVovCwT91sY</a></div>
<p>In the PMST web app, users can add layers to their map. These layers are usually shown using large images on the map, changing as you pan and zoom.</p>
<p>Obviously, I could not show a loading message each time user interacts with the map. That would block the user, and if loading happens quickly, the message would flash for a second and go.</p>
<p>It would be too annoying, so I use a toast (snack bar) message at the bottom of the screen that only appears if the loading of a layer takes more than 3 seconds, so the user knows we are trying.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628742839520/lr5VXW-eO.png" alt="Screen Shot 2021-08-12 at 1.51.12 pm.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628742880083/LKSABBUbH.png" alt="Screen Shot 2021-08-12 at 1.53.26 pm.png" /></p>
<p>Regardless of the UI library you use, many websites provide loading gifs or SVG animations. Like <a target="_blank" href="https://loading.io/">loading.io</a>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628743950908/GQbEJJFKd.png" alt="Screen Shot 2021-08-12 at 2.51.32 pm.png" /></p>
<p>If you have more space and feeling fancy, you can use a loading animation from <a target="_blank" href="https://lottiefiles.com/search?q=loading&amp;category=animations">lottiefiles</a></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/EhC37WMLXNY">https://youtu.be/EhC37WMLXNY</a></div>
<h3 id="bonus">Bonus</h3>
<p>If you are interested in using Lottie animations with Angular or Ionic, I have an example project that might help you.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/pazel-io/ionic-angular-lottifiles-example">https://github.com/pazel-io/ionic-angular-lottifiles-example</a></div>
<h3 id="6-provide-timely-feedback-to-users-using-micro-animation">6. Provide timely feedback to users using micro animation</h3>
<p>This is one of my favourites. Micro-animations are small, preferably functional animations that support the user by giving visual feedback and clearly displaying changes.</p>
<p>Have you noticed Twitter like animation?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628748364884/5I3gY3-V8.gif" alt="like.gif" /></p>
<p>Many UI component libraries have this built-in.</p>
<p>For example, the Ionic Fab button uses a scale animation and morphs the arrow icon to the close icon to provide visual feedback. (iOS style) </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/oBn8zf-5-wI">https://youtu.be/oBn8zf-5-wI</a></div>
<p>This example shows how Ionic provides the micro animation for the tap on cards based on iOS or Android design guidelines.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/fSXkCNTAgjY">https://youtu.be/fSXkCNTAgjY</a></div>
<h3 id="7-haptic-feedback-for-touch-devices">7. Haptic feedback for touch devices</h3>
<p>The Haptics provides physical feedback to the user through touch or vibration.</p>
<p>Simply put, you use the device vibration hardware to provide feedback to users when they tap a button or do a long press. The vibration pattern can be different based on the feedback.</p>
<p>For example, an error can cause a longer vibration.</p>
<p>The vibrate API is available in JS through <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/vibrate">Navigator.vibrate()</a></p>
<pre><code><span class="hljs-attribute">navigator</span>.vibrate(<span class="hljs-number">200</span>); // vibrate for <span class="hljs-number">200</span>ms

<span class="hljs-attribute">navigator</span>.vibrate([<span class="hljs-number">100</span>,<span class="hljs-number">30</span>,<span class="hljs-number">100</span>,<span class="hljs-number">30</span>,<span class="hljs-number">100</span>,<span class="hljs-number">30</span>,<span class="hljs-number">200</span>,<span class="hljs-number">30</span>,<span class="hljs-number">200</span>,<span class="hljs-number">30</span>,<span class="hljs-number">200</span>,<span class="hljs-number">30</span>,<span class="hljs-number">100</span>,<span class="hljs-number">30</span>,<span class="hljs-number">100</span>,<span class="hljs-number">30</span>,<span class="hljs-number">100</span>]); // Vibrate 'SOS' in Morse.
</code></pre><p>If you use Ionic with Capacitor, this is provided out of the box through the <a target="_blank" href="https://capacitorjs.com/docs/apis/haptics">Haptic feedback plugin</a>.</p>
<h3 id="conclusion">Conclusion</h3>
<p>We looked at the first rule from the "10 Usability Heuristics for User Interface" and explored how it is implemented in web or mobile UI/UX we build every day.</p>
<p>I just scratched the surface with this article, and there are many more ways you can improve the visibility of the system status.</p>
<p>Usually, you use a combination of all these techniques based on the context.</p>
<p>I encourage you to read the <a target="_blank" href="https://www.nngroup.com/articles/visibility-system-status/">original article</a> as well.</p>
<p>I hope you enjoyed reading my article, and as usual, if you have any comments, please let me know here or DM me on Twitter.</p>
<p>Twitter: _pazel</p>
]]></content:encoded></item><item><title><![CDATA[Faster maps with lazy-loaded components]]></title><description><![CDATA[Hello friends 👋
Parham here, with another step-by-step guide. This time, we will learn how to use Angular Router to lazy-load UI pieces that are not visible and doing it without changing the main route. So we can keep the first context.
Here is the ...]]></description><link>https://pazel.dev/faster-maps-with-lazy-loaded-components</link><guid isPermaLink="true">https://pazel.dev/faster-maps-with-lazy-loaded-components</guid><category><![CDATA[Angular]]></category><category><![CDATA[maps]]></category><category><![CDATA[performance]]></category><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Sun, 25 Jul 2021 10:04:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1627207237116/rs0K79BqY.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello friends 👋</p>
<p>Parham here, with another step-by-step guide. This time, we will learn how to use Angular <code>Router</code> to lazy-load UI pieces that are not visible and doing it without changing the main route. So we can keep the first context.</p>
<p>Here is the final demo deployed using Vercel. <a target="_blank" href="https://ng-lazy-loaded-modal.vercel.app">https://ng-lazy-loaded-modal.vercel.app</a></p>
<p>Here is a video demo of the final result.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=yEB0fLfd55o">https://www.youtube.com/watch?v=yEB0fLfd55o</a></div>
<p>The main principle is the same here regardless of the technique or frontend technology you choose. You want to reduce the number of resources that the browser needs to initially download and parse in order to render your application.
This is also referred to as optimising the critical rendering path. You can use different methods to optimize the critical rendering path. Lazy loading is one of them.
If you are interested in the Critical Rendering Path topic, please check this <a target="_blank" href="https://developers.google.com/web/fundamentals/performance/critical-rendering-path">course</a> from Google web fundamentals.</p>
<p>A reminder</p>
<blockquote>
<p>By default, NgModules are eagerly loaded, which means that as soon as the app loads, so do all the NgModules, whether or not they are immediately necessary. For large apps with lots of routes, consider lazy loading - a design pattern that loads NgModules as needed. Lazy loading helps keep initial bundle sizes smaller, which in turn helps decrease load times.
https://angular.io/guide/lazy-loading-ngmodules</p>
</blockquote>
<p>Following the link above, it is easy to lazy load a feature module by simply defining a route for it.
Here is an example to remind you how it’s done.</p>
<pre><code><span class="hljs-keyword">const</span> routes: Routes = [
    {
        <span class="hljs-attr">path</span>: <span class="hljs-string">'map'</span>,
        <span class="hljs-attr">loadChildren</span>: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./map/map.module'</span>).then(<span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> m.MapModule)
    }
];
</code></pre><p>Doing this, Angular will separately bundle the map module, and it will only be loaded when you access the /map route.</p>
<h3 id="the-problem">The problem</h3>
<p>The previous example is a good solution until you come across use cases with components or pieces of UI that cannot have a separate route. Let me explain what I mean.</p>
<p>Have a look at this example:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627193934839/ql0vGEGy8.png" alt="1_Y8CbkVRJ1OTTp1fqyWjYng.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627193965875/qOUriJoBW.png" alt="1_t2Giqnkj6llOoO1MLzaHIQ.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627193996508/swUQD5aw_.png" alt="1_x5sPF-Lbgvu_H-Ns920nVA.png" /></p>
<p>All the screenshots show the same map view. This view is created using a feature module with the route <code>/map</code> that hosts many other features that show up based on user interaction.</p>
<ul>
<li>Search modals that show up when the user is searching on the map.</li>
<li><p>A collapsible Layer selectors panel to find a layer to add to the map. So it will be only visible when expanded.</p>
</li>
<li><p>Layer details, which are only visible when a user clicks on a layer in the side panel to see its details.</p>
</li>
<li>Also, some more UI pieces that can show up when the user selects a specific menu. Like Draw controls, Print form, Set Region control and more.</li>
<li>Plus, each one of these UIs utilises services and other dependencies under the hood, which can quickly add up to map module bundle size.</li>
</ul>
<p>The goal is to load these pieces of UI only if the user needs them. This will help reduce the initial bundle size for the Angular app and improve the performance.</p>
<p>I cannot lazy-load the other feature modules like the <code>map</code> module because accessing any routes that define other feature modules means leaving and unloading the map that will not work for my use case.
For example, I need to stay on the map and show the modal.</p>
<p>So I want to lazy-load feature modules without leaving the <code>map</code> module. (<code>/map</code> route)
Now that we have a better picture of the problem, let's discuss the solution.</p>
<p>There are a couple of ways we can solve this problem:</p>
<ol>
<li>Use <a target="_blank" href="https://webpack.js.org/guides/code-splitting/#dynamic-imports">Dynamic imports</a> using the <code>import()</code> function and let Webpack do its magic to lazy load a feature module. This feature is available in Angular 9+.</li>
<li>Use <a target="_blank" href="https://angular.io/guide/router#lazy-loading">Angular Router to lazy load</a> a feature module.</li>
</ol>
<p>My solution demonstrates the second option which uses Angular router to handle lazy loading and state of the application (e.g. a modal being open or not). This means URLs are bookmarkable.
For example, if you open this URL </p>
<pre><code><span class="hljs-attribute">https</span>://invest.agriculture.vic.gov.au/#/map/(m:s/%<span class="hljs-number">5</span>B<span class="hljs-number">16030153</span>.<span class="hljs-number">917455776</span>,-<span class="hljs-number">4319458</span>.<span class="hljs-number">318312469</span>%<span class="hljs-number">5</span>D)?lat=<span class="hljs-number">144</span>.<span class="hljs-number">07623978175104</span>&amp;lon=-<span class="hljs-number">36</span>.<span class="hljs-number">15776605793607</span>&amp;z=<span class="hljs-number">11</span>&amp;bm=bm<span class="hljs-number">0</span>&amp;l=mb<span class="hljs-number">0</span>:y:<span class="hljs-number">100</span>
</code></pre><p>the app will automatically open the search modal and use the passed query params to search for exact lat/lng and layers that was passed through URL.</p>
<p>If you are interested in the first solution please also checkout <a target="_blank" href="https://netbasal.com/lazy-load-modal-components-in-angular-8cb54bba7bf7">Lazy Load Modal Components in Angular</a> by Netanel Basal.</p>
<h3 id="the-solution-angular-auxiliary-routes-to-the-rescue">The solution - Angular Auxiliary Routes to the rescue</h3>
<h4 id="first-lets-see-what-the-auxiliary-route-is">First, let's see what the Auxiliary route is?</h4>
<p>To put it simply, they are just plain routes like the primary route. The only difference is that auxiliary routes are mapped to a different router outlet which must be named (unlike the primary outlet).</p>
<p>The following video shows how Invest in Victorian Agriculture website uses this capability. Here I am starting in the context of the <code>/map</code> route, and all the JS required for the visible part of the view is loaded with the map module.
Please pay attention to network requests when I open the search results modal. It’s only then that Angular loads the JS files required for the SearchModule and my modal.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=TAuxqzVe97E">https://www.youtube.com/watch?v=TAuxqzVe97E</a></div>
<p>invest.agriculture.vic.gov.au is too complicated for the learning purpose, so I made an Example application replicating the lazy-loaded modal and map.
This application is available on Github. So you can download and follow the same steps mentioned in the rest of my article.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/pazel-io/ng-lazy-loaded-modal">https://github.com/pazel-io/ng-lazy-loaded-modal</a></div>
<h3 id="lets-see-how-the-routing-works-here">Let’s see how the routing works here</h3>
<p>This is the syntax for the URL</p>
<pre><code>http://base-<span class="hljs-type">path</span>/#/<span class="hljs-keyword">primary</span>-route-<span class="hljs-type">path</span>/(secondary-outlet-<span class="hljs-type">name</span>:route-<span class="hljs-type">path</span>)
</code></pre><p>Which translate to <code>http://localhost:4200/#/map/(map-outlet:modal)</code>.</p>
<ul>
<li><code>http://localhost:4200</code> is the base path. I am using <a target="_blank" href="https://angular.io/api/common/HashLocationStrategy">HashLocationStrategy</a>, which is why I have <code>/#/</code> after the base URL.</li>
<li><code>/map</code> is the primary route's path for the map view.</li>
<li>The remaining part in the parenthesis is my auxiliary route (route within the <code>/map</code> route) <code>(map-outlet:modal)</code>.</li>
</ul>
<h4 id="lets-breakdown-the-auxiliary-route">Let’s breakdown the auxiliary route</h4>
<ul>
<li><code>(</code> open and <code>)</code> close parenthesis indicates the beginning and end of my auxiliary route.</li>
<li>Next is <code>map-outlet</code>, which is my outlet name. This is the name I selected for my secondary outlet.</li>
<li>Followed by <code>:</code> that separates the outlet name from the actual path.
In this case, the path is <code>modal</code> for the modal. I am using short names here to shorten my URL length, but you can use any name.</li>
</ul>
<h3 id="how-to-build-the-auxiliary-routes">How to build the auxiliary routes?</h3>
<p>First, I need a secondary router outlet (named router outlet).
You can have one default(primary) router outlet and many named(secondary) router outlets as you need in Angular.</p>
<blockquote>
<p>The <a target="_blank" href="https://angular.io/api/router/RouterOutlet">RouterOutlet</a> is a directive from the router library that is used as a component. It acts as a placeholder that marks the spot in the template where the router should display the components for that outlet.</p>
</blockquote>
<p>Like any other Angular have we will have a primary outlet defined in the <code>app.component.html</code> where we render the primary routes.</p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"menu"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">routerLink</span>=<span class="hljs-string">"home"</span>&gt;</span>Home<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>|<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">routerLink</span>=<span class="hljs-string">"map"</span>&gt;</span>Map<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"content"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">router-outlet</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">router-outlet</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre><p>Here is the top-level application router config defining the /map route, which lazy loads the map module.</p>
<pre><code><span class="hljs-keyword">import</span> { NgModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { Routes, RouterModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;

const routes: Routes = [
  {
    path: <span class="hljs-string">''</span>,
    redirectTo: <span class="hljs-string">'home'</span>,
    pathMatch: <span class="hljs-string">'full'</span>,
  },
  {
    path: <span class="hljs-string">'home'</span>,
    loadChildren: <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./home/home.module'</span>).<span class="hljs-keyword">then</span>(<span class="hljs-function"><span class="hljs-params">(m)</span> =&gt;</span> m.HomeModule),
  },
  {
    path: <span class="hljs-string">'map'</span>,
    loadChildren: <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./map/map.module'</span>).<span class="hljs-keyword">then</span>(<span class="hljs-function"><span class="hljs-params">(m)</span> =&gt;</span> m.MapModule),
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes, { useHash: <span class="hljs-literal">true</span> })],
  exports: [RouterModule]
})
<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppRoutingModule</span> { }</span>
</code></pre><p>The primary router outlet will render <code>/home</code> and <code>/map</code>.
Given the configuration above, when the browser URL for this application becomes <code>/map</code>, the router matches that URL to the route path <code>/map</code> and displays the <code>MapComponent</code>. The rendered HTML will have the <code>&lt;app-map&gt;</code> as a sibling element to the RouterOutlet that you've placed in the host component's template.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627294542582/uI7apvCG3.png" alt="Screen Shot 2021-07-26 at 8.14.43 pm.png" /></p>
<p>I will place another router outlet in the <code>map.component.html</code> which is a named router outlet (my secondary outlet). Other floating components like modals or sliding panels can use this named router outlet to show within the map context.</p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">router-outlet</span> <span class="hljs-attr">name</span>=<span class="hljs-string">'map-outlet'</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">router-outlet</span>&gt;</span>
</code></pre><p>I need to specify the outlet name in the map module router when I define the child routes.</p>
<pre><code><span class="hljs-keyword">import</span> { NgModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { RouterModule, Routes } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { MapComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./map.component'</span>;

const routes: Routes = [
  {
    path: <span class="hljs-string">''</span>,
    component: MapComponent,
    children: [
      {
        path: <span class="hljs-string">'modal'</span>,
        outlet: <span class="hljs-string">'map-outlet'</span>,
        loadChildren: <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'../modal-wrapper/modal-wrapper.module'</span>)
          .<span class="hljs-keyword">then</span>(<span class="hljs-function"><span class="hljs-params">(m)</span> =&gt;</span> m.ModalWrapperModule),
      },
    ],
  },
];

@NgModule({
            imports: [RouterModule.forChild(routes)],
            exports: [RouterModule],
          })
<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MapRoutingModule</span> {</span>
}
</code></pre><p>That’s all you need to define a route within a route, and Angular will take care of bundling the lazy-loaded feature modules separately and also lazy loading the feature modules as you navigate to their respective route.
Here is the code structure for the map module.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627199507252/gQMmqPcQlK.png" alt="1_myjsCs_EOV1MMA34YatR0A.png" /></p>
<p>Now you can easily use the Angular router to navigate to the Modal route. Take a look at the <code>openModal()</code> method.</p>
<pre><code><span class="hljs-keyword">import</span> { Component } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { Router } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { TileLayer } <span class="hljs-keyword">from</span> <span class="hljs-string">'leaflet'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-map'</span>,
  templateUrl: <span class="hljs-string">'./map.component.html'</span>,
  styleUrls: [<span class="hljs-string">'./map.component.scss'</span>]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> MapComponent {
  options = {
    zoom: <span class="hljs-number">5</span>,
    maxZoom: <span class="hljs-number">18</span>,
    preferCanvas: <span class="hljs-literal">true</span>,
    attributionControl: <span class="hljs-literal">true</span>,
    center: [
      <span class="hljs-number">-28.690259</span>,
      <span class="hljs-number">131.5190514</span>,
    ],
    layers: [
      <span class="hljs-keyword">new</span> TileLayer(<span class="hljs-string">'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'</span>, { maxZoom: <span class="hljs-number">18</span>, attribution: <span class="hljs-string">'...'</span> })
    ]
  };

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> router: Router</span>) {}

  <span class="hljs-keyword">public</span> openModal(): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.router.navigate([<span class="hljs-string">'map'</span>, {outlets: {[<span class="hljs-string">'map-outlet'</span>]: [<span class="hljs-string">'modal'</span>]}}]);
  }
}
</code></pre><p>As you can see, I am passing multiple commands to the navigate method.</p>
<p>Basically telling the router to navigate to <code>/map/(map-outlet:modal)</code> where /map is the map module path, map-outlet is my named outlet, and modal is the path for the modal module.</p>
<p>And the last piece of the puzzle to make the router work. Place a named <code>&lt;router-outlet&gt;&lt;/router-outlet&gt;</code> in the map view HTML.</p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">leaflet</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"map"</span> [<span class="hljs-attr">leafletOptions</span>]=<span class="hljs-string">"options"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">router-outlet</span> <span class="hljs-attr">name</span>=<span class="hljs-string">'map-outlet'</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">router-outlet</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"map__overlayButton"</span> <span class="hljs-attr">mat-raised-button</span> <span class="hljs-attr">color</span>=<span class="hljs-string">"primary"</span> (<span class="hljs-attr">click</span>)=<span class="hljs-string">"openModal()"</span>&gt;</span>Open modal<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre><p>By putting an outlet in the <code>map.component.html</code> I am telling the Angular where to render the content of the named router outlet <code>(map-outlet)</code>.</p>
<h3 id="how-does-the-code-look-like-for-the-modal-module">How does the code look like for the modal module?</h3>
<p>The architecture for the module is a bit more complicated than the other feature modules in my application since with Angular material <code>dialog</code>, I need to pass a component to the <code>this.dialog.open()</code> method.
Here is how the code structure looks like.</p>
<p>This is <code>modal-wrapper.module.ts</code></p>
<pre><code><span class="hljs-keyword">import</span> { NgModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { CommonModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/common'</span>;
<span class="hljs-keyword">import</span> { ModalComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./modal/modal.component'</span>;
<span class="hljs-keyword">import</span> { ModalWrapperComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./modal-wrapper.component'</span>;
<span class="hljs-keyword">import</span> { ModalWrapperRoutingModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'./modal-wrapper-routing.module'</span>;
<span class="hljs-keyword">import</span> { MatDialogModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/material/dialog'</span>;
<span class="hljs-keyword">import</span> { MatButtonModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/material/button'</span>;

@NgModule({
  declarations: [ModalComponent, ModalWrapperComponent],
    imports: [
        CommonModule,
        ModalWrapperRoutingModule,
        MatDialogModule,
        MatButtonModule,
    ]
})
<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ModalWrapperModule</span> { }</span>
</code></pre><p>Here is <code>modal-wrapper-routing.module.ts</code></p>
<pre><code><span class="hljs-keyword">import</span> { NgModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { RouterModule, Routes } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { ModalWrapperComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./modal-wrapper.component'</span>;

<span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">''</span>,
    component: ModalWrapperComponent,
  },
];

<span class="hljs-meta">@NgModule</span>(
  {
    imports: [RouterModule.forChild(routes)],
    <span class="hljs-built_in">exports</span>: [RouterModule],
  },
)
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ModalWrapperRoutingModule {
}
</code></pre><p>this is <code>modal-wrapper.component.ts</code></p>
<pre><code><span class="hljs-keyword">import</span> { Component, OnDestroy, OnInit } from <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { MatDialog, MatDialogRef } from <span class="hljs-string">'@angular/material/dialog'</span>;
<span class="hljs-keyword">import</span> { ModalComponent } from <span class="hljs-string">'./modal/modal.component'</span>;
<span class="hljs-keyword">import</span> { take } from <span class="hljs-string">'rxjs/operators'</span>;
<span class="hljs-keyword">import</span> { Router } from <span class="hljs-string">'@angular/router'</span>;

<span class="hljs-meta">@Component({
  selector: <span class="hljs-meta-string">'app-modal-wrapper'</span>,
  template: <span class="hljs-meta-string">''</span>,
})</span>
export <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ModalWrapperComponent</span> <span class="hljs-title">implements</span> <span class="hljs-title">OnInit</span>, <span class="hljs-type">OnDestroy {</span></span>

  <span class="hljs-keyword">private</span> dialogRef: MatDialogRef&lt;ModalComponent&gt;;
  <span class="hljs-keyword">private</span> closedOnDestroy = <span class="hljs-literal">false</span>;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-keyword">private</span> dialog: MatDialog,
              <span class="hljs-keyword">private</span> router: Router,
  ) {
  }

  ngOnInit(): void {
    <span class="hljs-keyword">this</span>.openDialog();
  }

  <span class="hljs-keyword">private</span> openDialog(): void {
    <span class="hljs-keyword">this</span>.dialogRef = <span class="hljs-keyword">this</span>.dialog.<span class="hljs-keyword">open</span>(ModalComponent,
      {
        minWidth: <span class="hljs-string">'700px'</span>,
        minHeight: <span class="hljs-string">'400px'</span>
      });

    <span class="hljs-keyword">this</span>.dialogRef.afterClosed()
      .pipe(take(<span class="hljs-number">1</span>))
      .subscribe(result =&gt; {
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.closedOnDestroy) {
          <span class="hljs-keyword">return</span>;
        }
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.router.navigate([<span class="hljs-string">'map'</span>]);
      });
  }

  <span class="hljs-keyword">public</span> ngOnDestroy(): void {
    <span class="hljs-keyword">this</span>.closedOnDestroy = <span class="hljs-literal">true</span>;
    <span class="hljs-keyword">this</span>.dialogRef.close();
  }

}
</code></pre><p>and here is <code>modal.component.ts</code></p>
<pre><code><span class="hljs-keyword">import</span> { Component, OnInit } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-modal'</span>,
  templateUrl: <span class="hljs-string">'./modal.component.html'</span>,
  styleUrls: [<span class="hljs-string">'./modal.component.scss'</span>]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ModalComponent <span class="hljs-keyword">implements</span> OnInit {

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) { }

  ngOnInit(): <span class="hljs-built_in">void</span> {
  }

}
</code></pre><p>and finally <code>modal.component.html</code></p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">mat-dialog-title</span>&gt;</span>🎊 Hey, you made it! I am a route enabled modal 🎉<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">mat-dialog-content</span>&gt;</span>You may refresh the page but I will be back.<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">mat-dialog-actions</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">mat-raised-button</span> <span class="hljs-attr">mat-dialog-close</span> <span class="hljs-attr">color</span>=<span class="hljs-string">"primary"</span>&gt;</span>close me<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre><h3 id="conclusion">Conclusion</h3>
<p>We learned how to use Angular Router to lazy-load UI components based on user interactions without changing the main route.</p>
<p>I have simplified the code here for this demonstration, but you can imagine that a feature module can be more complicated and use more services or other third-party dependencies.</p>
<p>So being able to lazy-load modules with Auxiliary routes will be a great boost to the application performance.</p>
<ul>
<li>As a result of this architecture, your modal is route-enabled, which means you can refresh the page, and Angular will open the modal automatically.
This is great for bookmarking and sharing URLs.
In this case, I can share the link with someone, and they can see the search result for the exact lat/long I used for my search.</li>
<li><p>By opening each lazy-loaded route defined similarly to the search module on the map, I navigate away from the search module route, and the search modal will get destroyed. This is because they use the same router.
This can be good or bad based on the use case.
In my case, this is exactly what I needed.
For example, I want to close the search modal when I open the layer selector and vice versa.
This will naturally happen as I move away from the previous route.
Without this architecture, I will need to keep some flags locally in the map component or on the app state to make sure I close the layer selector component before opening the search modal, which can get really messy in a big application.</p>
</li>
<li><p>You can skip the wrapper component (<code>modal-wrapper.component.ts</code>) in your implementation if you have a simpler UI to show/hide.
The wrapper component is handy to support the usage of Angular Material Dialog.</p>
</li>
<li>You can prefetch the lazy-loaded modules using the <code>preloadingStrategy: PreloadAllModules</code> or use a custom <code>preloadingStrategy</code> as described in the following article.</li>
</ul>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://medium.com/angular-in-depth/network-aware-preloading-strategy-for-angular-lazy-loading-b883a0fbbaf0">https://medium.com/angular-in-depth/network-aware-preloading-strategy-for-angular-lazy-loading-b883a0fbbaf0</a></div>
<p>Also, if you require lazy-loading a specific component like a modal without routing, you might find the following article useful.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://netbasal.com/lazy-load-modal-components-in-angular-8cb54bba7bf7">https://netbasal.com/lazy-load-modal-components-in-angular-8cb54bba7bf7</a></div>
<p>I hope you learned something useful from this article.</p>
<p>As usual, if you have any questions, please leave me a comment here or DM me on Twitter.</p>
<p>Here is the code repository: <a target="_blank" href="https://github.com/pazel-io/ng-lazy-loaded-modal">https://github.com/pazel-io/ng-lazy-loaded-modal</a></p>
<p>Here is the final demo deployed using Vercel.</p>
<p><a target="_blank" href="https://ng-lazy-loaded-modal.vercel.app">https://ng-lazy-loaded-modal.vercel.app</a></p>
<p>Twitter: <a target="_blank" href="https://twitter.com/_pazel">_pazel</a></p>
]]></content:encoded></item><item><title><![CDATA[Reduce if/else using RxJS]]></title><description><![CDATA[Hello friends 👋
Parham here, this time with a quick tip about how I use RxJS to reduce if/else.
There are already many blogs written on why if/else might be bad or how to reduce if/else statements in your code.
You might have seen people asking ques...]]></description><link>https://pazel.dev/reduce-ifelse-using-rxjs</link><guid isPermaLink="true">https://pazel.dev/reduce-ifelse-using-rxjs</guid><category><![CDATA[RxJS]]></category><category><![CDATA[Angular]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Programming Tips]]></category><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Mon, 19 Jul 2021 14:25:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1626742317222/Ozmy7_Z2s.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello friends 👋</p>
<p>Parham here, this time with a quick tip about how I use RxJS to reduce if/else.</p>
<p>There are already many blogs written on why <code>if/else</code> might be bad or how to reduce <code>if/else</code> statements in your code.</p>
<p>You might have seen people asking questions like, "Why is the 'if' statement considered evil?", "Why is it a bad programming practice to use if/else?" or similar ones.</p>
<p>Here is a popular blog example on techniques on how to reduce <code>if/else</code> https://javascript.plainenglish.io/6-tips-to-improve-your-conditional-statements-for-better-readability-56256c5a5245</p>
<p>In my opinion, there is nothing wrong with <code>if</code> statements but overuse of <code>if/else</code> and especially nested <code>if/else</code> statements can add more complexity to the code, make it less readable and more error prone.</p>
<p>Some functional libraries like <a target="_blank" href="https://ramdajs.com/">ramdajs</a> even have functions like, <code>ifElse</code>, <code>unless</code>, <code>when</code> and <code>cond</code> to solve this problem by using a function instead of <code>if/else</code> blocks.</p>
<p>https://ramdajs.com/docs/#ifElse</p>
<pre><code><span class="hljs-string">const</span> <span class="hljs-string">incCount</span> <span class="hljs-string">=</span> <span class="hljs-string">R.ifElse(</span>
  <span class="hljs-string">R.has('count'),</span>
  <span class="hljs-string">R.over(R.lensProp('count'),</span> <span class="hljs-string">R.inc),</span>
  <span class="hljs-string">R.assoc('count',</span> <span class="hljs-number">1</span><span class="hljs-string">)</span>
<span class="hljs-string">);</span>
<span class="hljs-string">incCount({});</span>           <span class="hljs-string">//=&gt;</span> { <span class="hljs-attr">count:</span> <span class="hljs-number">1</span> }
<span class="hljs-string">incCount({</span> <span class="hljs-attr">count:</span> <span class="hljs-number">1</span> <span class="hljs-string">});</span> <span class="hljs-string">//=&gt;</span> { <span class="hljs-attr">count:</span> <span class="hljs-number">2</span> }
</code></pre><p>So overall it's better if you can write your code in a way that avoids branching off and needs fewer <code>if/else</code> statements. Writing functional code can be one more trick in your toolbelt that will eliminate the need for nested <code>if/else</code> in the first place. </p>
<p>Here is my take on utilising RxJS for the same purpose.</p>
<h3 id="the-code-example">The code example</h3>
<p>For a bit of context of how the code example is structured, here is what I am trying to achieve.</p>
<p>I am trying to decide where the user should land when they open the app.</p>
<p>The logic in plain English would be something like this:</p>
<ul>
<li><p>If this is the first time users visit the app, show Terms &amp; conditions to agree to T&amp;C. App will save and remember this choice. It's a one-off thing.</p>
</li>
<li><p>Then If the user has agreed to T&amp;C and has not viewed the app intro, show the introduction. This gives the user a tour of the app features and only happen once as well. So the app keeps a flag remembering this as well.</p>
</li>
<li><p>If the user has agreed to T&amp;C and has viewed the intro, take them to the home page.</p>
</li>
</ul>
<p>The code uses TypeScript and Angular, but no major dependency on these tech stacks, and you can replicate in any other tech stacks like React or Vue easily.</p>
<h3 id="original-code-i-came-across-this-code-in-one-of-the-code-reviews">Original code: I came across this code in one of the code reviews.</h3>
<pre><code>public enter(): Subscription {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.termsConditionsStateSelector.agreed()
      .pipe(
        switchMap(<span class="hljs-function"><span class="hljs-params">(termsAgreed)</span> =&gt;</span> <span class="hljs-built_in">this</span>.helpStateSelector.viewed()
          .pipe(
            map(<span class="hljs-function"><span class="hljs-params">(helpViewed)</span> =&gt;</span> ({helpViewed, termsAgreed})),
          ),
        ),
        tap(<span class="hljs-function"><span class="hljs-params">({ termsAgreed, helpViewed })</span> =&gt;</span> {
          <span class="hljs-keyword">if</span> (!termsAgreed) {
            <span class="hljs-built_in">this</span>.navCtrl.navigateRoot(<span class="hljs-string">'/terms-conditions'</span>);
          } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (termsAgreed &amp;&amp; !helpViewed) {
            <span class="hljs-built_in">this</span>.navCtrl.navigateRoot(<span class="hljs-string">'/help'</span>);
          } <span class="hljs-keyword">else</span> {
            <span class="hljs-built_in">this</span>.navCtrl.navigateRoot(<span class="hljs-string">'/home'</span>);
          }
        }),
      ).subscribe();
  }
</code></pre><h3 id="refactor-option-1-use-withlatestfrom-instead-of-that-weird-switchmap">Refactor Option 1: Use <code>withLatestFrom</code> instead of that weird <code>switchMap</code>.</h3>
<pre><code>  <span class="hljs-keyword">public</span> enter(): Subscription {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.termsConditionsStateSelector.agreed()
      .pipe(
        withLatestFrom(<span class="hljs-keyword">this</span>.helpStateSelector.viewed()),
        tap(([termsAgreed, helpViewed]) =&gt; {
          <span class="hljs-keyword">if</span> (!termsAgreed) {
            <span class="hljs-keyword">this</span>.navCtrl.navigateRoot(<span class="hljs-string">'/terms-conditions'</span>);
          } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (termsAgreed &amp;&amp; !helpViewed) {
            <span class="hljs-keyword">this</span>.navCtrl.navigateRoot(<span class="hljs-string">'/help'</span>);
          } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">this</span>.navCtrl.navigateRoot(<span class="hljs-string">'/home'</span>);
          }
        }),
      ).subscribe();
  }
</code></pre><h3 id="refactor-option-2-completely-remove-the-ifelse">Refactor option 2: Completely remove the <code>if/else</code>.</h3>
<pre><code> private showTnC(): <span class="hljs-keyword">void</span> {
    const showTnC$ = <span class="hljs-built_in">this</span>.termsConditionsStateSelector.agreed()
      .pipe(
        filter(<span class="hljs-function"><span class="hljs-params">(agree)</span> =&gt;</span> !agree),
        take(<span class="hljs-number">1</span>),
        tap(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-built_in">this</span>.navCtrl.navigateRoot(<span class="hljs-string">'/terms-conditions'</span>)),
      );
    showTnC$.subscribe();
  }
 private showIntro(): <span class="hljs-keyword">void</span> {
    const showIntro$ = <span class="hljs-built_in">this</span>.termsConditionsStateSelector.agreed()
      .pipe(
        filter(<span class="hljs-function"><span class="hljs-params">(agree)</span> =&gt;</span> agree),
        withLatestFrom(<span class="hljs-built_in">this</span>.helpStateSelector.viewed()),
        filter(<span class="hljs-function"><span class="hljs-params">([, viewed])</span> =&gt;</span> !viewed),
        take(<span class="hljs-number">1</span>),
        tap(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-built_in">this</span>.navCtrl.navigateRoot(<span class="hljs-string">'/help'</span>)),
      );
    showIntro$.subscribe();
  }
 private showHome(): <span class="hljs-keyword">void</span> {
    const showHome$ = <span class="hljs-built_in">this</span>.helpStateSelector.viewed()
      .pipe(
        filter(<span class="hljs-function"><span class="hljs-params">(viewed)</span> =&gt;</span> viewed),
        take(<span class="hljs-number">1</span>),
        tap(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-built_in">this</span>.navCtrl.navigateRoot(<span class="hljs-string">'/home'</span>)),
      );
    showHome$.subscribe();
  }
 public enter(): <span class="hljs-keyword">void</span> {
    <span class="hljs-built_in">this</span>.showTnC();
    <span class="hljs-built_in">this</span>.showIntro();
    <span class="hljs-built_in">this</span>.showHome();
  }
</code></pre><p>What do you think about the readability of code in these two examples?</p>
<p>Option 1 is easier from understanding the control flow, but Option 2 is more modular and functional.</p>
<h4 id="i-like-option-2-better-because">I like Option 2 better because:</h4>
<ul>
<li>There is no if/else. ✅</li>
<li>Each function is doing a single job, so concerns are separated.</li>
<li>Code is more modular and therefore easier to test.</li>
<li>Functions are named based on my DSL.</li>
<li>Code is more functional, and there is less side effect.</li>
<li>I know how many signals to expect to use the <code>take()</code> operator and do not need to worry about unsubscribing?!! (Not exactly correct always, depending on the code flow)</li>
</ul>
<h3 id="a-note-on-unsubscribing-and-memory-leak-issues-based-on-your-code-flow">⛔️ A note on unsubscribing and memory leak issues based on your code flow</h3>
<p>Using <code>filter()</code> with <code>take(1)</code> can lead to a memory leak. For example, imagine <code>agreed === true</code> at the time of subscription, then <code>showTnC$</code> will never get a signal and therefore never completes the subscription, even after the component is destroyed. In other words, it will be waiting forever. (potential memory leak).
If this is a component that you can reinitialise multiple times (like leaving a view and come back to it), you will have multiple instances of these subscriptions, leading to your app misbehaving.</p>
<p>You have three solutions to fix this problem.</p>
<ol>
<li>Use a destroy signal and the <code>takeUntil(this.destroyed$)</code> pattern with destroyed$ emitting a signal as soon a subscription is not required. For example, if I get a signal to showIntro$, Probably I can send a destroy signal for my showTnC$ and use the <code>takeUntil</code> operator, assuming there is no further use for showTnC$.</li>
<li>Skip the subscription altogether, use the <code>async</code> pipe in the template, and let Angular take care of unsubscribing.</li>
<li>Keep a reference to your subscription and unsubscribe when the component is destroyed (ngOnDestroy). If you want to do this, I suggest adding an array of subscriptions to your class and push every new subscription to this array. Later at the component destroy time, you can loop over your subscriptions array and unsubscribe from each.</li>
</ol>
<p>Something like this:</p>
<pre><code><span class="hljs-keyword">public</span> subscriptions: Subscription[] = [];

 <span class="hljs-keyword">private</span> showHome(): <span class="hljs-built_in">void</span> {
    <span class="hljs-keyword">const</span> showHome$ = <span class="hljs-built_in">this</span>.helpStateSelector.viewed()
      .pipe(
        filter(<span class="hljs-function">(<span class="hljs-params">viewed</span>) =&gt;</span> viewed),
        take(<span class="hljs-number">1</span>),
        tap(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">this</span>.navCtrl.navigateRoot(<span class="hljs-string">'/home'</span>)),
      );
    <span class="hljs-keyword">const</span> showHome$$ = showHome$.subscribe();
    <span class="hljs-built_in">this</span>.subscriptions.push(showHome$$);
  }

  <span class="hljs-keyword">public</span> ngOnDestroy() {
    <span class="hljs-built_in">this</span>.subscriptions.forEach(<span class="hljs-function"><span class="hljs-params">subscription</span> =&gt;</span> subscription.unsubscribe());
  }
</code></pre><p>Fortunately, in my case, this code lives in “app.component.ts”, which means the component will never get destroyed or reinitialised unless the whole page is reloaded. So even without applying any fix, I am safe for memory leaks.</p>
<p>Thanks for reading.</p>
<p>I hope you find this article useful, and as usual, please leave me a comment here or DM me on Twitter if you have any questions.</p>
<p>Twitter: <a target="_blank" href="https://twitter.com/_pazel">_pazel</a></p>
]]></content:encoded></item><item><title><![CDATA[Build a PWA Map application using Ionic and Leaflet 🗺 🧭]]></title><description><![CDATA[Hello friends 👋 
Parham here, with another step-by-step guide.
This time, we will build an Ionic PWA app that shows a map, but that is not all.
A quick preview of what is the final results:
https://www.youtube.com/watch?v=jN44U2voLwc
Here are the ap...]]></description><link>https://pazel.dev/ionic-angular-leaflet-pwa-offline-maps-part-1</link><guid isPermaLink="true">https://pazel.dev/ionic-angular-leaflet-pwa-offline-maps-part-1</guid><category><![CDATA[Ionic Framework]]></category><category><![CDATA[map]]></category><category><![CDATA[PWA]]></category><category><![CDATA[Angular]]></category><category><![CDATA[Google]]></category><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Sat, 17 Jul 2021 17:17:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1627562486224/h-zDHVUZM.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello friends 👋 </p>
<p>Parham here, with another step-by-step guide.
This time, we will build an Ionic PWA app that shows a map, but that is not all.</p>
<p>A quick preview of what is the final results:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=jN44U2voLwc">https://www.youtube.com/watch?v=jN44U2voLwc</a></div>
<p>Here are the app features, what you can expect to learn implementing each part and the difficulty level of each part:</p>
<ol>
<li><h4 id="create-a-blank-app-using-ionic-cli">Create a blank app using Ionic CLI.</h4>
<p>You will learn how to use Ionic CLI to create an Ionic/Angular app. (Difficulty level: simple)</p>
</li>
<li><h4 id="adding-a-leaflet-map-to-your-ionic-app">Adding a leaflet map to your Ionic app.</h4>
<p>You will learn how to implement a basic map integration with Ionic, Leaflet &amp; Angular. (Difficulty level: simple)</p>
</li>
<li><h4 id="add-a-base-layer-switcher-to-switch-between-different-base-maps">Add a base layer switcher to switch between different base maps</h4>
<p>You will learn how to interact with basic Leaflet APIs like <code>TileLayer</code>, <code>LayerGroup</code> and Map methods. (Difficulty level: Intermediate)</p>
</li>
<li><h4 id="geolocation-api-integration-to-use-device-gps-and-find-user-current-location">Geolocation API integration to use device GPS and find user current location.</h4>
<p>You will learn how to use Geolocation API to find device locations and handle related errors. (Difficulty level: Intermediate)</p>
</li>
<li><h4 id="address-search-to-find-a-location-using-google-address-search">Address search to find a location using google address search.</h4>
<p>You will learn how to use Google Places APIs to address search and find the coordinates related to an address. (Difficulty level: Intermediate)</p>
</li>
<li><h4 id="make-it-a-pwa">Make it a PWA</h4>
<p>You will learn how to use <code>@angular/pwa</code> to make your app an installable PWA. (Difficulty level: Intermediate)</p>
</li>
<li><h4 id="bring-it-all-home">Bring it all home</h4>
<p>What is the point of building all this if we cannot show it off? Let's deploy your new app using <a target="_blank" href="https://vercel.com/">Vercel</a> and show it off to our friends 😀 (Difficulty level: simple)</p>
</li>
</ol>
<p>Here is the demo: <a target="_blank" href="https://ionic-angular-leaflet-offline-map-pwa-one.vercel.app/#/home">demo deployed using Vercel</a></p>
<p>Here is the Github code repository: <a target="_blank" href="https://github.com/pazel-io/ionic-angular-leaflet-offline-map-pwa.git">ionic-angular-leaflet-offline-map-pwa</a></p>
<h2 id="lets-start">Let's start</h2>
<h3 id="1-create-the-base-app-using-ionic-cli">1. Create the base app using Ionic CLI</h3>
<p>If you have not installed Ionic CLI before, please head to https://ionicframework.com/docs/intro/cli and follow the instructions to install the CLI.</p>
<p>Next, let's create an app using CLI by running ionic start <code>ionic-leaflet-offline-map-pwa</code>. CLI should prompt you to choose the front end tech and more options.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626507364875/dfkLCo_Mi.png" alt="Screen Shot 2021-07-17 at 4.33.46 pm.png" /></p>
<p>I am going with Angular and the blank starter project. Ionic CLI asks if you like Capacitor integration. It's not required for this tutorial.
After selecting the options, CLI will download all required npm packages. (this might take few minutes)</p>
<p>Ionic CLI asks if you would like a free Ionic account after the npm package install. It's not required for this tutorial. You will eventually see some logs indicating that the setup is done.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626507817164/zP-e-tWTx.png" alt="Screen Shot 2021-07-17 at 5.43.03 pm.png" /></p>
<p>Let's cd to the new project we just created and run <code>ionic serve</code>. This will run a local web server and open the app in your default browser. (by default port 8100)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626508059761/ZkYSS_NaF.png" alt="Screen Shot 2021-07-17 at 4.40.38 pm.png" /></p>
<h3 id="2-add-a-map-using-leaflet">2. Add a map using Leaflet</h3>
<p><a target="_blank" href="https://leafletjs.com">Leaflet</a> is an open-source JavaScript library for mobile-friendly interactive maps. It offers heaps of cool features to work with maps.
Using the features we need to show the map, and we are gonna just scratch the surface. So it's relatively easy.</p>
<p>We use the Angular Leaflet wrapper for easier integration here. We will use the <code>@asymmetrik/ngx-leaflet</code> project. This package is not an official part of Angular, but it is reliable, and I have used it in many prod apps.</p>
<p>Run <code>npm i leaflet @asymmetrik/ngx-leaflet</code> to install both <code>leaflet</code> and <code>ngx-leaflet</code>.</p>
<p>We love TypeScript so let's add appropriate typings for Leaflet. </p>
<p>Run <code>npm i @types/leaflet -D</code> to install the typings as a dev dependency.</p>
<p>OK, we are done with the installation. Let's add a map to our Angular app!</p>
<p>I will add the map to our <code>HomePage(home.page.ts)</code> component. This is easy.</p>
<h4 id="add-leafletmodule-to-the-imports-array-in-homepagemodulehomemodulets">Add <code>LeafletModule</code> to the <code>imports</code> array in <code>HomePageModule(home.module.ts)</code></h4>
<pre><code><span class="hljs-keyword">import</span> { NgModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { CommonModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/common'</span>;
<span class="hljs-keyword">import</span> { LeafletModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@asymmetrik/ngx-leaflet'</span>;
<span class="hljs-keyword">import</span> { IonicModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@ionic/angular'</span>;
<span class="hljs-keyword">import</span> { FormsModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/forms'</span>;
<span class="hljs-keyword">import</span> { HomePage } <span class="hljs-keyword">from</span> <span class="hljs-string">'./home.page'</span>;

<span class="hljs-keyword">import</span> { HomePageRoutingModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'./home-routing.module'</span>;


@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    IonicModule,
    LeafletModule,
    HomePageRoutingModule
  ],
  declarations: [HomePage]
})
<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HomePageModule</span> {}</span>
</code></pre><h4 id="add-map-options-and-onmapready">Add map <code>options</code> and <code>OnMapReady</code></h4>
<p><code>options</code> define things like map centre, maxZoom, baseLayer and more.</p>
<p><code>onMapReady</code> will be called when the leaflet map is ready and receive the map instance. We will need this instance to interact with the map later.</p>
<pre><code><span class="hljs-keyword">import</span> { Component } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-built_in">Map</span> <span class="hljs-keyword">as</span> LMap, TileLayer } <span class="hljs-keyword">from</span> <span class="hljs-string">'leaflet'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-home'</span>,
  templateUrl: <span class="hljs-string">'home.page.html'</span>,
  styleUrls: [<span class="hljs-string">'home.page.scss'</span>],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> HomePage {

  <span class="hljs-keyword">public</span> map: LMap;
  <span class="hljs-keyword">public</span> center = [
    <span class="hljs-number">-28.690259</span>,
    <span class="hljs-number">131.5190514</span>,
  ];

  <span class="hljs-keyword">public</span> options = {
    zoom: <span class="hljs-number">5</span>,
    maxZoom: <span class="hljs-number">18</span>,
    zoomControl: <span class="hljs-literal">false</span>,
    preferCanvas: <span class="hljs-literal">true</span>,
    attributionControl: <span class="hljs-literal">true</span>,
    center: <span class="hljs-built_in">this</span>.center,
    layers: [
      <span class="hljs-keyword">new</span> TileLayer(<span class="hljs-string">'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'</span>)
    ],
  };

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {}

  <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> onMapReady(lMap: LMap) {
    <span class="hljs-built_in">this</span>.map = lMap;
    <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> lMap.invalidateSize(<span class="hljs-literal">true</span>), <span class="hljs-number">0</span>);
  }

}
</code></pre><h4 id="add-the-map-container-in-our-homepagehtml">Add the map container in our <code>home.page.html</code>.</h4>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">ion-header</span> [<span class="hljs-attr">translucent</span>]=<span class="hljs-string">"true"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ion-toolbar</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ion-title</span>&gt;</span>
      Ionic Leaflet Map
    <span class="hljs-tag">&lt;/<span class="hljs-name">ion-title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ion-toolbar</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ion-header</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">ion-content</span> [<span class="hljs-attr">fullscreen</span>]=<span class="hljs-string">"true"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"leaflet"</span> <span class="hljs-attr">leaflet</span>
       [<span class="hljs-attr">leafletOptions</span>]=<span class="hljs-string">"options"</span>
       (<span class="hljs-attr">leafletMapReady</span>)=<span class="hljs-string">"onMapReady($event)"</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ion-content</span>&gt;</span>
</code></pre><h4 id="add-the-leaflet-css-to-the-list-of-our-global-css">Add the leaflet CSS to the list of our global CSS.</h4>
<p>Open the <code>angular.json</code> file in the root of the project and update line 34 from </p>
<p><code>"styles": ["src/theme/variables.scss", "src/global.scss"],</code>
to</p>
<pre><code><span class="hljs-string">"styles"</span>: [<span class="hljs-string">"src/theme/variables.scss"</span>, <span class="hljs-string">"src/global.scss"</span>, <span class="hljs-string">"./node_modules/leaflet/dist/leaflet.css"</span>],
</code></pre><p>and add the map container CSS to <code>home.page.scss</code>. The updated file looks like this.</p>
<pre><code><span class="hljs-selector-class">.leaflet</span> {
  <span class="hljs-attribute">height</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">z-index</span>: <span class="hljs-number">0</span>;
}
</code></pre><p>Restart your server to make sure the new CSS loads properly. You should see a screen like this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626519110817/a2Yt9ZGLh.png" alt="Screen Shot 2021-07-17 at 8.51.21 pm.png" /></p>
<p>Congrats!! 🎉 👏 </p>
<p>Now we integrated a map into our Ionic app. </p>
<h3 id="3-add-the-ability-to-switch-base-maps">3. Add the ability to switch base maps</h3>
<p>We have already added one base map in the <code>options</code> we defined.
I want to change it and use Cycling and Transport base maps and use a button to switch between them.</p>
<p>Let's be fancy and add a FAB button from Ionic to do the layer switching. Go ahead and add a FAB button with a list of options inside your <code>&lt;ion-content&gt;</code>. The updated code looks like this.</p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">ion-header</span> [<span class="hljs-attr">translucent</span>]=<span class="hljs-string">"true"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ion-toolbar</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ion-title</span>&gt;</span>
      Ionic Leaflet Map
    <span class="hljs-tag">&lt;/<span class="hljs-name">ion-title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ion-toolbar</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ion-header</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">ion-content</span> [<span class="hljs-attr">fullscreen</span>]=<span class="hljs-string">"true"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ion-fab</span> <span class="hljs-attr">vertical</span>=<span class="hljs-string">"top"</span> <span class="hljs-attr">horizontal</span>=<span class="hljs-string">"end"</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"fixed"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ion-fab-button</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ion-icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"layers-outline"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ion-icon</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ion-fab-button</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ion-fab-list</span> <span class="hljs-attr">side</span>=<span class="hljs-string">"bottom"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ion-fab-button</span> <span class="hljs-attr">color</span>=<span class="hljs-string">"danger"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">ion-icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"bicycle"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ion-icon</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">ion-fab-button</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ion-fab-button</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">ion-icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"car"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ion-icon</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">ion-fab-button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ion-fab-list</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ion-fab</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"leaflet"</span> <span class="hljs-attr">leaflet</span>
       [<span class="hljs-attr">leafletOptions</span>]=<span class="hljs-string">"options"</span>
       (<span class="hljs-attr">leafletMapReady</span>)=<span class="hljs-string">"onMapReady($event)"</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ion-content</span>&gt;</span>
</code></pre><p>The updated screen will have a FAB button on the top right with two options (cycling &amp; transport) for selection.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626523400233/gCLhLtpF3.png" alt="Screen Shot 2021-07-17 at 10.02.06 pm.png" /></p>
<p>We need to add the code to actually switch the base map when we click our new buttons. Don't worry. I have got you!</p>
<p>I will show you the updated code, and we will go through the changes one by one.
This is the new <code>home.page.ts</code> file.</p>
<pre><code><span class="hljs-keyword">import</span> { Component } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { LayerGroup, <span class="hljs-built_in">Map</span> <span class="hljs-keyword">as</span> LMap, TileLayer } <span class="hljs-keyword">from</span> <span class="hljs-string">'leaflet'</span>;
<span class="hljs-keyword">import</span> { BaseLayer } <span class="hljs-keyword">from</span> <span class="hljs-string">'./BaseLayer.enum'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-home'</span>,
  templateUrl: <span class="hljs-string">'home.page.html'</span>,
  styleUrls: [<span class="hljs-string">'home.page.scss'</span>],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> HomePage {

  <span class="hljs-keyword">public</span> map: LMap;
  <span class="hljs-keyword">public</span> center = [
    <span class="hljs-number">-37.8182711</span>,
    <span class="hljs-number">144.9648731</span>
  ];

  <span class="hljs-keyword">public</span> options = {
    zoom: <span class="hljs-number">15</span>,
    maxZoom: <span class="hljs-number">18</span>,
    zoomControl: <span class="hljs-literal">false</span>,
    preferCanvas: <span class="hljs-literal">true</span>,
    attributionControl: <span class="hljs-literal">true</span>,
    center: <span class="hljs-built_in">this</span>.center,
  };

  <span class="hljs-keyword">public</span> baseMapUrls = {
    [BaseLayer.cycling]: <span class="hljs-string">'http://c.tile.thunderforest.com/cycle/{z}/{x}/{y}.png'</span>,
    [BaseLayer.transport]: <span class="hljs-string">'http://c.tile.thunderforest.com/transport/{z}/{x}/{y}.png'</span>,
  };

  <span class="hljs-keyword">public</span> selectedBaseLayer = BaseLayer.cycling;

  <span class="hljs-keyword">public</span> baseLayer = BaseLayer;

  <span class="hljs-keyword">private</span> baseMapLayerGroup = <span class="hljs-keyword">new</span> LayerGroup();

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
  }

  <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> onMapReady(lMap: LMap) {
    <span class="hljs-built_in">this</span>.map = lMap;
    <span class="hljs-built_in">this</span>.map.addLayer(<span class="hljs-built_in">this</span>.baseMapLayerGroup);
    <span class="hljs-built_in">this</span>.switchBaseLayer(<span class="hljs-built_in">this</span>.selectedBaseLayer);
    <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> lMap.invalidateSize(<span class="hljs-literal">true</span>), <span class="hljs-number">0</span>);
  }

  <span class="hljs-keyword">public</span> switchBaseLayer(baseLayerName: <span class="hljs-built_in">string</span>) {
    <span class="hljs-built_in">this</span>.baseMapLayerGroup.clearLayers();
    <span class="hljs-keyword">const</span> baseMapTileLayer = <span class="hljs-keyword">new</span> TileLayer(<span class="hljs-built_in">this</span>.baseMapUrls[baseLayerName]);
    <span class="hljs-built_in">this</span>.baseMapLayerGroup.addLayer(baseMapTileLayer);
    <span class="hljs-built_in">this</span>.selectedBaseLayer = BaseLayer[baseLayerName];
  }

}
</code></pre><p>I removed the <code>layers</code> array from the map <code>options</code>, and instead, I have added the baseMapUrls, an object. Keys are the map name, and values are the URL for map tiles. For keys, I am using a TypeScript Enum to make it easier when reusing the same string. Enum is in a new file called <code>BaseLayer.enum.ts</code>, and here is the code:</p>
<pre><code><span class="hljs-keyword">export</span> <span class="hljs-keyword">enum</span> BaseLayer {
  cycling = <span class="hljs-string">'cycling'</span>,
  transport = <span class="hljs-string">'transport'</span>,
}
</code></pre><p>Next, we have the <code>selectedBaseLayer</code> property to keep track of which the user selects the base layer. I have initialised it with the cycling base map using the same Enum.</p>
<p>Next, we have the <code>baseLayer</code> property pointing to the <code>BaseLayer</code> enum. This is to make the enum available to our map HTML template and use it there.</p>
<p>Next, we have the <code>private baseMapLayerGroup = new LayerGroup();</code>.
<code>LayerGroup</code> is a Leaflet way to group multiple layers and separate them from the rest of the layers on the map. Using it here makes it easier to manage base layers. We will use more LayerGroups later to add the address search and GPS search results.</p>
<p>Next, we have the <code>switchBaseLayer</code> method. It takes a base layer name and can switch the base layer. It first clears any layers in the layer group and then adds the new layer by finding the config from the <code>baseMapUrls</code> object. Notice that the layer we add is of type <code>TileLayer</code>. This special type of layer in Leaflet world can show tiles used for the base map.</p>
<p>And here are the changes you need in the HTML. Basically adding the <code>click</code> functions to buttons. Also, check the <code>selectedBaseLayer</code> to highlight the colour of the button representing selected base layer by changing it to red(danger). Notice I am using the same <code>BaseLayer</code> enum values to maximise reuse and not repeat myself.</p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">ion-header</span> [<span class="hljs-attr">translucent</span>]=<span class="hljs-string">"true"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ion-toolbar</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ion-title</span>&gt;</span>
      Ionic Leaflet Map
    <span class="hljs-tag">&lt;/<span class="hljs-name">ion-title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ion-toolbar</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ion-header</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">ion-content</span> [<span class="hljs-attr">fullscreen</span>]=<span class="hljs-string">"true"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ion-fab</span> <span class="hljs-attr">vertical</span>=<span class="hljs-string">"top"</span> <span class="hljs-attr">horizontal</span>=<span class="hljs-string">"end"</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"fixed"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ion-fab-button</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ion-icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"layers-outline"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ion-icon</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ion-fab-button</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ion-fab-list</span> <span class="hljs-attr">side</span>=<span class="hljs-string">"bottom"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ion-fab-button</span> [<span class="hljs-attr">color</span>]=<span class="hljs-string">"selectedBaseLayer === baseLayer.cycling ? 'danger' : 'primary'"</span>
                      (<span class="hljs-attr">click</span>)=<span class="hljs-string">"switchBaseLayer(baseLayer.cycling)"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">ion-icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"bicycle"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ion-icon</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">ion-fab-button</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ion-fab-button</span> [<span class="hljs-attr">color</span>]=<span class="hljs-string">"selectedBaseLayer === baseLayer.transport ? 'danger' : 'primary'"</span>
                      (<span class="hljs-attr">click</span>)=<span class="hljs-string">"switchBaseLayer(baseLayer.transport)"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">ion-icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"car"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ion-icon</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">ion-fab-button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ion-fab-list</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ion-fab</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"leaflet"</span> <span class="hljs-attr">leaflet</span>
       [<span class="hljs-attr">leafletOptions</span>]=<span class="hljs-string">"options"</span>
       (<span class="hljs-attr">leafletMapReady</span>)=<span class="hljs-string">"onMapReady($event)"</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ion-content</span>&gt;</span>
</code></pre><h3 id="4-use-device-gps-and-find-the-user-current-location">4. Use device GPS and find the user current location</h3>
<p>We will use <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API">Geolocation API</a> to find the device location. This is a standard web API.
For privacy reasons, users will be asked to grant access to their location. So the user can grant access or deny, and we need to handle the potential errors in this process.</p>
<p>First, let's add a method to read the device's location.</p>
<pre><code>  public <span class="hljs-keyword">async</span> locate() {
    <span class="hljs-built_in">this</span>.locationLayerGroup.clearLayers();
    <span class="hljs-keyword">if</span> (!navigator.geolocation) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Geolocation is not supported by your browser'</span>);
      <span class="hljs-keyword">return</span>;
    }
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.presentLoading();
    navigator.geolocation.getCurrentPosition(
      <span class="hljs-function"><span class="hljs-params">(position)</span> =&gt;</span> <span class="hljs-built_in">this</span>.onLocationSuccess(position),
      <span class="hljs-function"><span class="hljs-params">(error)</span> =&gt;</span> <span class="hljs-built_in">this</span>.onLocateError(error),
      {enableHighAccuracy: <span class="hljs-literal">true</span>}
    );
  }
</code></pre><p>As you might have noticed, I have added another <code>LayerGroup</code> for the GPS location to the map called <code>locationLayerGroup</code>.</p>
<p>Next, I am checking if the <code>navigator.geolocation</code> is supported. Notice, If it is not supported, I log an error and use the <code>return</code> statement to exit the function. This is a nicer alternative compared to doing if/else and makes your code more readable.</p>
<p>If it is supported, I show a loading message. Then, I call <code>getCurrentPosition</code>, which returns the device's current location (another method if you like to watch the location, which is good for tracking use case).</p>
<p><code>getCurrentPosition</code> takes three parameters. First, the success callback function, which is called when the location is successfully fetched. The second is the error callback which will be called if there is an error. Like location access denied or timeout error if GPS satellite is not available. And third, the <code>PositionOptions</code>, which I use to enable the <code>HighAccuracy</code> location. Enabling this option will cause the location reading to take longer but will make it more accurate.</p>
<p>Here is the code for success callback</p>
<pre><code>  <span class="hljs-keyword">private</span> onLocationSuccess(position: GeolocationPosition) {
    <span class="hljs-keyword">const</span> {accuracy, latitude, longitude} = position.coords;
    <span class="hljs-keyword">const</span> latlng = [latitude, longitude];
    <span class="hljs-keyword">this</span>.hideLoading();
    <span class="hljs-keyword">this</span>.map.setView(latlng, <span class="hljs-number">18</span>);
    <span class="hljs-keyword">const</span> accuracyValue = accuracy &gt; <span class="hljs-number">1000</span> ? accuracy / <span class="hljs-number">1000</span> : accuracy;
    <span class="hljs-keyword">const</span> accuracyUnit = accuracy &gt; <span class="hljs-number">1000</span> ? <span class="hljs-string">'km'</span> : <span class="hljs-string">'m'</span>;
    placeLocationMarker(<span class="hljs-keyword">this</span>.locationLayerGroup, latlng, `Accuracy <span class="hljs-keyword">is</span> ${accuracyValue} ${accuracyUnit}`);
    <span class="hljs-keyword">const</span> locationCircle = circle(latlng, accuracy);
    <span class="hljs-keyword">this</span>.locationLayerGroup.addLayer(locationCircle);
  }
</code></pre><p>Here I read some properties from the <code>GeolocationPosition</code> I receive. I use the latitude and longitude to create a Leaflet LatLng array that can centre the map. I use the map's <code>setView</code> method for that, passing a LatLng and zoom level (18).</p>
<p>The accuracy we receive here is in metres, so I convert it to KM if it's bigger than 1000 so it's more readable when presented to the user.</p>
<p>I use <code>placeLocationMarker</code> to display a marker on the map for device location. I have extracted this function because I want to show a tooltip on the marker, which shows the accuracy. And since this is a Leaflet tooltip and the whole DOM creation is handled by Leaflet, there is a bit of complication around how to handle the touch events. This complexity is handled in<code>placeLocationMarker</code> function.</p>
<p>And at last, I use the Leaflet <code>circle</code> function to draw a circle on the map, which shows the accuracy visually to the user. This circle is centred on the device location, and the radius is as big as accuracy.</p>
<p>Before you get too bored, here is the result</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626538968710/E2lbMrY6w.png" alt="Screen Shot 2021-07-18 at 2.22.01 am.png" /></p>
<p>Notice that I am using Chrome Devtools to fake the GPS location. You can access this feature in the <code>console</code> tab by clicking three dots next to Console and choose <code>Sensors</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626539302070/X0WhTywnr.png" alt="Screen Shot 2021-07-18 at 2.26.08 am.png" /></p>
<p>Ok, Let's have a quick look at the error callback. This one is simple. On error, we hide the loading and use the Ionic's Alert component to notify the user about the problem.
3 errors can happen. PERMISSION_DENIED, TIMEOUT , POSITION_UNAVAILABLE.
The first two are obvious, and the third can happen when there is faulty GPS hardware. For each error, you get a message and code. If you need to customise the message, use the code and draft your own message.</p>
<pre><code>  <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> <span class="hljs-title">onLocateError</span>(<span class="hljs-params">error</span>)</span> {
    <span class="hljs-keyword">this</span>.hideLoading();
    <span class="hljs-keyword">const</span> alert = <span class="hljs-keyword">await</span> <span class="hljs-keyword">this</span>.alertController.create({
      header: <span class="hljs-string">'GPS error'</span>,
      message: error.message,
      buttons: [<span class="hljs-string">'OK'</span>]
    });

    <span class="hljs-keyword">await</span> alert.present();
  }
</code></pre><p>And finally, here is the code for the <code>placeLocationMarker</code> function. This code is located in a separate file with the same name. It does three things.</p>
<ul>
<li>Creates a Leaflet marker using a custom icon.</li>
<li>Binds a leaflet popup to the marker that shows accuracy value and a delete link button. Why do we need the delete? Because when the user adds a marker to the map, they need a way to remove it when they are done.</li>
<li>Listen to the click event on a specific marker popup to handle the delete button click. As mentioned, the DOM operations here are totally happening outside of the Angular world, so I use RxJS <code>fromEvent</code> function to filter and receive the click events from this delete button.</li>
</ul>
<p>The subscription to the click event only happens when the popup is open, and it will unsubscribe as soon as it is closed.</p>
<p>Another good practice here is extracting the <code>placeLocationMarker</code>. This has made it as pure as possible. So this function only relies on its inputs to operate.</p>
<pre><code><span class="hljs-keyword">import</span> { LatLng, LayerGroup, marker, Marker } <span class="hljs-keyword">from</span> <span class="hljs-string">'leaflet'</span>;
<span class="hljs-keyword">import</span> { fromEvent, Subscription } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { filter, map, tap } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs/operators'</span>;
<span class="hljs-keyword">import</span> { markerIcon } <span class="hljs-keyword">from</span> <span class="hljs-string">'./markerIcon'</span>;

const subscribeToDeleteLocationMarker = (id: string, marco: Marker, layerGroup: LayerGroup): Subscription =&gt; {
  <span class="hljs-keyword">return</span> fromEvent(<span class="hljs-built_in">document</span>, <span class="hljs-string">'click'</span>)
    .pipe(
      tap(<span class="hljs-function"><span class="hljs-params">(event)</span> =&gt;</span> event.stopPropagation()),
      map(<span class="hljs-function"><span class="hljs-params">(event)</span> =&gt;</span> event.target),
      filter(<span class="hljs-function"><span class="hljs-params">(target: HTMLElement)</span> =&gt;</span> target.id === id),
    )
    .subscribe(<span class="hljs-function"><span class="hljs-params">($event)</span> =&gt;</span> layerGroup.clearLayers());
};

const bindMarkerPopup = <span class="hljs-function"><span class="hljs-params">(marco: Marker, text: string, layerGroup: LayerGroup)</span> =&gt;</span> {
  const id = `<span class="javascript">location-marker-${<span class="hljs-built_in">Date</span>.now()}</span>`;
  marco.bindPopup(`<span class="javascript">
        &lt;p&gt;${text}&lt;/p&gt;
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"location-marker-popup__buttons"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"location-marker-popup__button"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"${id}"</span>&gt;</span>delete<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
      </span>`,          {
    maxWidth: <span class="hljs-number">200</span>,
    maxHeight: <span class="hljs-number">120</span>,
    className: <span class="hljs-string">'location-marker-popup'</span>,
  });
  let deleteMarkerSubscription;
  marco.<span class="hljs-literal">on</span>(<span class="hljs-string">'popupopen'</span>, <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> deleteMarkerSubscription = subscribeToDeleteLocationMarker(id, marco, layerGroup));
  marco.<span class="hljs-literal">on</span>(<span class="hljs-string">'popupclose'</span>, <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> deleteMarkerSubscription.unsubscribe());
  marco.openPopup();
};

<span class="hljs-keyword">export</span> const placeLocationMarker = <span class="hljs-function"><span class="hljs-params">(layerGroup: LayerGroup, latLng: LatLng, text: string)</span> =&gt;</span> {
  layerGroup.clearLayers();
  const marco = marker(latLng, {icon: markerIcon()});
  layerGroup.addLayer(marco);
  bindMarkerPopup(marco, text, layerGroup);
};
</code></pre><h3 id="5-use-google-places-autocomplete-servicehttpsdevelopersgooglecommapsdocumentationjavascriptreferenceplaces-autocomplete-service-to-find-an-address-and-show-it-on-the-map">5. Use <a target="_blank" href="https://developers.google.com/maps/documentation/javascript/reference/places-autocomplete-service">Google Places Autocomplete Service</a> to find an address and show it on the map</h3>
<p>We will start by adding the Google Maps JavaScript API. To do this, you need a Google account and a Developer API Key. </p>
<p>Follow the instructions on the following instruction to get an API key.
https://developers.google.com/maps/documentation/javascript/get-api-key</p>
<p>You will need to:</p>
<ul>
<li>Create a new project
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627103334267/FtrWoBoNr.png" alt="Screen Shot 2021-07-24 at 3.08.25 pm.png" /></li>
<li>Get an API key. Make sure to restrict the key to APIs you will add and also restrict by HTTP referer. So you can make sure no one can take advantage of your API key and use your quota.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627102204128/7aBv8MmzT.png" alt="Screen Shot 2021-07-24 at 2.44.06 pm.png" />
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627102256247/qWeMiWwkK.png" alt="Screen Shot 2021-07-24 at 2.45.30 pm.png" /></li>
<li>Enable Places API and Maps API in the Google Cloud Console.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627102280111/W3apZFTSV.png" alt="Screen Shot 2021-07-24 at 2.48.22 pm.png" />
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627102322180/tZZanXqFB.png" alt="Screen Shot 2021-07-24 at 2.46.21 pm.png" />
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627102353233/XtgpQ0rSo.png" alt="Screen Shot 2021-07-24 at 2.49.10 pm.png" /></li>
<li>Enable billing for the services you just added. Google cloud offers <a target="_blank" href="https://cloud.google.com/maps-platform/pricing">$200 free monthly usage</a> which can be sufficient for testing or low usage websites. Plus, you can add quota usage alerts if you want to make sure your bill will not get too big.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627102575531/fH2SfCUFR.png" alt="Screen Shot 2021-07-24 at 2.54.58 pm.png" />
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627102592989/cdmaZc5iy.png" alt="Screen Shot 2021-07-24 at 2.55.30 pm.png" /></li>
</ul>
<p>This process can be a bit tricky, so please read the instruction carefully. Let me know if you get stuck in the comments. I will try to help you out.</p>
<p>After you have done the steps above, the next step is to add the Google Maps JavaScript API to your app. Open <code>index.html</code> and add this script.</p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">async</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&amp;libraries=places"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre><p>Next, install proper typings for Google APIs using <code>npm i -D @types/google.maps</code>. This will let us use the <code>google</code> global namespace in TypeScript without issues. There are multiple to use the TS definitions. It's <a target="_blank" href="https://developers.google.com/maps/documentation/javascript/using-typescript#configuration_optional">described here</a>. I use the second method adding this comment at the top of my file.</p>
<pre><code>/// &lt;reference <span class="hljs-keyword">types</span>="google.maps" /&gt;
</code></pre><p>There are multiple ways to add the Google address search (<a target="_blank" href="https://developers.google.com/maps/documentation/javascript/places-autocomplete">Places Autocomplete</a>) to your app.</p>
<ol>
<li><a target="_blank" href="https://developers.google.com/maps/documentation/javascript/places-autocomplete#add-autocomplete">Adding an Autocomplete widget</a></li>
<li><a target="_blank" href="https://developers.google.com/maps/documentation/javascript/places-autocomplete#places-searchbox">Adding a SearchBox widget</a></li>
<li><a target="_blank" href="https://developers.google.com/maps/documentation/javascript/places-autocomplete#place_autocomplete_service">Programmatically retrieving Place Autocomplete Service predictions</a></li>
</ol>
<p>I will use the third method so I can create my own UI/UX.
I will create a component to separate this. Let's call it <code>addressSearchComponent</code>.</p>
<p>Go ahead and generate a new component with the same name using Angular CLI using the following command. </p>
<blockquote>
<p>Note: It's always a good idea to use the <code>--dryRun</code> flag if you are not sure. This will give you a chance to review the changes before it happens to avoid mistakes.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626960989817/38H-1p45o.png" alt="Screen Shot 2021-07-22 at 11.36.13 pm.png" /></p>
<pre><code>npx ng g component addressSearchComponent --create-<span class="hljs-keyword">module</span> --spec=<span class="hljs-literal">false</span>
</code></pre><p>Next, we need to create an instance of <code>AutocompleteService</code>. This service provides <a target="_blank" href="https://developers.google.com/maps/documentation/javascript/places-autocomplete#place_autocomplete_service">two methods</a>.</p>
<p>I will use the <code>AutocompleteService.getPlacePredictions()</code> method. This method takes a search query and returns an array of <a target="_blank" href="https://developers.google.com/maps/documentation/javascript/reference/places-autocomplete-service#AutocompletePrediction">prediction objects</a>. </p>
<p>Each suggestion has a <code>place_id</code> associated with it which can be used to get more details using  <code>PlacesService.getDetails()</code> method. I will use the second service call the details of the place, including the <code>geometry</code>, which has the latitude and longitude of a place to show it on the map.</p>
<p>Here is the final result:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=Ay59gLnORF4">https://www.youtube.com/watch?v=Ay59gLnORF4</a></div>
<p>Here is the code for what I explained.</p>
<pre><code><span class="hljs-comment">/// &lt;reference types="google.maps" /&gt;</span>

<span class="hljs-keyword">import</span> { Component, EventEmitter, Output, ViewChild } from <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> AutocompleteResponse = google.maps.places.AutocompleteResponse;
<span class="hljs-keyword">import</span> AutocompletePrediction = google.maps.places.AutocompletePrediction;

<span class="hljs-meta">@Component({
  selector: <span class="hljs-meta-string">'app-address-search-component'</span>,
  templateUrl: <span class="hljs-meta-string">'./address-search-component.component.html'</span>,
  styleUrls: [<span class="hljs-meta-string">'./address-search-component.component.scss'</span>],
})</span>
export <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AddressSearchComponentComponent</span> </span>{
  <span class="hljs-meta">@ViewChild(<span class="hljs-meta-string">'result'</span>)</span> result: any;
  <span class="hljs-meta">@Output()</span> placeSelect: EventEmitter&lt;any&gt; = new EventEmitter();
  <span class="hljs-keyword">public</span> predictions: AutocompletePrediction[] = [];
  <span class="hljs-keyword">public</span> predictionsVisible = <span class="hljs-literal">false</span>;
  <span class="hljs-keyword">private</span> autocompleteService: google.maps.places.AutocompleteService;
  <span class="hljs-keyword">private</span> placesService: google.maps.places.PlacesService;
  <span class="hljs-keyword">private</span> sessionToken: google.maps.places.AutocompleteSessionToken;

  <span class="hljs-keyword">constructor</span>() {
  }

  <span class="hljs-keyword">public</span> async lookupAddress(event) {
    <span class="hljs-keyword">if</span>(event.target.value === <span class="hljs-string">''</span>){
      <span class="hljs-keyword">return</span>;
    }
    <span class="hljs-keyword">const</span> addressQuery = event.target.value;
    <span class="hljs-keyword">const</span> autocompleteResponse = await <span class="hljs-keyword">this</span>.getPlacePredictions(addressQuery);
    <span class="hljs-keyword">this</span>.predictions = autocompleteResponse.predictions;
    <span class="hljs-keyword">this</span>.predictionsVisible = <span class="hljs-keyword">this</span>.predictions.length &gt; <span class="hljs-number">0</span>;
    console.log(<span class="hljs-string">'predictions'</span>, autocompleteResponse.predictions);
  }

  <span class="hljs-keyword">public</span> showPredictions() {
    <span class="hljs-keyword">this</span>.predictionsVisible = <span class="hljs-keyword">this</span>.predictions.length &gt; <span class="hljs-number">0</span>;
  }

  <span class="hljs-keyword">public</span> clearPredictions() {
    <span class="hljs-keyword">this</span>.predictionsVisible = <span class="hljs-literal">false</span>;
    <span class="hljs-keyword">this</span>.predictions = [];
  }

  <span class="hljs-keyword">public</span> getPlaceDetails(placeId: string) {
    <span class="hljs-keyword">this</span>.predictionsVisible = <span class="hljs-literal">false</span>;
    <span class="hljs-keyword">this</span>.getPlacesService().getDetails({
      placeId,
      sessionToken: <span class="hljs-keyword">this</span>.getSessionToken(),
      fields: [<span class="hljs-string">'formatted_address'</span>, <span class="hljs-string">'geometry'</span>],
    }, (placeResult) =&gt; <span class="hljs-keyword">this</span>.placeSelect.emit(placeResult));
  }

  <span class="hljs-keyword">private</span> getPlacePredictions(addressQuery: string): Promise&lt;AutocompleteResponse&gt; {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.getAutocompleteService().getPlacePredictions({
      componentRestrictions: {country: [<span class="hljs-string">'au'</span>]},
      input: addressQuery,
      sessionToken: <span class="hljs-keyword">this</span>.getSessionToken(),
      types: [<span class="hljs-string">'address'</span>],
    });
  }

  <span class="hljs-keyword">private</span> getPlacesService() {
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.placesService) {
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.placesService;
    }
    <span class="hljs-keyword">this</span>.placesService = new google.maps.places.PlacesService(<span class="hljs-keyword">this</span>.result.nativeElement);
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.placesService;
  }

  <span class="hljs-keyword">private</span> getAutocompleteService() {
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.autocompleteService) {
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.autocompleteService;
    }
    <span class="hljs-keyword">this</span>.autocompleteService = new google.maps.places.AutocompleteService();
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.autocompleteService;
  }

  <span class="hljs-keyword">private</span> getSessionToken() {
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.sessionToken) {
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.sessionToken;
    }
    <span class="hljs-keyword">this</span>.sessionToken = new google.maps.places.AutocompleteSessionToken();
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.sessionToken;
  }
}
</code></pre><p>And here is the template and CSS files:</p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">ion-searchbar</span>
  <span class="hljs-attr">mode</span>=<span class="hljs-string">"ios"</span>
  <span class="hljs-attr">type</span>=<span class="hljs-string">"search"</span>
  <span class="hljs-attr">inputmode</span>=<span class="hljs-string">"search"</span>
  <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Enter address"</span>
  [<span class="hljs-attr">animated</span>]=<span class="hljs-string">"true"</span>
  (<span class="hljs-attr">ionChange</span>)=<span class="hljs-string">"lookupAddress($event)"</span>
  (<span class="hljs-attr">ionClear</span>)=<span class="hljs-string">"clearPredictions()"</span>
  (<span class="hljs-attr">ionFocus</span>)=<span class="hljs-string">"showPredictions()"</span>
&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ion-searchbar</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> #<span class="hljs-attr">result</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"display: none;"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ion-list</span> [<span class="hljs-attr">hidden</span>]=<span class="hljs-string">"!predictionsVisible"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ion-item</span> *<span class="hljs-attr">ngFor</span>=<span class="hljs-string">"let prediction of predictions"</span>
            (<span class="hljs-attr">click</span>)=<span class="hljs-string">"getPlaceDetails(prediction.place_id)"</span>&gt;</span>{{prediction.description}}<span class="hljs-tag">&lt;/<span class="hljs-name">ion-item</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ion-list</span>&gt;</span>
</code></pre><pre><code><span class="hljs-selector-pseudo">:host</span> {
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">60px</span>;
  <span class="hljs-attribute">top</span>: <span class="hljs-number">60px</span>;
  <span class="hljs-attribute">left</span>: <span class="hljs-number">5%</span>;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">90%</span>;
  <span class="hljs-attribute">z-index</span>: <span class="hljs-number">1000</span>;
  <span class="hljs-attribute">display</span>: block;
  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#121212</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">10px</span>;
}

<span class="hljs-selector-tag">ion-list</span> {
  <span class="hljs-attribute">margin-top</span>: -<span class="hljs-number">7px</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">15px</span> <span class="hljs-number">5px</span> <span class="hljs-number">5px</span> <span class="hljs-number">5px</span>;
  <span class="hljs-attribute">border-bottom-right-radius</span>: <span class="hljs-number">10px</span>;
  <span class="hljs-attribute">border-bottom-left-radius</span>: <span class="hljs-number">10px</span>;
}

<span class="hljs-selector-tag">ion-item</span> {
  <span class="hljs-attribute">--inner-padding-bottom</span>: <span class="hljs-number">5px</span>;
  <span class="hljs-attribute">--inner-padding-top</span>: <span class="hljs-number">10px</span>;
}
</code></pre><h3 id="6-make-it-a-pwa">6. Make it a PWA</h3>
<p>This step is easy because Angular CLI does all the heavy lifting for you.
Just run <code>npx ng add @angular/pwa</code>. This command will add all the required packages to your project, configure your service worker, and add the default assets for caching. The only file that you might need to interact with is the <code>ngsw-config.json</code>.</p>
<p>This file tells the Angular service worker, what assets to cache and what caching strategy to use.</p>
<p>For example, all JS files are cached eagerly when all the image assets are cached lazily.
You can read more about managing this file in Angular docs for <a target="_blank" href="https://angular.io/guide/service-worker-config#assetgroups">Service worker configuration</a>.</p>
<pre><code>{
  <span class="hljs-attr">"$schema"</span>: <span class="hljs-string">"./node_modules/@angular/service-worker/config/schema.json"</span>,
  <span class="hljs-attr">"index"</span>: <span class="hljs-string">"/index.html"</span>,
  <span class="hljs-attr">"assetGroups"</span>: [
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"app"</span>,
      <span class="hljs-attr">"installMode"</span>: <span class="hljs-string">"prefetch"</span>,
      <span class="hljs-attr">"resources"</span>: {
        <span class="hljs-attr">"files"</span>: [
          <span class="hljs-string">"/favicon.ico"</span>,
          <span class="hljs-string">"/index.html"</span>,
          <span class="hljs-string">"/manifest.webmanifest"</span>,
          <span class="hljs-string">"/*.css"</span>,
          <span class="hljs-string">"/*.js"</span>
        ]
      }
    },
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"assets"</span>,
      <span class="hljs-attr">"installMode"</span>: <span class="hljs-string">"lazy"</span>,
      <span class="hljs-attr">"updateMode"</span>: <span class="hljs-string">"prefetch"</span>,
      <span class="hljs-attr">"resources"</span>: {
        <span class="hljs-attr">"files"</span>: [
          <span class="hljs-string">"/assets/**"</span>,
          <span class="hljs-string">"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"</span>
        ]
      }
    }
  ]
}
</code></pre><p>And as always, make sure to test your PWA locally. To do this, run the build command <code>ionic build</code>.</p>
<p>This will generate the <code>www</code> folder. The application in this folder is a PWA. You can use a local server like <code>serve</code> to run it.</p>
<p>Service Workers require the app to be hosted in a secure context. To serve the build, we use a local node server. Install the <code>npmjs.com/package/serve</code> by running <code>npm i serve -g</code>.</p>
<p>Next, we can run the <code>serve www</code> command. This runs the app on http://localhost:5000, and luckily, with localhost is considered safe, and you do not need HTTPS origin to test our PWA now.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627558763936/pFWelRUpI.png" alt="Screen Shot 2021-07-29 at 9.39.02 pm.png" /></p>
<p>Open an incognito window in chrome and open the <code>http://localhost:5000</code>. You will notice another issue. When you refresh the page, the app will not load. This is because the <code>serve</code> tries to find <code>localhost:5000/home</code> and expect a home folder with an index.html file in it, and this file does not exist. There are multiple ways to fix this issue. For this tutorial, I will use hash routing.</p>
<p>All you need to change is to go to your <code>app-routing.module.ts</code> file and add <code>useHash: true</code> to your router options. Now page URL will look like localhost:5000/#/home, and you can refresh without an issue.</p>
<pre><code><span class="hljs-keyword">import</span> { NgModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { PreloadAllModules, RouterModule, Routes } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;

<span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">'home'</span>,
    loadChildren: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./home/home.module'</span>).then( <span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> m.HomePageModule)
  },
  {
    path: <span class="hljs-string">''</span>,
    redirectTo: <span class="hljs-string">'home'</span>,
    pathMatch: <span class="hljs-string">'full'</span>
  },
];

<span class="hljs-meta">@NgModule</span>({
  imports: [
    RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules, useHash: <span class="hljs-literal">true</span> })
  ],
  <span class="hljs-built_in">exports</span>: [RouterModule]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppRoutingModule { }
</code></pre><p>After fixing the routing issue, open the http://localhost:5000.Then open Chrome Devtools, open the Lighthouse tab and generate a report for the Progressive web app. This is my test result which shows the PWA app has an issue that needs to be fixed.</p>
<ul>
<li>There is no apple-touch-icon.</li>
</ul>
<p>Add the following code to your index.html in the head tag to fix the second and third issues.</p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/assets/icon/icon-192x192.png"</span>&gt;</span>
</code></pre><p>We are all set. Let's build and test the PWA app one more time. Run <code>ionic build</code> and then <code>serve www</code>. Open the Chrome incognito window and test using Lighthouse.</p>
<p>You should see a result like this one, indicating that your PWA is ready and installable.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627559218660/QPrW_2iT5.png" alt="Screen Shot 2021-07-29 at 9.46.33 pm.png" /></p>
<p>If you want to test the install, you can open your app on a normal window (not incognito), and you should be able to see the install button in the Chrome address bar.</p>
<h3 id="7-deploy-your-new-app-using-vercel-in-less-than-5-mins">7. Deploy your new app using Vercel in less than 5 mins</h3>
<p>To test our PWA on the phone, it would be easier to deploy the app to access it through a URL over HTTPS easily. I will use Vercel for this. Vercel is a zero-configuration cloud CI/CD that can understand the structure of our Ionic project and deploy it out of the box.</p>
<p>To do this, please go to vercel.com and register for a free account.</p>
<p>Log in, and you have the option to import a project from your Git repo. I am using Github, as mentioned before. So I will import the project from my Github.</p>
<p>Vercel already knows that your project is Ionic Angular and knows what command to run to build it. Just go ahead and click deploy. This will run the build and give you a URL that your project will be available on.</p>
<p>Simply click the button that says "Visit" to access your deployed app.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627560354460/HWkSFt5iW.png" alt="Screen Shot 2021-07-29 at 10.05.17 pm.png" /></p>
<p>Congratulations if you have made it this far through the tutorial. 👏 🙌 </p>
<p>Thanks for reading. As usual, if you have any questions, please leave me a comment here or DM me on Twitter.</p>
<p>Here is the demo: <a target="_blank" href="https://ionic-angular-leaflet-offline-map-pwa-one.vercel.app/#/home">demo deployed using Vercel</a></p>
<p>Here is the code repository: <a target="_blank" href="https://github.com/pazel-io/ionic-angular-leaflet-offline-map-pwa.git">ionic-angular-leaflet-offline-map-pwa</a></p>
<p>Twitter: <a target="_blank" href="https://twitter.com/_pazel">_pazel</a></p>
]]></content:encoded></item><item><title><![CDATA[Short review of crossxpost.app]]></title><description><![CDATA[Recently I read Cody Bontecou blog about Post to Dev, Hashnode, and Medium using their APIs.
This was something I was curious about as well. Doing some googling, I came across two solutions that seemed to solve the same problems.

cross-post-blog npm...]]></description><link>https://pazel.dev/short-review-of-crossxpostapp</link><guid isPermaLink="true">https://pazel.dev/short-review-of-crossxpostapp</guid><category><![CDATA[Blogging]]></category><category><![CDATA[medium]]></category><category><![CDATA[Hashnode]]></category><category><![CDATA[APIs]]></category><category><![CDATA[features]]></category><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Thu, 15 Jul 2021 04:35:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1626329421298/U4uAhrhnA.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently I read <a class="user-mention" href="https://hashnode.com/@codybontecou">Cody Bontecou</a> <a target="_blank" href="https://codybontecou.hashnode.dev/post-to-dev-hashnode-and-medium-using-their-apis#ckr468fs703cxt5s1hc352gfk">blog</a> about <a target="_blank" href="https://codybontecou.hashnode.dev/post-to-dev-hashnode-and-medium-using-their-apis#ckr468fs703cxt5s1hc352gfk">Post to Dev, Hashnode, and Medium using their APIs</a>.</p>
<p>This was something I was curious about as well. Doing some googling, I came across two solutions that seemed to solve the same problems.</p>
<ol>
<li><p><a target="_blank" href="https://www.npmjs.com/package/cross-post-blog">cross-post-blog npm package</a></p>
</li>
<li><p><a target="_blank" href="https://crossxpost.app">cross post as SaaS</a></p>
</li>
</ol>
<p>The second solution seemed interesting since it's SaaS and seemed to do it with minimum effort, so I decided to test the trial.</p>
<h4 id="the-following-is-a-cross-posted-article-from-medium-to-hashnode-using-httpscrossxpostapphttpscrossxpostapp">The following is a cross-posted article from Medium to Hashnode using <a target="_blank" href="https://crossxpost.app/">https://crossxpost.app</a>.</h4>
<p>Here is the link to the Medium blog for comparison.
https://medium.com/lapis/reduce-if-else-using-rxjs-950a5cb50684</p>
<p>I used their import function, which basically asks for the article's URL, and imports it to the <code>crossxpost.app</code> text editor, which seems it's an HTML editor.</p>
<p>They also offer some SEO tools that can suggest optimisation for SEO. In my case, it just said the following, which is not much of help.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626328737064/FyEtdTnuz.png" alt="Screen Shot 2021-07-15 at 3.58.17 pm.png" /></p>
<p>To integrate with Hashnode, they ask for a <code>key</code> which was not clear what does that mean. I guessed that it might be the API access key, and I was right.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626328848008/VkFQLyHeB.png" alt="Screen Shot 2021-07-15 at 4.00.31 pm.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626329933953/VY9jbG8DK.png" alt="Screen Shot 2021-07-15 at 4.16.45 pm.png" /></p>
<p>I intentionally did not edit it to demonstrate what this tool is capable of.</p>
<p>There are some edits possible in the tool. It has a text editor there.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626328269604/bWHd3l9nU.png" alt="Screen Shot 2021-07-15 at 3.49.20 pm.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626328264236/Tj94ze1Hc.png" alt="Screen Shot 2021-07-15 at 3.49.33 pm.png" /></p>
<p>It copies the Medium blog as HTML to Hashnode, and the code is not clean. It is hard to edit. Overall not the best solution. It offered some tags, but the list was limited and missing what I needed. I could type in my tag but would not save it unless it was from the provided list.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626329116883/yxNR-eXmZ.png" alt="Screen Shot 2021-07-15 at 4.04.36 pm.png" /></p>
<p>This is how the code looks like in my Hashnode editor.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626328549400/glCgmenos.png" alt="Screen Shot 2021-07-15 at 3.54.57 pm.png" /></p>
<p>It's not a perfect and polished UI, but it did the job. I prefer to use MD as the source and publish across. So it might still make sense to build a tool that you can write MD and publish across.</p>
<p>It's a nice idea, but it needs improvements to be a solid solution for me.</p>
<p>=====================================================================</p>
<p>The rest of the page is the posted article by crossxpost.app</p>
<p>=====================================================================</p>
<p></p><h1>Reduce if/else using RxJS</h1><div><img src="https://miro.medium.com/fit/c/96/96/1*x6SvRoK5hGqoRTX3G0PweQ.png" /></div><div><span>Follow</span></div><div><span><a>Parham</a></span></div><div><a>Sep 12, 2020</a> · 2 min read</div><p>Elevate your code series 😊</p><div><img src="https://miro.medium.com/max/1400/1*E27LNTdIZQeU72RALogJ4Q.jpeg" /></div><p>I am trying to decide where the user should land when they open the app.</p><p>The logic in plain English would be something like this:</p><ul><li>If this is the very first time user visits the app show Terms &amp; conditions so they can agree to T&amp;C. This will be saved and the app will remember this choice. It’s a one-off thing.</li><li>Then If the user has agreed to T&amp;C and has not viewed the app intro, show the introduction. This gives the user a tour of the app features and only happen once as well. So the app keeps a flag remembering this as well.</li><li>If the user has agreed to T&amp;C and has viewed the intro, just take them to the home page.</li></ul><p>The code is using TypeScript and Angular but no major dependency on these tech stacks and you can replicate in any other tech stacks like React or Vue easily.</p><p>Original code</p><pre><span>// From this ================&gt;<br />  <br />public enter(): Subscription {<br />    return this.termsConditionsStateSelector.agreed()<br />      .pipe(<br />        switchMap((termsAgreed) =&gt; this.helpStateSelector.viewed()<br />          .pipe(<br />            map((helpViewed) =&gt; ({helpViewed, termsAgreed})),<br />          ),<br />        ),<br />        tap(({ termsAgreed, helpViewed }) =&gt; {<br />          if (!termsAgreed) {<br />            this.navCtrl.navigateRoot('/terms-conditions');<br />          } else if (termsAgreed &amp;&amp; !helpViewed) {<br />            this.navCtrl.navigateRoot('/help');<br />          } else {<br />            this.navCtrl.navigateRoot('/home');<br />          }<br />        }),<br />      ).subscribe();<br />  }</span></pre><p>Refactored with RxJS</p><pre><span>// To this ================&gt;</span><span> private showTnC(): void {<br />    const showTnC$ = this.termsConditionsStateSelector.agreed()<br />      .pipe(<br />        filter((agree) =&gt; !agree),<br />        take(1),<br />        tap(() =&gt; this.navCtrl.navigateRoot('/terms-conditions')),<br />      );<br />    showTnC$.subscribe();<br />  }</span><span> private showIntro(): void {<br />    const showIntro$ = this.termsConditionsStateSelector.agreed()<br />      .pipe(<br />        filter((agree) =&gt; agree),<br />        withLatestFrom(this.helpStateSelector.viewed()),<br />        filter(([, viewed]) =&gt; !viewed),<br />        take(1),<br />        tap(() =&gt; this.navCtrl.navigateRoot('/help')),<br />      );<br />    showIntro$.subscribe();<br />  }</span><span> private showHome(): void {<br />    const showHome$ = this.helpStateSelector.viewed()<br />      .pipe(<br />        filter((viewed) =&gt; viewed),<br />        take(1),<br />        tap(() =&gt; this.navCtrl.navigateRoot('/home')),<br />      );<br />    showHome$.subscribe();<br />  }</span><span> public enter(): void {<br />    this.showTnC();<br />    this.showIntro();<br />    this.showHome();<br />  }</span></pre><p>What do you think about the readability of code in these 2 examples?<br />To me first one is easier from understanding the control flow but the second one is more modular and functional.</p><p>I like the second one better because:</p><ul><li>There is no if/else. ✅</li><li>Each function is doing a single job so concerns are separated.</li><li>Code is more modular and therefore easier to test.</li><li>Functions are named based on my DSL.</li><li>Code is more functional and there is less side effect.</li><li>I know how many signals to expect so I can use take operator and do not need to worry about unsubscribing.</li></ul><div>Photo by <a>Ruiyang Zhang</a> from <a>Pexels</a></div><p><br /></p><p></p>
<p>=====================================================================</p>
<p>End of posted article by crossxpost.app</p>
<p>=====================================================================</p>
<h2 id="conclusion">Conclusion</h2>
<p>I think what Cody has done is on the right track and can be a good solution. As mentioned before, I would prefer to have an app with a similar editor to my Hashnode blog.</p>
<p>This app would be the main place to write the article and publish it so other platforms.</p>
<p>Having such a solution will definitely help to avoid copy/paste across blogging platforms but still, to get the most out of your Hashnode, you might need to come back to Hashnode and do some final touches.</p>
<p>I did not test the first solution, but it also works similarly to ask for your article URL to cross-post. So most probably, the second platform will receive an HTML version that is not the source.</p>
<p>Maybe this can be a cool feature that Hashnode offers. 🤷‍♂️ </p>
<p>I hope this was helpful if you were looking for a similar solution.</p>
<p>Thanks for reading. I am available if you have any questions. Leave me a comment here or DM me on Twitter.</p>
<p>Twitter: _pazel</p>
]]></content:encoded></item><item><title><![CDATA[Ionic, React PWA with Auth0 login for web and mobile (no plugin required)]]></title><description><![CDATA[This article builds an Ionic PWA  app that uses Auth0 for authentication.
We will go through a step by step guide on how to do this.
Here is the final demo.
Git repo: https://github.com/pazel-io/ionic-auth0-react-pwa
Configuring Auth0
Before anything...]]></description><link>https://pazel.dev/ionic-react-pwa-with-auth0-login-for-web-and-mobile-no-plugin-required</link><guid isPermaLink="true">https://pazel.dev/ionic-react-pwa-with-auth0-login-for-web-and-mobile-no-plugin-required</guid><category><![CDATA[Ionic Framework]]></category><category><![CDATA[Auth0]]></category><category><![CDATA[PWA]]></category><category><![CDATA[React]]></category><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Mon, 12 Jul 2021 02:08:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1626055516784/zp-4mJeMat.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This article builds an Ionic <a target="_blank" href="https://web.dev/progressive-web-apps/">PWA</a>  app that uses Auth0 for authentication.</p>
<p>We will go through a step by step guide on how to do this.</p>
<p>Here is the <a target="_blank" href="https://ionic-auth0-react-pwa-deploy.vercel.app/#/home">final demo</a>.</p>
<p>Git repo: https://github.com/pazel-io/ionic-auth0-react-pwa</p>
<h3 id="configuring-auth0">Configuring Auth0</h3>
<p>Before anything, let's set you up with Auth0. Please head to https://auth0.com/ and register for a free account. After signing up, you can log in to your account. You will land on the Auth0 dashboard. 
From the menu on the left, choose the Applications page and click the “Create Application” button.</p>
<p>Click on the option that says a new Single Page Web Applications, choose a name and click create. (PWAs are web apps that behave like a native app)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625901138315/dZmKVgHPQ.png" alt="Screen Shot 2021-07-10 at 5.06.31 pm.png" /></p>
<p>Choose the technology you are using for your front end, or go with the pure JS version. I will go with React here. This selection helps Auth0 to provide relevant examples and code snippets.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625901171113/G7PljNDD5.png" alt="Screen Shot 2021-07-10 at 5.10.34 pm.png" /></p>
<p>After choosing your front end technology, you land on a quick start guide that walks you through integrating Auth0 into your web app. Before doing that, let's create our Ionic app using the Ionic CLI. 
The code from this app is pretty much what I will use for my Ionic PWA integration.</p>
<p>If you have not installed Ionic CLI before, please head to https://ionicframework.com/docs/intro/cli and follow the instructions to install the CLI. </p>
<p>Next, let's create an app using CLI by running <code>ionic start ionic-auth0-pwa</code>.
CLI should prompt you to choose the front end tech and more options.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625902933151/W7hrxZJES.png" alt="Screen Shot 2021-07-10 at 5.41.42 pm.png" /></p>
<p>I am going with React and the blank starter project. After selecting the options, CLI will download all required npm packages. (this might take few minutes)</p>
<p>Ionic CLI asks if you would like a free Ionic account after the npm package install. It's not required for this tutorial. You will eventually see some logs indicating that the setup is done.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625903592303/k9PwpIE1F.png" alt="Screen Shot 2021-07-10 at 5.49.11 pm.png" /></p>
<p>Let's <code>cd</code> to the new project we just created and run <code>ionic serve</code>. This will run a local web server and open the app in your default browser. (by default port 8100)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625983726629/YUxkxLdgg.png" alt="Screen Shot 2021-07-10 at 6.04.10 pm.png" /></p>
<p>Now we can add the Auth service based on the example. I am going to follow the instruction from Auth0 React example to add the Auth0 login.</p>
<p>The first step adds the Auth0 React package to my Ionic app.</p>
<pre><code><span class="hljs-attribute">npm</span> install @auth<span class="hljs-number">0</span>/auth<span class="hljs-number">0</span>-react
</code></pre><p>Next open <code>index.tsx</code> and wrap the <code>&lt;App&gt;</code> component in the <code>Auth0Provider</code> like this.</p>
<pre><code><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> ReactDOM <span class="hljs-keyword">from</span> <span class="hljs-string">'react-dom'</span>;
<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">'./App'</span>;
<span class="hljs-keyword">import</span> { Auth0Provider } <span class="hljs-keyword">from</span> <span class="hljs-string">'@auth0/auth0-react'</span>;

ReactDOM.render(
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">React.StrictMode</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Auth0Provider</span>
            <span class="hljs-attr">domain</span>=<span class="hljs-string">"REPLACE_WITH_YOUR_DOMAIN"</span>
            <span class="hljs-attr">clientId</span>=<span class="hljs-string">"REPLACE_WITH_YOUR_CLIENT_ID"</span>
            <span class="hljs-attr">redirectUri</span>=<span class="hljs-string">{window.location.origin}</span>
        &gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">App</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Auth0Provider</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">React.StrictMode</span>&gt;</span></span>,
    <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'root'</span>)
);
</code></pre><p>The values for ClientId and Domain are populated based on your application. You can also find them in the setting section of your app.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625992054240/TB0oMHTpy.png" alt="Screen Shot 2021-07-11 at 6.21.44 pm.png" /></p>
<p>Next, we create two components for Login and Logout.</p>
<pre><code><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useAuth0 } <span class="hljs-keyword">from</span> <span class="hljs-string">"@auth0/auth0-react"</span>;
<span class="hljs-keyword">import</span> { IonButton, IonIcon } <span class="hljs-keyword">from</span> <span class="hljs-string">"@ionic/react"</span>;
<span class="hljs-keyword">import</span> { logIn } <span class="hljs-keyword">from</span> <span class="hljs-string">"ionicons/icons"</span>;

<span class="hljs-keyword">const</span> LoginButton = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> { loginWithRedirect, loginWithPopup } = useAuth0();

    <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">IonButton</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> loginWithPopup()}&gt;
        <span class="hljs-tag">&lt;<span class="hljs-name">IonIcon</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"start"</span> <span class="hljs-attr">icon</span>=<span class="hljs-string">{logIn}</span> /&gt;</span>
        Log In
    <span class="hljs-tag">&lt;/<span class="hljs-name">IonButton</span>&gt;</span></span>;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> LoginButton;
</code></pre><p>The Login component in the Auth0 example uses the redirect method, which leaves your app for the Auth0 website, does the login and comes back to your app with Auth token. There is another method for loginWithPopup, which I will use for this example. loginWithPopup does not leave your app and makes auth flow a bit simpler.</p>
<p>And here is our LogoutButton component.</p>
<pre><code><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useAuth0 } <span class="hljs-keyword">from</span> <span class="hljs-string">"@auth0/auth0-react"</span>;
<span class="hljs-keyword">import</span> { IonButton, IonIcon } <span class="hljs-keyword">from</span> <span class="hljs-string">"@ionic/react"</span>;
<span class="hljs-keyword">import</span> { logOut } <span class="hljs-keyword">from</span> <span class="hljs-string">"ionicons/icons"</span>;

<span class="hljs-keyword">const</span> LogoutButton = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> { logout } = useAuth0();

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">IonButton</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> logout({ returnTo: window.location.origin })}&gt;
            <span class="hljs-tag">&lt;<span class="hljs-name">IonIcon</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"start"</span> <span class="hljs-attr">icon</span>=<span class="hljs-string">{logOut}</span> /&gt;</span>
            Log Out
        <span class="hljs-tag">&lt;/<span class="hljs-name">IonButton</span>&gt;</span></span>
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> LogoutButton;
</code></pre><p>Let's make use of our newly created components. I will replace the content of <code>ExploreContainer.tsx</code>, which is the default homepage of our Ionic app, with the following code.</p>
<pre><code><span class="hljs-keyword">import</span> <span class="hljs-string">'./ExploreContainer.css'</span>;
<span class="hljs-keyword">import</span> LoginButton <span class="hljs-keyword">from</span> <span class="hljs-string">"./LoginButton"</span>;
<span class="hljs-keyword">import</span> LogoutButton <span class="hljs-keyword">from</span> <span class="hljs-string">"./LogoutButton"</span>;
<span class="hljs-keyword">import</span> { useAuth0 } <span class="hljs-keyword">from</span> <span class="hljs-string">"@auth0/auth0-react"</span>;

interface ContainerProps {
}

<span class="hljs-keyword">const</span> ExploreContainer: React.FC&lt;ContainerProps&gt; = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> {isAuthenticated} = useAuth0();
    <span class="hljs-keyword">if</span> (isAuthenticated) {
        <span class="hljs-keyword">return</span> (
            <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"container"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">LogoutButton</span>/&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
        );
    }
    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"container"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">LoginButton</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );

};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ExploreContainer;
</code></pre><p>This is to check the <code>isAuthenticated</code> flag and decide to show <code>LoginButton</code> or <code>LogoutButton</code>. The result should be something like this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625993617721/OHQgrN3T6a.png" alt="Screen Shot 2021-07-11 at 6.53.15 pm.png" /></p>
<h3 id="test-the-login">Test the Login</h3>
<p>Clicking the login button should open a popup and show the login form. 
If instead of the login form, you see a message saying, "Oops! something went wrong". 
You have probably missed the step to set up the callback URLs for your Auth0 app. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625995560571/pe4YRxpis.png" alt="Screen Shot 2021-07-11 at 7.22.31 pm.png" /></p>
<p>To do this, go to your app settings. Scroll down to the section that says "Allowed Callback URLs" and enter your app URL there. We need to add <code>http://localhost:8100</code> for the "Allowed Callback URLs", "Allowed Web Origins", and "Allowed Logout URLs".</p>
<p>If you click the login now, you should see this screen.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626000028430/BNdMY7tdW.png" alt="Screen Shot 2021-07-11 at 8.37.49 pm.png" /></p>
<p>You can signup &amp; log in with a username/password or use a google account to log in. The login options provided are based on the default settings when you added your application in Auth0. You can obviously change these through Auth0 to add other social logins like Facebook, Apple or enable Multi-factor authentication.</p>
<h3 id="add-a-user-profile-to-show-user-info">Add a User Profile to show user info</h3>
<p>Now that we have a login and logout, let's add a UserProfile component to show some user details.</p>
<pre><code><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useAuth0 } <span class="hljs-keyword">from</span> <span class="hljs-string">"@auth0/auth0-react"</span>;

<span class="hljs-keyword">const</span> UserProfile = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> {user, isAuthenticated, isLoading} = useAuth0();

    <span class="hljs-keyword">if</span> (isLoading) {
        <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>Loading ...<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>;
    }

    <span class="hljs-keyword">if</span> (isAuthenticated) {
        <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">{user?.picture}</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">{user?.name}/</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Hello, {user?.name}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    }
    <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Tap the login button<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> UserProfile;
</code></pre><p>I will use the new <code>UserProfile</code> component in the <code>ExploreContainer</code>. The new updated code is:</p>
<pre><code><span class="hljs-keyword">import</span> <span class="hljs-string">'./ExploreContainer.css'</span>;
<span class="hljs-keyword">import</span> LoginButton <span class="hljs-keyword">from</span> <span class="hljs-string">"./LoginButton"</span>;
<span class="hljs-keyword">import</span> LogoutButton <span class="hljs-keyword">from</span> <span class="hljs-string">"./LogoutButton"</span>;
<span class="hljs-keyword">import</span> UserProfile <span class="hljs-keyword">from</span> <span class="hljs-string">"./UserProfile"</span>;
<span class="hljs-keyword">import</span> { useAuth0 } <span class="hljs-keyword">from</span> <span class="hljs-string">"@auth0/auth0-react"</span>;

interface ContainerProps {
}

<span class="hljs-keyword">const</span> ExploreContainer: React.FC&lt;ContainerProps&gt; = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> {isAuthenticated} = useAuth0();
    <span class="hljs-keyword">if</span> (isAuthenticated) {
        <span class="hljs-keyword">return</span> (
            <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"container"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">UserProfile</span>/&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">LogoutButton</span>/&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
        );
    }
    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"container"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">UserProfile</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">LoginButton</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );

};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ExploreContainer;
</code></pre><p>Now, after you log in, you should see your name, avatar and a logout button.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626002309377/_j-AkTXGq.png" alt="Screen Shot 2021-07-11 at 9.17.25 pm.png" /></p>
<h3 id="make-it-a-pwa">Make it a PWA</h3>
<p>We are almost there. All we need to do is to make this Ionic app a PWA. 
That is very easy. Open your <code>index.tsx</code> file and find the <code>serviceWorkerRegistration.unregister();</code> on line 24 and change it to <code>serviceWorkerRegistration.register();</code></p>
<p>To make sure our PWA is ready, we need to build the app and test it using Lighthouse. Run the <code>ionic build</code> to build the app. This should create a build directory with all of the build files, including the PWA's service worker and manifest file.</p>
<p>Service Workers require the app to be hosted in a secure context. To serve the build, we use a local node server. Install the https://www.npmjs.com/package/serve by running <code>npm i serve -g</code>.</p>
<p>Next, we can run the <code>serve build</code> command. This runs the app on <code>http://localhost:5000</code> and luckily with localhost is considered safe, and you do not need HTTPS origin to test our PWA now.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626005195059/bQiR4KeWQ.png" alt="Screen Shot 2021-07-11 at 10.05.23 pm.png" /></p>
<p>Open an incognito window in chrome and open the <code>http://localhost:5000</code>.
You will notice another issue. When you refresh the page, the app will not load. This is because the <code>serve</code> tries to find <code>localhost:5000/home</code> and expect a <code>home</code> folder with an <code>index.html</code> file in it, and this file does not exist. There are multiple ways to fix this issue. For this tutorial, I will use hash routing.</p>
<p>All you need to change is to go to your <code>App.tsx</code> file and replace <code>IonReactRouter</code> with <code>IonReactHashRouter</code>. Now page URL will look like <code>localhost:5000/#/home</code>, and you can refresh without an issue.</p>
<p>After fixing the routing issue open the <code>http://localhost:5000</code>.Then open Chrome Devtools, open the Lighthouse tab and generate a report for the Progressive web app. This is my test result which shows the PWA app has a couple of issues that need to be fixed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626005266143/G3Rs9kdY1.png" alt="Screen Shot 2021-07-11 at 10.07.23 pm.png" /></p>
<p>Issues are: </p>
<ul>
<li>Some icon sizes are not provided.</li>
<li>There is no apple-touch-icon.</li>
<li>There is not theme-color specified in meta tags.</li>
</ul>
<p>To fix the first one, we need to provide icons for all sizes in the <code>manifest.json</code> file. This file is located in the <code>public</code> folder of your project. I will use an online PWA manifest &amp; icon generator (https://www.simicart.com/manifest-generator.html)</p>
<p>Just specify your app icon with a minimum (512px x 512px) and a name here. Then click generate the manifest to download a zip file. It should contain all the icons we need. I will use the default icon provided by Ionic in the asset folder. Your zip file content should look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626043792042/GpSkQGZ1f.png" alt="Screen Shot 2021-07-12 at 8.49.37 am.png" /></p>
<p>Go ahead and copy all icons to your project under the <code>public -&gt; assets -&gt; icon</code> folder.</p>
<p>Your new manifest.json will include these icons, and it should be like this:</p>
<pre><code>{
  <span class="hljs-attr">"short_name"</span>: <span class="hljs-string">"Ionic App"</span>,
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"My Ionic App"</span>,
  <span class="hljs-attr">"icons"</span>: [
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/icon/icon-192x192.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"192x192"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
      <span class="hljs-attr">"purpose"</span>: <span class="hljs-string">"maskable any"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/icon/icon-256x256.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"256x256"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
      <span class="hljs-attr">"purpose"</span>: <span class="hljs-string">"maskable any"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/icon/icon-384x384.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"384x384"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
      <span class="hljs-attr">"purpose"</span>: <span class="hljs-string">"maskable any"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/icon/icon-512x512.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"512x512"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
      <span class="hljs-attr">"purpose"</span>: <span class="hljs-string">"maskable any"</span>
    }
  ],
  <span class="hljs-attr">"start_url"</span>: <span class="hljs-string">"."</span>,
  <span class="hljs-attr">"display"</span>: <span class="hljs-string">"standalone"</span>,
  <span class="hljs-attr">"theme_color"</span>: <span class="hljs-string">"#ffffff"</span>,
  <span class="hljs-attr">"background_color"</span>: <span class="hljs-string">"#ffffff"</span>
}
</code></pre><p>That is the fix for the first issue.</p>
<p>Add the following code to your <code>index.html</code> in the <code>head</code> tag to fix the second and third issues.</p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"%PUBLIC_URL%/assets/icon/icon-192x192.png"</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"theme-color"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"#ffffff"</span>/&gt;</span>
</code></pre><p>We are all set. Let's build and test the PWA app one more time. 
Run <code>ionic build</code> and then <code>serve build</code>. Open the Chrome incognito window and test using Lighthouse.</p>
<p>You should see a result like this one, indicating that your PWA is ready and installable.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626044217437/X4UyeVIGo.png" alt="Screen Shot 2021-07-12 at 8.55.53 am.png" /></p>
<p>If you want to test the install, you can open your app on a normal window (not incognito), and you should be able to see the install button in the Chrome address bar.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626046082542/XO1PvLHnK.png" alt="Screen Shot 2021-07-12 at 9.27.29 am.png" /></p>
<p>If you try to log in on the installed version of the app, you will notice that you get the same error as before, saying, "Oops! something went wrong". This is because the origin is changed to <code>localhost:5000</code>. You can add the new origin to Auth0 settings or specify the port when running the server. <code>serve build -p 8100</code></p>
<p>Some screenshots of how the app works after installing on desktop.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626046401821/FjBsgNHJA.png" alt="Screen Shot 2021-07-12 at 9.32.51 am.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626046473328/WdoXqz8U2.png" alt="Screen Shot 2021-07-12 at 9.33.38 am.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626046485996/aZorv2HlU.png" alt="Screen Shot 2021-07-12 at 9.33.54 am.png" /></p>
<p>Congrats!! 🥳 🎉 👏 🥂 </p>
<p>Now you have a functional PWA app with Auth0 login!</p>
<p>You can use this the same on the phone and install it as a PWA app.</p>
<p>You can download the code (project) I build as part of this blog from Github.</p>
<p>https://github.com/pazel-io/ionic-auth0-react-pwa</p>
<h3 id="bonus-point-deploy-your-new-app-using-vercel-in-less-than-5-mins">Bonus point - Deploy your new app using Vercel in less than 5 mins</h3>
<p>To test our PWA on phone it would be easier if we deploy the app so we can easily access it through a URL over HTTPS.
I will use <a target="_blank" href="https://vercel.com/">Vercel</a> for this. Vercel is a zero-configuration cloud CI/CD that can understand the structure of our Ionic project and deploy it out of the box.</p>
<p>To do this please go to https://vercel.com and register for a free account.</p>
<p>Log in and you have the option to import a project from your Git repo. I am using Github as mentioned before. So I will import the project from my Github.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626052220857/v0hStHZMf.png" alt="Screen Shot 2021-07-12 at 10.36.06 am.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626052241628/dzRROA4MV.png" alt="Screen Shot 2021-07-12 at 10.36.28 am.png" /></p>
<p>Vercel already knows that your project is Ionic React and knows what command to run to build it. Just go ahead and click deploy. This will run the build and give you a URL that your project will be available on.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626052256376/1oVtoYO4a.png" alt="Screen Shot 2021-07-12 at 10.36.46 am.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626052319005/fMmm647LT.png" alt="Screen Shot 2021-07-12 at 10.37.08 am.png" /></p>
<p>Simply click the button that says "Visit" to access your deployed app.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626052426325/BxBiqZsup.png" alt="Screen Shot 2021-07-12 at 11.13.03 am.png" /></p>
<p>Remember you will need to add the new origin (Vercel deployment domain) to Auth0 settings for Allowed Callback URLs, Allowed Logout URLs and Allowed Web Origins so you login successfully. You can add the new one like this. Replace the <code>YOUR_APP_NAME</code> with your own Vercel app.</p>
<pre><code><span class="hljs-attribute">http</span>:<span class="hljs-comment">//localhost:8100, https://{YOUR_APP_NAME}.vercel.app</span>
</code></pre><p>Now I can test it on a phone.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626053116799/F4wM7c1mm.jpeg" alt="Screenshot_20210712-111628_Chrome.jpg" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626053128266/GRAwTgDWF.jpeg" alt="Screenshot_20210712-111636_Chrome.jpg" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626053137711/592TQueCV.jpeg" alt="Screenshot_20210712-111640_Chrome.jpg" /></p>
<p>Go ahead open the installed version of the app and try to log in.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626053889890/c1qL_VfAQ.jpeg" alt="Screenshot_20210712-113629_Chrome.jpg" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1626053905049/DNjGc5wRQG.jpeg" alt="Screenshot_20210712-113644_Chrome.jpg" /></p>
<p>That's it for this tutorial.
This implementation is good enough for Web and Mobile as long as you run it in a browser or installed it as PWA.</p>
<p>I will be writing on Ionic &amp; Capacitor integration with Auth0 as a native app in my next article.</p>
<p>Thanks for reading.
I am available if you have any questions.
Leave me a comment here or DM me on Twitter.</p>
<p>Git repo: https://github.com/pazel-io/ionic-auth0-react-pwa</p>
<p>Demo: https://ionic-auth0-react-pwa-deploy.vercel.app/#/home</p>
<p>Twitter: <a target="_blank" href="https://twitter.com/_pazel">_pazel</a></p>
]]></content:encoded></item><item><title><![CDATA[How to Debug Ionic Mobile Apps in Production]]></title><description><![CDATA[This article is the second part of a two-part article on how to debug ionic apps. The techniques described here are helpful if your app is in release mode. If you are looking for ways to debug an app during development(debug version), please look at ...]]></description><link>https://pazel.dev/how-to-debug-ionic-apps-in-production-202bb10f7c55</link><guid isPermaLink="true">https://pazel.dev/how-to-debug-ionic-apps-in-production-202bb10f7c55</guid><category><![CDATA[Ionic Framework]]></category><category><![CDATA[debugging]]></category><category><![CDATA[Mobile Development]]></category><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Mon, 05 Apr 2021 10:51:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469459273/FndJjNJTa.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This article is the second part of a two-part article on how to debug ionic apps. The techniques described here are helpful if your app is in release mode. If you are looking for ways to debug an app during development(debug version), please look at the first part of this article.</p>
<p>The downside of all the methods I explained in the <a target="_blank" href="https://pazel.medium.com/how-to-debug-ionic-apps-during-development-19d5df51c6bf">first part of this article</a> is that they are only available as long as the app is in debug mode (not the release version).</p>
<p>When you sign the app for Release, the app cannot be debugged using remote debugging anymore or using the Native IDE. So You need an alternative way to see your application logs. Let’s explore some of the options.</p>
<h3 id="heading-1-use-device-logs">1. Use device logs</h3>
<p>This method is not my favourite, but it can be handy sometimes to look at the device logs. You can connect your iOS or Android device to a computer to see the logs written to the console by all applications.</p>
<p>It’s not the friendliest debugging experience, but sometimes it is a good option since some of the issues can be at the OS level and not related to code, so WebView might not be much of help.</p>
<p>If you go down this path, you need to learn how to filter the logs to see the logs related to your application. You can use your app bundle id or some specific keywords to filter the logs and see your application logs. Still, this will be a very low-level debugging experience compared to the experience you can get from Chrome DevTools.</p>
<p>Here is an example of Android debug logs by running the <code>adb logcat</code> command.</p>
<p>Tip: use a consistent structure in your application logs. This way, you can always use <code>grep</code> to filter the device logs. Here I am using<code>adb logcat grep | 'File manager'</code> to get all logs starting with that keyword.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469444178/4SdMGe_eY.png" alt="Viewing Android debug logs using adb logcat" /></p>
<p><em>Viewing Android debug logs using adb logcat</em></p>
<p>If you decide to go down this path, you should use Android Studio since looking at adb logs is not much helpful. This way, you can set the log level as you wish and filter by relevant keywords you know, like your bundle id.</p>
<p>Here is an example of adb logs for the Spotify app with bundle id <code>com.spotify.music</code>. To access the logs open Android Studio, click the <code>logcat</code> in the bottom toolbar and then in the logcat window, select your device and provide the filtering options and log level(info, error).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469446927/rSHvwcr6v.png" alt="Viewing Android debug logs using adb logcat in Android studio." /></p>
<p><em>Viewing Android debug logs using adb logcat in Android studio.</em></p>
<p>Here is an example of iOS debug logs using Xcode. You will get a lot of logs, but you can filter by application. The nice thing is that you can access crash logs to give you an insight into why the app is crashing. To access these logs, open Xcode, then go to <code>window</code> menu and select <code>Device and Simulators</code> (or use cmd+shit+2 as shortcut) and select your device or simulator from the list to see its related logs.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469449236/Ur5QhK-9K.png" alt="Looking at device logs from the prod app using Xcode" /></p>
<p><em>Looking at device logs from the prod app using Xcode</em></p>
<p>Some other limitations are that you need access to the device. This can be problematic since your mobile app is distributed to an extensive range of Android and iOS devices that you do not physically have access to them.</p>
<p>Usually, when an issue happens in production, you have no clue until an upset customer reaches out and tells you about it.</p>
<p>Even then, replicating the issue might be very difficult considering all the environment settings you may need. (OS version, Device model, Runtime issue related to lack of resources like memory or storage on the user device and bad network issues, to name a few.)</p>
<p>All you will get is that your app doesn’t work! If you are lucky and dealing with an experienced user, you may get some more details on the workflow leading to the issue. I think you agree that this sort of debugging is very inefficient, and you can go forth and back with the user many times to pinpoint the issue.</p>
<h3 id="heading-2-create-an-on-screen-logger">2. Create an on-screen logger</h3>
<p>This method is very similar to the previous solution but a little easier to access the logs. Instead of connecting the device to a computer and looking at runtime logs, you can show a human-readable version of all the logs on a debug page in your app. This method is helpful, especially during internal QA when the app is tested as a proper release version by your own QA in-house. Here is an example of the on-screen logger in one of our older Ionic app, which uses AngularJS. These logs are categorised for easy access to errors and have a specific format.</p>
<p>It might not look obvious at first, but in the hands of an experienced dev who knows the app’s workflow, this is good information for debugging.</p>
<p>Also, the user can send a copy of logs by sending the dump file to an API or email address.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469451317/tVyoPVY7A.jpeg" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469453182/vM9jOhPV-.jpeg" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469455298/8Zh5oYLyz.jpeg" alt="On-screen debugger page showing application logs" /></p>
<p><em>On-screen debugger page showing application logs</em></p>
<p>The easiest way to implement this is to override the <code>console</code> methods and add logs to an array so you can print them on your page.</p>
<p>Even this method has its limitations. You can only write a limited number of logs on the screen, which is not easy to read through or search. Also, you cannot keep too many logs on the device since it can turn into a large file quickly. So look at it as an option that you want to only enable temporarily for debugging a specific flow. The chances are that you might miss some errors that might be happening earlier in the workflow.</p>
<p>So are there any better ways to efficiently debug the Ionic apps in production?</p>
<p>Yes!</p>
<h3 id="heading-3-solutions-that-can-record-the-whole-user-session">3. Solutions that can record the whole user session</h3>
<p>So the idea is that you record whatever the user does (interaction in the app like click or visiting pages) and send it to a remote server. Then you can access an admin dashboard and replay the user’s session. Several platforms provide this type of solution.</p>
<h3 id="heading-logrockethttplogrocketcom"><a target="_blank" href="http://logrocket.com">LogRocket</a></h3>
<p>LogRocket lets you replay what users do on your site or hybrid mobile app, helping you reproduce bugs and fix issues faster. With LogRocket, you can replay problems as if they happened in your browser.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469457304/o4Htu8_9w.png" alt /></p>
<p>LogRocket lets you:</p>
<ul>
<li><p><strong>Session replay</strong> See a pixel-perfect replay of what the user saw. LogRocket can recreate the UI/UX of your app for you in its admin console.</p>
</li>
<li><p>**Redux **Inspect actions and state at any point in time.</p>
</li>
<li><p>**Network activity **View every network request and response, including the error code. Quickly find that nasty CORS error or the error related to API.</p>
</li>
<li><p>**Console and errors **Inspect console logs and JavaScript errors</p>
</li>
</ul>
<p>Plus, you can add a user identifier to logs, so you know which session belongs to which user. This method is beneficial during UAT testing. Also, Logrocket provides easy integration with your favourite framework and even Redux state management (it has a middleware for NgRX). So you can see the state changes as if you have access to the browser in debug mode.</p>
<p>Sounds great! Isn’t it? Well, there are some pitfalls!</p>
<ul>
<li><p>There is no offline support out of the box. Meaning if a user is offline, LogRocket has no means to keep the device's data and send it later. You can add a service worker to your app and proxy the LogRocket request to provide the offline storage yourself.</p>
</li>
<li><p>If you have a sizeable redux state, it can affect your app's performance and hug too much bandwidth. LogRocket will show you a warning if your state is too large and let you selectively ignore reporting parts of the state.</p>
</li>
<li><p>LogRocket has a free tier and very flexible pricing. So if you have not tried it, I highly recommend it.</p>
</li>
</ul>
<p>I have used LogRocket in many of our Ionic apps, and it makes prod debugging much easier for us. To avoid unnecessary data capture and respect user privacy, we usually avoid sending sensitive data by removing it from the payload that is sent to the LogRocket.</p>
<p>LogRocket session recording is disabled by default in our prod. We only enable it when debugging a specific user problem.</p>
<p>Enabling LogRocket for a specific user is usually triggered by the user by accessing a debug menu in the app. This way, the user will be in control of when their session is recorded.</p>
<p>There is another service called <a target="_blank" href="https://www.fullstory.com/">Fullstory</a>, which provides session replay, and I have not used it personally. Also, the pricing seems more expensive than LogRocket.</p>
<h3 id="heading-4-solutions-that-can-handle-runtime-errors">4. Solutions that can handle runtime errors</h3>
<p>There are multiple solutions known as JS error trackers, which can send any runtime error in the JS runtime to a remote server. These errors are usually accompanied by helpful environment and user-session information. Like browser spec, the stack trace of errors, time, location, user id and more.</p>
<p>Also, you can integrate them with other notification services like Slack so you can get real-time feedback from your production and act as quickly as possible to fix user issues.</p>
<p>I usually have Sentry as part of most of the apps I build to make it easier for me to address user issues proactively.</p>
<h3 id="heading-sentryiohttpsentryio"><a target="_blank" href="http://Sentry.io">Sentry.io</a></h3>
<p>Sentry provides self-hosted and cloud-based error monitoring that helps all software teams discover, triage, and prioritise errors in real time. Sentry integrates with all major frontend frameworks easily and also has a free tier for you to start with.</p>
<h3 id="heading-trackjshttpstrackjscomhow"><a target="_blank" href="https://trackjs.com/how/">TRACKJS</a></h3>
<p>JavaScript Error Logging from TrackJS monitors your web applications for JavaScript errors, alerting you with excellent context about how the user, application, and network got into trouble. Find and fix your bugs fast with TrackJS. Here is a good video by Bas explaining how TRACKJS works.</p>
<h3 id="heading-5-use-google-analytics-to-track-exceptions">5. Use google analytics to track exceptions</h3>
<p><strong>Google Analytics</strong> is a <a target="_blank" href="https://en.wikipedia.org/wiki/Web_analytics">web analytics</a> service offered by <a target="_blank" href="https://en.wikipedia.org/wiki/Google">Google</a> that tracks and reports website traffic.</p>
<p>Google Analytics is used to <a target="_blank" href="https://en.wikipedia.org/wiki/Web_tracking">track</a> website activity such as <a target="_blank" href="https://en.wikipedia.org/wiki/Session_(web_analytics)">session</a> duration, pages per session, <a target="_blank" href="https://en.wikipedia.org/wiki/Bounce_rate">bounce rate</a>, etc., of individuals using the site and the traffic source information.</p>
<p>To use Google Analytics for exception reporting, you can send data of any exceptions you are interested in along with custom metrics that you have defined. Then you can make use of Google Analytics reports understanding what type of exceptions happen or how many users get a specific kind of exception.</p>
<p>An example of this use-case can be if many users are searching for a product in your shopping app that you do not provide. This exception is not necessarily an error, but at the same time, it’s valuable stats for you to improve the UI/UX to give the user a better error message or maybe even consider adding that product to your offering.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>I hope you have enjoyed reading this article, and please check the <a target="_blank" href="https://pazel.medium.com/how-to-debug-ionic-apps-during-development-19d5df51c6bf">first part of this article</a>.</p>
<p>Please leave a comment if you have questions or need help with some specific topic in the Ionic apps debugging.</p>
]]></content:encoded></item><item><title><![CDATA[How to debug ionic apps during development?]]></title><description><![CDATA[This article is the first part of a two-part article on how to debug ionic apps. The techniques described here are helpful if your app is in debug mode. If you are looking for ways to debug an app in prod(signed release), please look at part two of t...]]></description><link>https://pazel.dev/how-to-debug-ionic-apps-during-development-19d5df51c6bf</link><guid isPermaLink="true">https://pazel.dev/how-to-debug-ionic-apps-during-development-19d5df51c6bf</guid><category><![CDATA[Ionic Framework]]></category><category><![CDATA[debugging]]></category><category><![CDATA[Mobile Development]]></category><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Mon, 05 Apr 2021 10:48:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469439819/Fn8cSAbFj.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This article is the first part of a two-part article on how to debug ionic apps. The techniques described here are helpful if your app is in debug mode. If you are looking for ways to debug an app in prod(signed release), please look at part two of this article.</p>
<h3 id="heading-1-remote-debugging-during-development-using-web-view">1- Remote debugging during development using Web View</h3>
<p><img src="https://cdn-images-1.medium.com/max/2624/0*7PMXkw-jrU9Rnakb.png" alt="from Ionic website: [Web Views power web apps on native devices](https://cdn.hashnode.com/res/hashnode/image/upload/v1628469413269/tWMMGamg3.html)" /><em>from Ionic website: <a target="_blank" href="https://ionicframework.com/docs/core-concepts/webview">Web Views power web apps on native devices</a></em></p>
<p>Ionic apps (native apps) are web apps that run in an embedded browser, so you can use remote debugging with chrome dev tools on Android or Safari Web Inspector on iOS to debug JavaScript, HTML and CSS.</p>
<p>You can use remote debugging with an actual device or simulator. If you use a real device, ensure that debugging is enabled both from the device and on the remote browser.</p>
<p>In Android, you need to go to the settings and enable developer mode first and then USB Debugging under the developer options menu. 
If you do not see the Developer options menu, you need first to enable it. To do that, go to <code>About phone &amp;gt; Software information</code> find the build number and tap on it until you get the confirmation for enabling developer options. Then the <code>Developer options</code> menu will be available at the same level as <code>About phone</code> menu option.</p>
<p>To enable remote debugging for android. Open a new chrome tab and type in <code>chrome://inspect</code> this will show you all the web views available for debugging, grouped by app name.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469414909/VAu4zM-Wf.png" alt="Chrome inspect devices tab, showing apps that can be debugged on Simulator and Actual Device" /><em>Chrome inspect devices tab, showing apps that can be debugged on Simulator and Actual Device</em></p>
<p>iOS is very similar. Open Safari and find the menu that reads <code>Develop</code> from there; find the device you want to debug. Open the device menu, and you will see all available apps(Web Views) to debug. If you do not see the <code>Develop</code> menu, you need to go to Safari preferences and enable developer mode from the advanced settings. Also, make sure that the developer option is enabled on the actual device you use for debugging.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469416935/EQUCUO6KA.png" alt="Develop menu in Safari Desktop" /><em>Develop menu in Safari Desktop</em></p>
<p>You can use console methods like console.log and look at runtime logs to see how your app works. Also, you can use the JS debugger as you would do with a standard web application. You can inspect DOM and manipulate HTML and CSS for debugging.</p>
<p>Here is an example of remote debugging using Safari web inspector running the app on iOS simulator.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469419399/OdEMsc4a2.png" alt="Remote debugging using Safari web inspector running the app on iOS simulator" /><em>Remote debugging using Safari web inspector running the app on iOS simulator</em></p>
<p>Here is an example of remote debugging using Chrome DevTools running the app on Android simulator.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469421676/XoHdvYluR.png" alt="Remote debugging using Chrome DevTools running the app on Android simulator" /><em>Remote debugging using Chrome DevTools running the app on Android simulator</em></p>
<p>If you prefer remote debugging using your IDE, VSCode already supports this feature. So instead of putting a debugger in JS or adding console logs, you can simply put a breakpoint in your code in IDE and runtime will pause that your breakpoints. There is a section in the Ionic debugging guide about this method.
<a target="_blank" href="https://ionicframework.com/docs/troubleshooting/debugging#debugging-with-visual-studio-locally-in-chrome-both-android-ios-">https://ionicframework.com/docs/troubleshooting/debugging#debugging-with-visual-studio-locally-in-chrome-both-android-ios-</a></p>
<h3 id="heading-2-debugging-during-development-using-a-browser">2- Debugging during development using a browser</h3>
<p>If you use Ionic with Cordova or Capacitor, some of the native features will be only available when the app runs natively on a device or simulator. This limitation means you cannot simply serve the app using a desktop browser. To avoid this issue, I usually try to wrap my native service calls. Many plugins already provide a web implementation like Geolocation which falls back to use the browser Geolocation API. With other plugins, you can mock the plugin behaviour for the web or wrap the plugin calls in a service/function that ignores the web calls.</p>
<p>The following code is an example of using the Capacitor Haptics plugin on the device and ignoring the call on the web. This way, I will not get errors for the Haptics call in the console if I serve the app in a desktop browser. Alternatively, I could have used the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/vibrate">Navigator.vibrate()</a> to replicate the same behaviour, but in this case, I did not have a web target for deployment.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Capacitor, HapticsImpactStyle, HapticsNotificationType, Plugins } <span class="hljs-keyword">from</span> <span class="hljs-string">'@capacitor/core'</span>;
<span class="hljs-keyword">const</span> { Haptics } = Plugins;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> hapticWarning = <span class="hljs-function">() =&gt;</span> Capacitor.isNative ? Haptics.notification({ <span class="hljs-keyword">type</span>: HapticsNotificationType.WARNING }) : <span class="hljs-string">''</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> hapticError = <span class="hljs-function">() =&gt;</span> Capacitor.isNative ? Haptics.notification({ <span class="hljs-keyword">type</span>: HapticsNotificationType.ERROR }) : <span class="hljs-string">''</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> hapticLight = <span class="hljs-function">() =&gt;</span> Capacitor.isNative ? Haptics.impact({ style: HapticsImpactStyle.Light }) : <span class="hljs-string">''</span>;
<span class="hljs-keyword">if</span> (Capacitor.isNative) {
  Haptics.selectionStart();
}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> hapticSelect = <span class="hljs-function">() =&gt;</span> Capacitor.isNative ? Haptics.selectionChanged() : <span class="hljs-string">''</span>;
</code></pre>
<p>Debugging on a regular browser like chrome helps you to use your favourite debugging tools and extensions. For example, I use redux DevTools to see how my state is changing as I navigate my application workflow. It’s much easier to simulate different screen sizes for testing your UI and the app layout responsiveness using the chrome dev tools.
Another benefit of this approach is the faster feedback loop since changes are just a refresh away. Not suitable for debugging that involves native apis access.</p>
<p>Other useful extra tools available compared to remote debugging are the performance audit tab(lighthouse) to test your performance, accessibility, PWA scores, or animation debugger.</p>
<p>Here is an example of debugging an app, which uses ionic v5 and Angular v9.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469424267/XaWk0wNKs.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469426623/fZSCa86Sd.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469428839/f80xldEG0.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469430958/cA4orw3Mn.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469432867/d6Y-ikIT7.png" alt="Debugging app layout using Chrome DevTools" /><em>Debugging app layout using Chrome DevTools</em></p>
<h3 id="heading-3-debugging-during-development-using-native-ides">3- Debugging during development using native IDEs</h3>
<p>If you use Xcode or Android Studio to run your app on an actual device or simulator, you can look at debug logs output in IDE. This method is beneficial to debug issues that happen at the native level. Sometimes these issues happen early on app startup, which means your Web View does not even get a chance to load. So previous methods will not work.</p>
<p>Plus, if you use plugins to access native capabilities, you can quickly put a breakpoint in the Swift (Objective C) or Java code to find out issues caused by a bug in that plugin.</p>
<p>Running the app using native IDE’s is the primary way Ionic &amp; Capacitor works. With Ionic &amp; Cordova, you will need to go to generated iOS or Android project and open it in the respected IDE to debug it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469435140/6v-OQP3Z6.png" alt="Debugging using Xcode" /><em>Debugging using Xcode</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469437675/WgcFi7FwW.png" alt="Debugging using Android Studio" /><em>Debugging using Android Studio</em></p>
<p>There are many more tools for debugging. For example, using the desktop browser or native simulators, you throttle the network to simulate intermittent network condition or mock GPS location for debugging related to a device moving on a path or at a certain speed.</p>
<p>I hope you have enjoyed reading through this article, and please check <a target="_blank" href="https://pazel.medium.com/how-to-debug-ionic-apps-in-production-202bb10f7c55">part two of this article</a>.</p>
<p>Please leave a comment if you have questions or need help with some specific topic in the Ionic apps debugging.</p>
]]></content:encoded></item><item><title><![CDATA[The Others! A story of MacOs temp files.]]></title><description><![CDATA[This is not a guide but rather my personal experience and struggle with macOS temp files and how I resolved it. I am not sure if it matters but I use the latest update of macOS Catalina (10.15.7)
I had 884GB of my 1TB hard disk taken by macOS temp fi...]]></description><link>https://pazel.dev/the-others-a-story-of-macos-temp-files-52694919e24</link><guid isPermaLink="true">https://pazel.dev/the-others-a-story-of-macos-temp-files-52694919e24</guid><category><![CDATA[macOS]]></category><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Fri, 23 Oct 2020 01:29:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469395025/p_IalyQrk.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is not a guide but rather my personal experience and struggle with macOS temp files and how I resolved it. I am not sure if it matters but I use the latest update of macOS Catalina (10.15.7)</p>
<p>I had 884GB of my 1TB hard disk taken by macOS temp files.
These files plus my files, apps and projects had reduced the free space to less than 100 MB (out of 1 TB) and nothing was working properly. I was getting disk space alert constantly.</p>
<p>These temp files are hidden so you will not find them in any reports. like when you look at storage analysis in macOS. It just comes under the category of <code>Other</code> which does not tell you what or where the files are.If you google these many people have the same issue.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469377830/4JwNhsBra.png" alt="macOS storage analysis example showing others" /><em>macOS storage analysis example showing others</em></p>
<p>Googling I found the “Other” category includes the following types of files:</p>
<ul>
<li><p>System temporary files</p>
</li>
<li><p>macOS system folders</p>
</li>
<li><p>Archives and disk images (.zip, .iso, etc.)</p>
</li>
<li><p>Personal user data</p>
</li>
<li><p>Files from the user’s library (Application Support, iCloud files, screensavers, etc.)</p>
</li>
<li><p>Cache files</p>
</li>
<li><p>Fonts, plugins, extensions</p>
</li>
<li><p>Hidden files</p>
</li>
<li><p>Other files that are not recognized by a Spotlight search</p>
</li>
</ul>
<p>I tried multiple techniques to make these hidden files visible.</p>
<ul>
<li>I restarted the machine and that seemed to release some disk. I got to 4GB free space.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469379811/4cjx8xYdW.jpeg" alt /></p>
<ul>
<li>Using <a target="_blank" href="https://dev.yorhel.nl/ncdu">ncdu</a> with command <code>sudo ncdu /</code>
ncdu is a disk usage analyzer with a ncurses interface. It is designed to find space hogs on a remote server where you don’t have an entire graphical setup available, but it is a useful tool even on regular desktop systems. 
It takes a long time to analyse and for me was crashing halfway. I believe the size of disk or data was too big for this tool. Also for some reason, it was showing the size of the drive as 3.9 TiB which is way more than 1TB.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469381600/8S3IxsuCa.png" alt="ncdu tool scanning my HD" /><em>ncdu tool scanning my HD</em></p>
<ul>
<li>Next tried <a target="_blank" href="https://daisydiskapp.com/">DiskDaisy</a> and after analysis, it showed that there are 881GB of hidden files but I needed to pay and buy a licence to see the hidden files. It’s a premium feature.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469383336/4abn_bW6g.png" alt="DiskDaisy scan results on the free version" /><em>DiskDaisy scan results on the free version</em></p>
<ul>
<li>Next tried <a target="_blank" href="https://cleanmymac.com/">CleanMyMac</a>. It’s a nice software and has some free feature but it was only finding 7.5 GB of junk to clear. Things like app caches, unneeded download files and some more junk. I removed the IMovie through CleanMyMac since I don’t use it and that gave me another extra 2GB disk space.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469385055/S66XR0qD7.jpeg" alt="CleanMyMac UI example — I did not save the actual one from my test" /><em>CleanMyMac UI example — I did not save the actual one from my test</em></p>
<ul>
<li>Then tried the <a target="_blank" href="https://www.omnigroup.com/more">OmniDiskSweeper</a> and when I ran the analysis It found 110GB of used disk space. Failed to see the hidden files. I used the GUI but apparently, you can use it as command line as well.
Install with HomeBrew using <code>brew cask install omnidisksweeper</code></li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469387205/vOfboECpT.png" alt="OmniDiskSweeper UI example — I did not save the actual one from my test" /><em>OmniDiskSweeper UI example — I did not save the actual one from my test</em></p>
<ul>
<li><p>Looking at Mac DiskUtility I could see the same info on disk space usage but no more info on what or where are those files.</p>
</li>
<li><p>Last hope was going back to DiskDaisy and purchasing a licence. Hoping it can show what are those hidden files finally. Good news! It worked and it showed that the hidden files are located under <code>private/var/tmp</code> and there they were!</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469389276/Yw8Ddo_Em.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469390972/vD1oIGHGJ.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469392748/EyhSpLUgI.png" alt="DiskDaisy showing the hidden file in the premium version" /><em>DiskDaisy showing the hidden file in the premium version</em></p>
<ul>
<li><p>Then after reading more on the forums, I found these are macOS temp files. Articles I read were primarily suggesting 2 solutions. 
1- Restart your machine and macOS should get rid of temp files. 
2- If the first solution does not work, start in safe mode and then restart in normal mode and that should remove the temp file. 
None of them worked for me. I even kept the machine in safe mode for a couple of hours to give the so-called macOS cleanup task enough of the time. Still no luck!</p>
</li>
<li><p>Then I used macOS built-in Disk utility in safe mode and the First Aid and hoping it can clean up any temp files. It did not find any errors and did not delete any files. So no luck here either!</p>
</li>
<li><p>Last resort was to manually delete the files. 
If you read on this manual deletion topic you will find a lot of ppl warning not to mess around with files in <code>private/var/folders</code> and also <code>private/var/tmp</code> since they are managed by OS and can cause irreversible damage to files. 
Since I was out of options and HD space I relied on one article that was claiming removing temp files manually will not harm anything. 
I removed the files using DiskDaisy and freed up space. 
Then restarted and all looks good so far 🤞.</p>
</li>
</ul>
<p>It took a good 40mins to remove all the files and my computer is alive.
It was like choking the resources before.
Disk space is one problem and also this issue was causing my BitDefender to go crazy and upload a lot to its server (I guess BitDefender does send some data about file for scanning to its server). So it was using a lot of bandwidth as well.</p>
<p>I still am not sure if what I did was right and if you should have manually deleted the temp files.
The main question is why the OS didn’t clear out the temp files in that folder in the first place as expected. I will keep an eye on things in that area for the next few weeks and make sure it cleans up after itself.</p>
<p>The <a target="_blank" href="https://daisydiskapp.com/#">DiskDaisy</a> licence did cost me AUD 15 and saved me a lot of time on this diagnosis. Also, I can use it on my other machines.</p>
<p>My machine is much faster now and I have not heard the fan sound today. 
BitDefender network activity is reduced significantly as well.</p>
<p>I was so focused on solving the issue and did not think about checking how old these files were or potentially try to find out if they belong to any specific programs which might have caused the issue.</p>
<p>So if you have more information on the macOS temp files, please share it here with me.</p>
<p>ps: I have used my machine for 5 days since and so far no signs of anything missing after the cleanup.</p>
]]></content:encoded></item><item><title><![CDATA[Is your organisation taking care of the Golden Goose?]]></title><description><![CDATA[Photo by fauxels from Pexels
Many software development teams practice agile with scrum for software development. As a part of these practices, you are always trying to maximise productivity and get more goals done in a given sprint.
Unfortunately, it...]]></description><link>https://pazel.dev/is-your-organisation-taking-care-of-the-golden-goose-part-1-2-77f596e0731</link><guid isPermaLink="true">https://pazel.dev/is-your-organisation-taking-care-of-the-golden-goose-part-1-2-77f596e0731</guid><category><![CDATA[teambuilding]]></category><category><![CDATA[burnout]]></category><category><![CDATA[Career]]></category><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Sat, 12 Sep 2020 02:55:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469409175/9d0-lcLap.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Photo by fauxels from Pexels</p>
<p>Many software development teams practice agile with scrum for software development. As a<strong><em> </em></strong>part of these practices, you are always trying to maximise productivity and get more goals done in a given <a target="_blank" href="https://www.atlassian.com/agile/scrum/sprints">sprint</a>.</p>
<p>Unfortunately, it’s very easy to get caught up in answering the never-ending demands and deadlines of projects and<strong> </strong>forget about the amount of stress and load put on the team and the individuals.</p>
<p>Achieving the maximum amount of work done <strong>DONE</strong> does not necessarily mean you have a long term efficient team. In<strong> </strong>sprint retrospective meetings when you ask how each individual feels about the sprint, although they have achieved a lot from the product owner’s point of view, they are not feeling good about the achievements and some are probably stressed out.</p>
<p>This is not a good outcome because it’s not sustainable. If you continue on this path you will not only have an unhappy team and loss of productivity but you also risk losing good resources as well.</p>
<p>Does it sound familiar? Can you relate to this story in your team or organisation?</p>
<h2 id="heading-stress-and-burnout">Stress and burnout</h2>
<blockquote>
<p><a target="_blank" href="https://www.healthdirect.gov.au/stress">Stress</a> is a natural human response to challenging or dangerous situations. A small amount of stress, such as working to a deadline, can actually be helpful and allow increased alertness, energy and productivity.
However, ‘living on adrenaline’ can only be effective for a short time. If the pressure goes on for too long or becomes greater than our ability to cope with the stress, it can drain our physical and mental resources. Stress can have a negative effect on physical and mental health, relationships, work and wellbeing.
<a target="_blank" href="https://www.healthdirect.gov.au/nervous-breakdown">Burnout</a> is a state of emotional and physical exhaustion that can occur after a long period of excessive or stressful work.
The 3 key features of burnout are:</p>
<ul>
<li>emotional exhaustion</li>
<li>a feeling of detachment from work or becoming cynical</li>
<li><strong>reduced efficiency or lacking a sense of achievement</strong></li>
</ul>
</blockquote>
<p>source: <a target="_blank" href="https://www.healthdirect.gov.au/work-life-balance">https://www.healthdirect.gov.au/work-life-balance</a></p>
<p>To avoid burnouts in your team, make sure you are not encouraging Working overtime regularly or on weekends. STOP rewarding workaholics. The workaholism culture is bad for the team and it should be discouraged.</p>
<blockquote>
<p>Workaholics arent‘ heroes. They don’t save the day, they just use it up. The real hero is already home because she figured out a faster way to get things done.
from: <a target="_blank" href="http://37signals.com/rework/">http://37signals.com/rework/</a></p>
</blockquote>
<p>Some of the common reasons why you work extra hours might be:</p>
<ul>
<li><p>You are a junior developer so you want to learn fast.</p>
</li>
<li><p>You have started a new job and want to prove yourself.</p>
</li>
<li><p>You want to learn a new language, technology as part of your job so you are putting extra hours to compensate for the learning on the job.</p>
</li>
<li><p>The project has a very aggressive deadline and instead of raising this issue you quietly put in more hours to finish the task.</p>
</li>
<li><p>There are hidden tasks that are not accounted for but need to be done as part of your task. Like a pre-existing bug preventing you to add the new feature.</p>
</li>
<li><p>You lack some skills to do the task. Instead of learning that skill properly before doing the task, you try to finish the task by endless google searches and copy/paste from stack overflow. Eventually, you barely make it work by spending more time.</p>
</li>
<li><p>You are a perfectionist and find endless improvements to do. Things that no one asks you but regardless you want to add that small UI tweak here and there or refactor the code 100 times so it looks perfect.</p>
</li>
<li><p>Tasks are underestimated and instead of fixing the estimates or the process that produces those estimates you put in more hours.</p>
</li>
</ul>
<p>Whatever is your reason for working compulsively it’s hurting you and your team. So it must be stopped because:</p>
<ul>
<li><p>You have other responsibilities outside of work and working overtime affects your life in a way that reflects badly on the work. So it’s not even sustainable.</p>
</li>
<li><p>As said before you will lose efficiency. Putting in more hours will not result in more work done. Say goodbye to creativity!</p>
</li>
<li><p>You might think you are doing your team a favour. 
Unfortunately, you are hurting the project budget by putting extra hours that are not counted in the costing. 
These are the hours going unreported because you quietly worked a little more here and there so you can finish.
So when you cost the next project this broken cycle repeats.</p>
</li>
</ul>
<p>Many of you probably have read <a target="_blank" href="https://en.wikipedia.org/wiki/The_7_Habits_of_Highly_Effective_People"><em>*</em>The 7 Habits of Highly Effective People</a>.<em>*</em></p>
<p>I was revisiting this classic recently and one story, in particular, grabbed my attention. The story of the golden goose and P/PC concept.</p>
<h2 id="heading-ppc">P/PC</h2>
<p>P stands for the production of desired results. PC stands for production capability.</p>
<p>To explain the P/PC <a target="_blank" href="https://en.wikipedia.org/wiki/Stephen_Covey">Stephen Covey</a> uses the story of the <a target="_blank" href="https://en.wikipedia.org/wiki/The_Golden_Goose">Golden Goose</a> by <a target="_blank" href="https://en.wikipedia.org/wiki/The_Golden_Goose">Aarne-Thompson</a>. 
 This is the story of a farmer who finds out that his goose can lay golden eggs. Eventually, the farmer becomes greedy and wants more golden eggs quickly so, he kills the goose hoping to get all the eggs at once. This results in him losing the golden eggs (the desired result) as well as the goose (production capacity).</p>
<p>There is always a tension between results and the ability to produce those results (aka effectiveness).</p>
<p>The P/PC Balance is about balancing between two extremes. One end is being focused only on the result and the other end is focusing on the production capacity only.</p>
<h2 id="heading-who-is-the-golden-goose-in-your-development-team">Who is the golden goose in your development team?</h2>
<p>I think the development team as a whole is your golden goose. 
 So the team must be nurtured. This way we will have a sustainable production of golden eggs.</p>
<p>The key here is the <strong>team</strong>, so avoid rewarding or criticising individuals as much as possible. In my experience that does not help with team development.</p>
<p>People have different roles in a team <strong><em>which adds </em></strong>different values to the business but at the end of the day what you get is the result of teamwork.</p>
<p>You want to create a nurturing culture that prevents the issues that are caused by bad habits/processes and encourages good habits/processes. 
The main focus is here the <strong>team culture</strong> rather than individuals.</p>
<blockquote>
<p>“You don’t create a culture. It happens. This is why new companies don’t have a culture. Culture is the byproduct of consistent behaviour. If you encourage people to share, then sharing will be built into your culture.“
from: <a target="_blank" href="http://37signals.com/rework/">http://37signals.com/rework/</a></p>
</blockquote>
<h2 id="heading-how-do-you-nurture-your-development-team">How do you nurture your development team?</h2>
<p>First, we need to understand what motivates the team and makes them happy.</p>
<p>Here are some items that apply to my team:</p>
<ul>
<li><p>Recognition by peers and team.</p>
</li>
<li><p>Working with smart people and learning from them.</p>
</li>
<li><p>Being technically challenged and coming up with creative solutions.</p>
</li>
<li><p>Experimenting with cutting edge technology and staying relevant with the new technologies.</p>
</li>
<li><p>Career development and feeling of growth</p>
</li>
<li><p>Having a proper work-life balance</p>
</li>
<li><p>Having flexible work arrangements</p>
</li>
</ul>
<p>Over the years we have tried different techniques to inspire my team.
Here are some of them.</p>
<h2 id="heading-transparent-workflows-and-processes">Transparent workflows and processes</h2>
<p>We use a mixture of <a target="_blank" href="https://www.scrum.org/resources/what-is-scrum">Scrum</a> and <a target="_blank" href="https://en.wikipedia.org/wiki/Kanban_(development)">Kanban</a>.
 We have clearly defined workflows and processes that the team must follow.
 Through this, the team knows how to start a piece of work, how to measure progress and when to call it done.
 This clarity reduces the stress in the team.</p>
<p>For example, some of the rules are:</p>
<blockquote>
<p>1- You should not start on a task without clear understanding of business requirements.
2- You should not start a task that does not have an agreed and documented acceptance criteria.
3- You should raise your hand and ask for help if you are stuck and cannot progress more than 1hr.
4- You should break the task if it’s too big to commit to in a day.</p>
</blockquote>
<p>People know what to expect at each stage of the workflow and also how to realign and come back to the right track in case something unexpected happens. Like scope changes when the client changes their mind or when a technical challenge rises at the very last part of the implementation.</p>
<h2 id="heading-team-values-system">Team values system</h2>
<ul>
<li><p>We have clearly defined our values as a team.</p>
</li>
<li><p>A few examples of my team’s values are #teamwork, #commitment, #responsibility, #respect, #skills.</p>
</li>
<li><p>We have even visualised these values to constantly remind ourselves about them.</p>
</li>
<li><p>Teammates help each other to understand and achieve these values by completing daily tasks. For example, knowledge sharing helps us to upskill. We also take courses individually or as groups to learn new things.</p>
</li>
<li><p>This values system is also used to recognise teammates contributions.</p>
</li>
</ul>
<p><img src="https://cdn-images-1.medium.com/max/4536/1*-byTNXnDlh7QAgiSwmA6xw.jpeg" alt="Team values visualised as a tree by [Anastasia Foulidis](https://cdn.hashnode.com/res/hashnode/image/upload/v1628469406703/51Uo3mYlZ.html)" /><em>Team values visualised as a tree by <a target="_blank" href="https://www.linkedin.com/in/anastasia-foulidis-5a690165/?originalSubdomain=au">Anastasia Foulidis</a></em></p>
<h2 id="heading-team-rewards">Team rewards</h2>
<ul>
<li><p>We have a team-driven rewards system so colleagues can recognise and appreciate each other.</p>
</li>
<li><p>Heytaco on Slack is how the team can appreciate one another with tacos. Rewards can be claimed individually (coffee and brownies) or can be used to contribute to a team reward, like a company-sponsored lunch or an outdoor activity.</p>
</li>
<li><p>Super tacos are given at the end of each sprint. This is a letter of recognition describing the significance of the contribution along with a gift card or movie tickets. This will also land you in the Taco hall of fame.</p>
</li>
<li><p>Staff awards at company events for more significant contributions. This is a certificate of excellence from the CEO and a gift card. Plus you get a chance to present your work in front of the whole company.</p>
</li>
</ul>
<h2 id="heading-team-activities">Team activities</h2>
<ul>
<li><p>We do team building activities and encourage the whole team to participate.</p>
</li>
<li><p>Tech Talks once a fortnight is where we share new things and learn new technologies.</p>
</li>
<li><p>Group code reviews to understand and solve some technical challenges that are common across the team.</p>
</li>
<li><p>Working groups. These are multiple teams that cover different areas of software development like DevOps, UI/UX and front end development, Backend development and QA. 
These working groups have fortnightly meetings to discuss the issues in their respective area and come up with plans and strategies on how to tackle them.</p>
</li>
<li><p>I almost forgot to mention we have some serious competitions going on table tennis and footy tipping. Even during COVID time, we do virtual games and daily step challenges.</p>
</li>
<li><p>We do many more group activities to help us socialise as a team and connect on a human level. When I talk to former colleagues, one of the things that come up consistently is our team’s social culture which they miss.</p>
</li>
</ul>
<h2 id="heading-feedback-loop">Feedback loop</h2>
<p>We have multiple feedback channels. Some are through predefined meetings/surveys and some initiated by the teammates when they want to provide feedback.
 This helps to find the issues early and avoid potential damages.</p>
<ul>
<li><p>Multiple surveys throughout the year to anonymously give your opinion on different topics that affect you and your work.</p>
</li>
<li><p>There are 1–1 meeting with your manager every fortnight. You have a chance to discuss all the issues you are facing.</p>
</li>
<li><p>On top of that doors are always open and we value feedback and transparency.</p>
</li>
</ul>
<h2 id="heading-support-for-career-development">Support for career development</h2>
<p>We have a range of activities that try to cater to each i<strong>ndividual’s career development and define clear goals for the team so we can support it as much as possible.</strong></p>
<ul>
<li><p>A skills matrix to help us measure and assess each person’s skills in different areas.</p>
</li>
<li><p>A chance to work on new tech in R&amp;D projects. For example, trying machine learning in the context of an R&amp;D project.</p>
</li>
<li><p>Accommodate people when transitioning from one technology or software development layer to another. For example, a Java developer moving on to NodeJS or a front-end developer trying full-stack.</p>
</li>
<li><p>Dedicated Personal Development time and budget that you can decide how to spend. Be it buying books, courses or going to conferences. Your choice!</p>
</li>
</ul>
<h2 id="heading-work-life-balance">Work-life balance</h2>
<ul>
<li><p>My team offers flexibility around the working hours. If someone needs to come in late and work late it’s fine. As long as you and other teammates can do their task efficiently. The only exception being the 9:45 am standup and sprint planning because these sessions are critical to helping us communicate as a team.</p>
</li>
<li><p>People can work from home if they need to and we have all the required infrastructure in place to support it.</p>
</li>
</ul>
<p>These are just some of the examples my team uses to nurture our development team. I am sure your team has similar ways.</p>
<h2 id="heading-what-are-the-signs-that-you-are-too-focused-on-the-golden-eggs">What are the signs that you are too focused on the golden eggs?</h2>
<p>I am going to discuss these signs in more details in part 2 of this article but just to give you a peek, here are some of the common ones.</p>
<ul>
<li><p>People work overtime for extended periods instead of improving the processes.</p>
</li>
<li><p>Creative ideas come from mangers and clients instead of the development team.</p>
</li>
<li><p>Lack of interest to participate in team activities.</p>
</li>
<li><p>The team is happy to keep using outdated practices and technology and people do not care to suggest tech upgrades or upskilling.</p>
</li>
<li><p>People are not sharing their opinions or ideas and prefer to be quiet.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[The Art of Criticism]]></title><description><![CDATA[photographer Tim Gouw
I consider myself a seasoned software engineer as I have been working in this industry for a good amount of time.
I recently received feedback from my team in one of the sprint retrospective sessions.
I agreed with many of the p...]]></description><link>https://pazel.dev/the-art-of-criticism-2c30dd16380b</link><guid isPermaLink="true">https://pazel.dev/the-art-of-criticism-2c30dd16380b</guid><category><![CDATA[team]]></category><category><![CDATA[Feedback]]></category><dc:creator><![CDATA[Parham]]></dc:creator><pubDate>Tue, 19 Nov 2019 21:05:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469360812/NAXvPDGmq.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>photographer Tim Gouw</p>
<p>I consider myself a seasoned software engineer as I have been working in this industry for a good amount of time.</p>
<p>I recently received feedback from my team in one of the sprint retrospective sessions.</p>
<p>I agreed with many of the points my teammates were making. At the same time, I tried to present my views on the issue and the reasons why I made the decisions I made.</p>
<p>I approached the feedback with an open mind but still, it felt pretty harsh and I found it a bit discouraging.</p>
<p>This made me think, why did this feedback make me feel bad? Wondered if there is a better formula to provide the same feedback constructively.</p>
<blockquote>
<p>Over 50 percent of employees in today’s workplace receive feedback that’s too general or not designed to give enough constructive criticism. This was mentioned in an article in the journal of Association for Talent Development titled “<a target="_blank" href="https://www.td.org/magazines/td-magazine/please-boss-me-around">Please Boss Me Around</a>.”</p>
</blockquote>
<p>There are times that you need to give feedback to a colleague or they might be asking a question ( and no there are no stupid questions just people with different skills or skill levels). You might be their peer, team lead, or manager.</p>
<p>How do you give feedback? Have you ever paid attention to your choice of words or your tone?</p>
<p>Sometimes conversation in a team can be just logical, binary if you like, and the conversation tone might be condescending or even insulting.</p>
<p>When giving feedback, specifically if this is to criticise something that has gone wrong, discussions can easily take an ugly turn and become unproductive arguments or make the other person feel bad and inadequate.</p>
<p>We need to be mindful that people’s sensitivity levels are different and not everyone can stomach the same level of criticism.</p>
<p>This is not to say you cannot give them feedback but it is best to understand how the human brain works when receiving criticism/feedback.</p>
<p>Constructive<a target="_blank" href="https://en.wikipedia.org/wiki/Varieties_of_criticism#Constructive_criticism"> criticism</a> as it’s called is truly a hidden gem that can unlock many potentials in people. 💎</p>
<blockquote>
<p>A <a target="_blank" href="http://onlinelibrary.wiley.com/doi/10.1348/096317905X40105/abstract;jsessionid=8E7173940CB6F8343B973377B09F5C0B.d01t01?systemMessage=Wiley+Online+Library+will+be+disrupted+on+23+February+from+10%3A00-12%3A00+BST+%2805%3A00-07%3A00+EDT%29+for+essential+maintenance&amp;userIsAuthenticated=false&amp;deniedAccessCustomisedMessage=">research</a> at the University of Minnesota shows that negative events at work, such as being criticised, have a more powerful effect on an employee’s mood than do positive events, such as receiving praise. The study reveals that employees react five times more strongly to a negative encounter with their boss than to a positive encounter. Moods are the dimmer switch of performance, so be careful how you use that dial. (Ref: <a target="_blank" href="https://www.americanexpress.com/en-us/business/trends-and-insights/articles/what-our-brains-look-like-on-praise-and-criticism/">What Our Brains Look Like on Praise and Criticism</a>)</p>
</blockquote>
<p>Giving constructive feedback is an art, and there are a few things to consider before designing your message. Luckily there are proven ways that you can use to give feedback without offending people. Here are some tips:</p>
<ol>
<li><p>Create a feedback-friendly atmosphere. 🤝
No matter what your position is, you can ask for feedback for yourself. Others around you are more likely to open up, drop their guard, and do the same. Next time you lead a meeting or give a presentation, ask a colleague to reflect on a few specifics.</p>
</li>
<li><p>Make sure what you are about to criticise can be improved in the first place. In other words, no point in asking for a change in something that is not in control of the other person.</p>
</li>
<li><p>Make sure that the other person understands your point of view before trying to ask them to change!</p>
</li>
<li><p>Use the sandwich technique! 🥪 🌯
Wrap the issue you want to discuss between a couple of good feedbacks so your teammate doesn’t feel they are being cornered. People get defensive when they feel they are cornered.</p>
</li>
<li><p>Always find the action that is causing the issue and criticise the action, not the person.</p>
</li>
<li><p>When working in a software development team, you need to always approach issues from the team point of view! Use what is called “WE” language to emphasise that you as the team are willing to work and improve the situation.</p>
</li>
<li><p>Avoid sarcasm! Avoid sarcasm! Avoid sarcasm! Also, keep your emotions out of the conversation!</p>
</li>
<li><p>Don’t use a parenting tone! (Unless you are working with 5-year-old developers!) 👶 🍼
You might be a mom/dad but your teammates are not kids! (Eg: asking questions like this one with a parenting tone: “Alex! What do we do when the task’s estimate changes?!”)</p>
</li>
<li><p>Avoid impulsive decisions for criticising someone. Think thoroughly and carefully before saying anything. You may not be able to undo what you say and this will cost you in terms of team relationships.</p>
</li>
<li><p>Choose a proper time and place! ⏰
Remember you are about to tell someone there is an issue with what/how they do their job so you don’t want to add to that the embarrassment of being in front of everyone. You may even ask your colleague what is a good time to give them some feedback.
This way they will not be caught off-guard and know what is coming.</p>
</li>
<li><p>Watch your body language and tone of voice! 📣 
Try to be friendly and approachable. For example, if you are sitting across the table from your colleague and hitting your hand on the table whilst talking loudly, you might be demonstrating an aggressive body language. (<a target="_blank" href="https://cdn.hashnode.com/res/hashnode/image/upload/v1628469359239/XUNSqBKHb.html">CHOPPING MOVEMENTS</a> 🤺)</p>
</li>
</ol>
<p><img src="https://cdn-images-1.medium.com/max/2000/1*Ue-422o3n9bF_rYgf7kgug.png" alt="CHOPPING MOVEMENTS (Image from [13 Revealing Body Language Hand Gestures](https://nicolasfradet.com/hand-body-language/) article)" /><em>CHOPPING MOVEMENTS (Image from <a target="_blank" href="https://nicolasfradet.com/hand-body-language/">13 Revealing Body Language Hand Gestures</a> article)</em></p>
<p>What is your experience with giving or receiving feedback at the workplace?
Do you have any techniques to share?</p>
<p>Please feel free to comment.😊</p>
]]></content:encoded></item></channel></rss>