<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Accesto</title>
    <description>The latest articles on DEV Community by Accesto (@accesto).</description>
    <link>https://dev.to/accesto</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F5352%2Fc91dd6a4-29e3-4d8d-8626-56b47a8c258a.png</url>
      <title>DEV Community: Accesto</title>
      <link>https://dev.to/accesto</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/accesto"/>
    <language>en</language>
    <item>
      <title>Insights from the PHP Foundation Executive Director</title>
      <dc:creator>Michał Kurzeja</dc:creator>
      <pubDate>Mon, 14 Apr 2025 11:22:51 +0000</pubDate>
      <link>https://dev.to/accesto/insights-from-the-php-foundation-executive-director-33g9</link>
      <guid>https://dev.to/accesto/insights-from-the-php-foundation-executive-director-33g9</guid>
      <description>&lt;p&gt;I recently interviewed &lt;a href="https://www.linkedin.com/in/pronskiy/" rel="nofollow noopener noreferrer"&gt;Roman Pronskiy&lt;/a&gt;, who works at &lt;a href="https://www.jetbrains.com/" rel="nofollow noopener noreferrer"&gt;JetBrains&lt;/a&gt; as the Executive Director of the &lt;a href="https://thephp.foundation/" rel="nofollow noopener noreferrer"&gt;PHP Foundation&lt;/a&gt; and has been a PHP dev since 2010. In this interview, Roman shared his insights and vision for PHP. Roman’s experience offers a unique perspective on the evolution and future of PHP. In this post, I will sum up the topics we discussed, from the Foundation’s milestones and ongoing projects to what lies ahead for the PHP community.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flxlmu09lhpbzmggobe3k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flxlmu09lhpbzmggobe3k.png" alt="Image description" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Emergence of the PHP Foundation
&lt;/h2&gt;

&lt;p&gt;Firstly, I wanted to hear directly from Roman how the PHP Foundation came to be: &lt;/p&gt;

&lt;p&gt;“JetBrains hired Nikita Popov around 2018 and worked closely with him on modernizing PHP. They realised that the current model for supporting the PHP language was not sustainable. Despite PHP being widely used across the globe — from small projects to giant enterprises — the responsibility for its maintenance and development fell on the shoulders of only a handful of people. Recognising this imbalance and its potential risks, JetBrains decided to initiate the project, ensuring PHP would have the backing of a larger, more sustainable group of contributors.” — Shares Roman.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Evolution of Confidence in PHP’s Future
&lt;/h2&gt;

&lt;p&gt;I asked Roman about how his perspective has evolved since the PHP Foundation’s inception. His first post on the topic was full of uncertainty about the PHP future because of Nikita leaving, so it was interesting to see the change of perception: “Absolutely, we started because we saw the problem, we needed to act. We did, and now there are no doubts in the PHP's bright future. PHP is turning 30 next year and it’s as good as immortal at that point (laugh). I see a lot of new faces joining PHP and a lot of excitement in the community.”&lt;/p&gt;

&lt;h2&gt;
  
  
  PHP Foundation’s Initial Steps
&lt;/h2&gt;

&lt;p&gt;Naturally, the Foundation's road to where it is now was quite long, so I asked about its humble beginnings: “The Foundation’s primary focus in its early days was straightforward: pay developers to maintain the language.” Roman emphasized, “Right now, we maintain a great pace to deliver big features every year.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Milestones Achieved in Three Years
&lt;/h2&gt;

&lt;p&gt;Later our discussion drifted to the most memorable achievements of the Foundation during those three years. “First two years of PHP Foundation we were figuring out the basics, what we want to achieve, how we would proceed with that goal and what people should be hired for that job.” — said Roman. &lt;/p&gt;

&lt;h3&gt;
  
  
  Property Hooks and PIE Tool
&lt;/h3&gt;

&lt;p&gt;When the Foundation truly started to shine it’s in its third year: “These were much-needed features that nobody wanted to tackle for years,” Roman explained. PIE, a tool designed to simplify the installation of PHP extensions such as Xdebug, significantly enhances the developer experience.&lt;/p&gt;

&lt;p&gt;“Extensions are now very close to being like packages; they basically look like &lt;a href="https://getcomposer.org/" rel="nofollow noopener noreferrer"&gt;Composer&lt;/a&gt; packages. It’s still open to discussion whether PIE will be part of Composer someday. It’s not decided yet, but I hope it will be,” Roman added.&lt;/p&gt;

&lt;p&gt;These and other quality-of-life improvements have made the current state of the language much, much better.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security Audits
&lt;/h3&gt;

&lt;p&gt;With support from a German government-backed agency, the Foundation conducted extensive security audits, identifying and addressing some flaws. A public report will soon detail these findings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modernizing PHP.net website
&lt;/h3&gt;

&lt;p&gt;PHP Foundation not only focuses on PHP core development but also does its best to ensure the growth of PHP's popularity. It is hard to discuss a language's future without considering how it is seen by new developers joining the market, or returning developers assessing if PHP would be a good choice. &lt;a href="https://www.php.net/" rel="nofollow noopener noreferrer"&gt;PHP.net&lt;/a&gt; is supposed to be the “face” of the language, so we started this discussion there:&lt;/p&gt;

&lt;p&gt;“For years, the official PHP website didn't even mention popular 3rd party tools like Composer that nearly everyone uses. That's finally changing.” Besides that, Roman highlights incremental improvements, including better navigation, interactive code examples, analytics, and a new “Why PHP” page.&lt;/p&gt;

&lt;p&gt;I wanted to know more about the changes that were made to php.net and those that are still in the plans. So I kept asking, and I got some interesting information: &lt;/p&gt;

&lt;p&gt;“Recently, we went through many code examples on the page and checked them for possible vulnerabilities. We would also like to add more marketing to the website, including some case studies of companies that used PHP to great success and other mentions, examples, and statistics of PHP success. If you think about it — their success is also partly ours, and few people know how many great products are built with the help of PHP”&lt;/p&gt;

&lt;p&gt;On a side note, it’s obviously still an open-source project. As I learned during the interview — the top bar of php.net recently was changed, the change itself was suggested by one enthusiast who worked a couple of months on that change, which was later approved. &lt;/p&gt;

&lt;h2&gt;
  
  
  Overcoming Challenges: AI and Performance
&lt;/h2&gt;

&lt;p&gt;I was also wondering what the Foundation identifies as the biggest challenges for PHP right now. Roman shared his thoughts on that topic acknowledging the dual challenges of AI integration and performance:&lt;/p&gt;

&lt;p&gt;“We cannot ignore AI and the industry trends,” he emphasises. “While PHP isn't primarily an AI language, it is crucial to ensure it works seamlessly with AI technologies. This includes improving HTTP interfaces and API handling capabilities. For example, &lt;a href="https://wiki.php.net/rfc/curl_share_persistence_improvement" rel="nofollow noopener noreferrer"&gt;persistent curl handles&lt;/a&gt; were implemented for PHP 8.5, which could significantly improve performance for applications making frequent API calls.”&lt;/p&gt;

&lt;p&gt;On performance, he cites community-driven initiatives like FrankenPHP, which combines PHP with a Go web server. “What we want is to enable the community and unblock them on any issues so they can build amazing things on PHP. We are constantly in direct contact with products like FrankenPHP and if they encounter any issues — we prioritise resolving them”.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generics in PHP
&lt;/h2&gt;

&lt;p&gt;The question of Generics in PHP arises basically on every PHP event I went to, clearly, that’s the topic that is on the mind of many PHP developers, so it was worth discussing. Responding to the community’s interest in Generics, Roman explains:&lt;br&gt;
“&lt;a href="https://thephp.foundation/blog/2024/08/19/state-of-generics-and-collections/" rel="nofollow noopener noreferrer"&gt;We’ve invested heavily in researching&lt;/a&gt; how to tackle this. Our goal is to find the best solution for the community.”&lt;/p&gt;

&lt;h2&gt;
  
  
  PHP Foundation plans on scaling
&lt;/h2&gt;

&lt;p&gt;I was interested in knowing if there are any plans to scale the Foundation's size in the near future. &lt;/p&gt;

&lt;p&gt;“I would like to, but that definitely comes with challenges. First and foremost, there is the financial challenge. We are trying to plan the budget at least two years ahead to give our developers a sense of security. Second, as of now, we have 10 developers on board. Any more to that number will most likely require an addition to the leadership part of the team.&lt;/p&gt;

&lt;p&gt;But for now, we would like to hire additional developers only for specific projects for a year or two. A good example would be the delivery of Generics we discussed prior. We also want to experiment with Rust in the PHP core, for that we would need someone extremely experienced in that specific area.”, Roman concludes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exploring Rust Integration in PHP Core
&lt;/h2&gt;

&lt;p&gt;Intrigued, I wanted to know more about the reasoning for adding Rust to the PHP core. &lt;/p&gt;

&lt;p&gt;“This is something we might like to try in 2025. It’s not about performance; it’s about safety and attracting more engineers. Starting with support for Rust extensions. You will be able to write extensions with Rust, install them with PIE and treat them as regular packages.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Supporting the PHP Foundation
&lt;/h2&gt;

&lt;p&gt;As a PHP developer myself and a co-founder of a company employing more than a dozen other PHP developers, I appreciate the Foundation's work and want to draw more attention to the ways companies or individuals can support it. So, I steered the discussion in this direction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Financial Contributions
&lt;/h3&gt;

&lt;p&gt;“The first and obvious answer to anyone who wants to help the PHP Foundation — &lt;a href="https://thephp.foundation/sponsor/" rel="nofollow noopener noreferrer"&gt;You can always support us financially&lt;/a&gt;. All of our expenses are fully transparent and are &lt;a href="https://opencollective.com/phpfoundation#category-BUDGET" rel="nofollow noopener noreferrer"&gt;published on our website&lt;/a&gt;.” — Roman starts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spreading the Word
&lt;/h3&gt;

&lt;p&gt;“Articles, like yours, bring attention to our cause, help other developers and improve the community. We are also always happy to repost any valuable content on our social media to help those who support us.”&lt;/p&gt;

&lt;p&gt;Roman also adds, “If you don’t have a direct line of communication with us — write public feedback, critique, suggestions, or anything that you find important. We would also happily share detailed case studies on how you used PHP to achieve success on our social media, as I mentioned previously — your product success is also partly ours.” &lt;/p&gt;

&lt;h3&gt;
  
  
  Providing Feedback
&lt;/h3&gt;

&lt;p&gt;“Lastly, feedback from people like you helps us a lot. If you think about it, PHP core developers are not PHP developers themselves. They have a rough understanding of what they want to improve in the language, but I would bet that experienced PHP developers like you will have much-needed input for them to consider and address the issues better. &lt;/p&gt;

&lt;p&gt;With the help of analytics on &lt;a href="https://www.jetbrains.com/phpstorm/" rel="nofollow noopener noreferrer"&gt;PhpStorm&lt;/a&gt; and php.net, we are getting some information, but the real feedback is even more helpful. I am constantly in discussions with PHP developers, and I am sure I will have some for you too, later on. A client can come to you and ask if they should upgrade to PHP 8.4 or rewrite the project in Golang, which is also the worst decision they can ever make. But If such concerns arise in your clients, you know where they stem from, and that’s extremely valuable information for us.“&lt;/p&gt;

&lt;h2&gt;
  
  
  Final questions
&lt;/h2&gt;

&lt;p&gt;Wrapping up the interview, I wanted to know Roman`s favourite sources for keeping up to date with PHP news: “The research I do for my newsletter &lt;a href="" rel="nofollow"&gt;PHP Annotated&lt;/a&gt; is usually more than enough. I get the information by following and directly talking to cool people in the community on Twitter (now X), Bluesky, or Mastodon. This newsletter should cover all the news of the ecosystem,” said Roman.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As Roman Pronskiy aptly puts it, “PHP is as good as immortal.” As PHP approaches its 30th anniversary, the language continues to evolve at a never-before-seen pace — now under the stewardship of the PHP Foundation. With a focus on innovation, security, and community engagement, the future of PHP shines brighter than ever. &lt;/p&gt;

</description>
      <category>php</category>
    </item>
    <item>
      <title>A brief history of PHP language &amp; what we can learn from it?</title>
      <dc:creator>Piotr Gołofit</dc:creator>
      <pubDate>Fri, 11 Apr 2025 10:05:02 +0000</pubDate>
      <link>https://dev.to/accesto/a-brief-history-of-php-language-what-we-can-learn-from-it-3hkl</link>
      <guid>https://dev.to/accesto/a-brief-history-of-php-language-what-we-can-learn-from-it-3hkl</guid>
      <description>&lt;p&gt;This year PHP gets into its thirties 🎉 And on this occasion I would like to briefly summarise the history of PHP web development, to better understand the present and the future of the PHP programming language. I am pretty sure there is a lot we can learn from this story. &lt;/p&gt;

&lt;p&gt;So let's begin...&lt;/p&gt;

&lt;p&gt;It was in June 1995, thirty years ago... or wait, let's start even a bit earlier...&lt;/p&gt;

&lt;h2&gt;
  
  
  How did it all start?
&lt;/h2&gt;

&lt;p&gt;It is November 1968 in frosty Greenland, when the future programmer Rasmus Lerdorf is born. The World Wide Web is still 21 years from being invented. And Greenland is still part of the Kingdom of Denmark, and no one is trying to buy it 😉 &lt;/p&gt;

&lt;p&gt;Would his parents know that Rasmus will become a programmer? Probably not, as the C programming language will be unknown for another few years. &lt;/p&gt;

&lt;p&gt;Fast forward to 1994. Rasmus Lerdorf is working on his online resume. Yes, online, because the World Wide Web was invented in 1989 and became open to the public in 1993. He is probably one of the first to have a digital CV 💪&lt;/p&gt;

&lt;p&gt;Rasmus wanted to track how many people had visited his resume. How would you do it back then? It was way before Google Analytics was launched. Heck, Google itself would be unknown for another four years! So what did he do? He coded himself a simple set of Common Gateway Interface (CGI) binaries first in Pearl and later in the C programming language, which eventually led to PHP being born. Here is his story.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"PHP began life as a simple little cgi wrapper written in Perl. I wrote it in an afternoon during a period between contracts when I needed a quick tool to get an idea of who was reading my online resume. It was never intended to go beyond my own private use. The web server where I had my resume was extremely overloaded and had constant problems forking processes. I rewrote the Perl wrapper in C to get rid of the considerable overhead of having to fork Perl each time my resume was accessed."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;~Rasmus Lerdorf (source: &lt;a href="https://www.php.net/manual/phpfi2.php" rel="noopener noreferrer"&gt;https://www.php.net/manual/phpfi2.php&lt;/a&gt;)&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm93c29py94w0wlrlfkze.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm93c29py94w0wlrlfkze.png" alt="Image description" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  From PHP Tools to PHP Foundation
&lt;/h2&gt;

&lt;p&gt;Later more people using the same Web Server contacted Rasmus if they could use his wrapper. And as usual, if you give something to people, they keep asking for more. So Rasmus added more and more features to his toolset leading eventually to  a semi-complete distribution along with documentation, a mailing list and a FAQ.  How did he name it? A "Personal Home Page Tools", or in short, a PHP Toolset. &lt;/p&gt;

&lt;h3&gt;
  
  
  PHP/FI (aka PHP 1)
&lt;/h3&gt;

&lt;p&gt;Back then, Rasmus also began experimenting with databases and developed a tool for seamlessly integrating SQL queries into web pages. This solution, essentially a CGI wrapper, parsed SQL queries and simplified the creation of forms and tables based on those queries. He named this tool a Form Interpreter or FI for short. And that's how a PHP/FI was born. By many called a &lt;strong&gt;PHP v1&lt;/strong&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  PHP 2
&lt;/h3&gt;

&lt;p&gt;In 1997 the PHP acronym was formally changed to PHP: HyperText Preprocessor. In the second version of PHP/FI 2.0, Rasmus has completely rewritten the PHP and FI packages and combined them into a single program. It also evolved to the point, where it was a simple scripting language embedded inside HTML files. That's probably the moment where the term "dynamic web pages" was born, as PHP enriched static HTML sites with server-side generated content. And in this very way, it was to be known for many years to come, until web development got into templating and finally APIs and SPA applications. &lt;/p&gt;

&lt;h3&gt;
  
  
  PHP 3
&lt;/h3&gt;

&lt;p&gt;In 1997, two Israeli students from Tel Aviv - Andi Gutmans and Zeev Suraski — reached Rasmus online regarding his implementation of PHP. They wanted to use it for their university project, but it was simply inefficient and lacking some features. So they started discussions around various aspects of the current implementation and their redevelopment of PHP. And that's how PHP version 3 was born. It introduced included object-oriented programming and was the first version that more or less resembles PHP as it exists today. Two years later Zeev and Andi would combine their first names to register their new company - Zend Technologies. You might have heard of it 😉 &lt;/p&gt;

&lt;h3&gt;
  
  
  PHP 4
&lt;/h3&gt;

&lt;p&gt;May 2000 brought us PHP version 4. It was the one which, among other features, introduced HTTP sessions and output buffering. Andi Gutmans and Zeev Suraski started working on it just after the official release of v3. Their goal was to improve PHP and its performance for larger and more complex applications. The result of their work was a release of Zend Engine 1.0 which became the core of PHP 4.&lt;/p&gt;

&lt;p&gt;Thanks to OOP introduced in version 3 and new features from version 4, PHP became something more than just a simple scripting language for dynamic web pages. It evolved from a hypertext preprocessor to a fully fledged programming language for building more and more popular dynamic web applications and API's. It was around this time when the first ever RESTful API started to appear. &lt;/p&gt;

&lt;p&gt;PHP 4 was also the first one that powered... WordPress 1.0! The very first public version of this popular CMS was released in May 2003 when most of the servers were running on PHP 4.1. This was probably one of the crucial moments in the history of PHP. It was also the spark that led to a famous statistic saying that PHP powered over 80% of the whole internet. A year later, a young Harvard student Mark Zuckenber (ever heard of him? 😉) will code &lt;strong&gt;thefacebook.com&lt;/strong&gt; using PHP 4. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvogtamhch75ul0815mrw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvogtamhch75ul0815mrw.png" alt="Image description" width="700" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;First logos of Facebook (named TheFaceBook back then) and WordPress from 2003/2004&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  PHP 5
&lt;/h3&gt;

&lt;p&gt;Quick jump to July 2004 and release of PHP v5. By this point in time PHP's development community comprises dozens of developers, along with numerous others contributing to PHP-related projects such as PEAR, PECL, and documentation. It is a huge success for PHP and it is believed that this programming language is currently installed on even hundreds of millions of domains worldwide.&lt;/p&gt;

&lt;p&gt;PHP 5 represented a fundamental redesign of the language, featuring the new Zend Engine that improved performance while expanding object-oriented programming features with better syntax and functionality. It also added innovations like exception handling and enhanced error reporting. These advancements helped cement PHP's status as a powerful and widely used language for web development.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmaxird4z6cxc6004b434.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmaxird4z6cxc6004b434.png" alt="Image description" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The first version of Symfony released in 2005 extended PHP code with many useful components&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The year 2004 and the release of PHP 5 was also a turning point for a passionate Perl developer Fabien Potencier. That's exactly when he decided to switch his focus to PHP and created the Symfony framework project (released a year later) to help his company leverage the power of PHP for complex web applications.&lt;/p&gt;

&lt;p&gt;And what may surprise you, as of April 2025, still more than &lt;a href="https://w3techs.com/technologies/details/pl-php" rel="nofollow noopener noreferrer"&gt;10% of PHP pages use version 5!&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  PHP 6
&lt;/h3&gt;

&lt;p&gt;Legendary 🦄 sixth version of PHP. How to summarise it? I think this comic does it pretty well:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdjg53hmy6u7r7hhy7vdm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdjg53hmy6u7r7hhy7vdm.png" alt="Image description" width="800" height="1675"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nophpunintended.com/the-unifant/" rel="noopener noreferrer"&gt;&lt;em&gt;No PHPun Intended comic about PHP 6&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And now being more serious - why PHP 6 was never released? The plan was to introduce deep native support for Unicode in PHP. Unfortunately, the number of technical difficulties around this implementation (including performance issues and backward compatibility) led to abandoning the PHP 6 project in 2010 and its Unicode features. Many of the non-Unicode features that had been developed for PHP 6 were incorporated into PHP 5.3 and 5.4 instead. For the next PHP version web developers had to wait for over a decade. But in the meantime, two important things happen to PHP. First of all, in 2009 we founded &lt;a href="https://accesto.com/" rel="noopener noreferrer"&gt;Accesto&lt;/a&gt; 😉 And a year later, Taylor Otwell released the very first version of Laravel. For some reason I see the first one as a more important fact - but I guess you may disagree 😉&lt;/p&gt;

&lt;h3&gt;
  
  
  PHP 7
&lt;/h3&gt;

&lt;p&gt;This version feels like yesterday, but is already 10 years old! Released in 2015, delivered substantial performance enhancements (up to twice as fast as PHP 5.6 🚀) and reduced memory consumption. With its subsequent versions 7.1-7.4 added a lot of language features like the ?? operator, anonymous classes and return type declaration, just to name some. &lt;/p&gt;

&lt;h3&gt;
  
  
  PHP8
&lt;/h3&gt;

&lt;p&gt;PHP 8.0 was released in 2020 with many new features (eg. named arguments, union types, attributes, null safe operator) and significant performance improvements (huge difference thanks to the JIT compiler). Further minor releases brought us enumerations, fibers, readonly classes, DNF Types, and typed class constants. And many more! An impressive list for just a minor release I must say. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjpy6tjnlr6g34n7u2s31.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjpy6tjnlr6g34n7u2s31.png" alt="Image description" width="800" height="295"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The last PHP version (8.4) introduced property hooks, asymmetric visibility and array functions&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  PHP.... The Foundation
&lt;/h3&gt;

&lt;p&gt;November 2021 sets another important milestone in PHP history. Nikita Popov, one of the significant contributors to PHP is leaving the project. Is it the end of PHP? On the contrary! This event became a catalyst for a series of events which in the end led to &lt;a href="https://accesto.com/blog/php-foundation/" rel="noopener noreferrer"&gt;the PHP Foundation being established&lt;/a&gt;. With its mission to ensure the long-term prosperity of the PHP language. From now on, PHP as a programming language has a solid team of core developers, contributors and supporters. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb8xfe6q75vqncq8hbe8x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb8xfe6q75vqncq8hbe8x.png" alt="Image description" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The goal of The PHP Foundation is to ensure the long-term prosperity of the PHP language&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we can learn from PHP history for today?
&lt;/h2&gt;

&lt;p&gt;The history of PHP shows that this was never a hype-driven language. Throughout its history, PHP wasn't the first one to introduce various features. It adopted object-oriented programming pretty late and for many years was pretty far from types.  But at the same time, it never stopped evolving! PHP may not be up to date with what's latest or sexy and may not have all the sugar yet. But it is getting the modern syntax eventually. May not be the first one, but thanks to that, it does implement only those solutions, that proved to be useful in other languages and other technologies. And eventually, modern PHP code does not differ much from any other contemporary language used by web developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting mature, not old!
&lt;/h3&gt;

&lt;p&gt;For the last 30 years, PHP has constantly developed and improved as a language. It wasn't getting old through these years! On the contrary, it was constantly evolving and adapting to modern web development. &lt;/p&gt;

&lt;p&gt;How much more you can improve after 30 years of constant development? PHP shows that quite a lot. &lt;a href="https://accesto.com/blog/php-8-4/" rel="noopener noreferrer"&gt;The last version 8.4&lt;/a&gt; introduced property hooks, asymmetric visibility and array functions. But one thing is language features, another is better security and performance improvements.&lt;/p&gt;

&lt;p&gt;And speaking of performance. Last month we upgraded PHP for one of our clients from 8.1 to the latest 8.4. Results? Approximately 15-20% performance boost:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpmot89p6blk21fl5tjgf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpmot89p6blk21fl5tjgf.png" alt="Image description" width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Web transaction time was shortened by 15-20% after upgrading&lt;/em&gt; &lt;strong&gt;&lt;em&gt;PHP to the latest 8.4 version&lt;/em&gt;&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;We have already seen some good performance boosts in this project with previous upgrades within version 7 and version 8. So I wasn't really expecting any significant further improvements this time. But PHP continues to surprise me. After 30 years we can still expect some nice gains. Good job to all the core PHP developers! 💪&lt;/p&gt;

&lt;h3&gt;
  
  
  PHP was built out of necessity
&lt;/h3&gt;

&lt;p&gt;Personally, I also like the fact that PHP was created by Rasmus Lerdorf to solve his real-life problem. And for years it continued to evolve in that very manner — driven by real needs, not by hype. Also the same was with the PHP Foundation. It wasn't established to gain power over PHP development or because having a foundation is cool. It was built out of a real need to support PHP and its growth. &lt;/p&gt;

&lt;p&gt;Recently our CTO did a small &lt;a href="https://accesto.com/blog/php-foundation-interview/" rel="noopener noreferrer"&gt;interview with Roman Pronskiy&lt;/a&gt;, an Executive director of the PHP Foundation. Roman revealed couple of initiatives that will shape the future of PHP in the upcoming months and years. And of them are driven by real needs, not by hype. Examples? Introduction of generic types in PHP, and extensive security audits for PHP language, supported by German government-backed agency!&lt;/p&gt;

&lt;h2&gt;
  
  
  Happy birthday, PHP!
&lt;/h2&gt;

&lt;p&gt;Some may think that a programming language that is 30 years old is a finished history. That after so many years we should not expect any new language features. But that's definitely not the case with the PHP! It may not be the first one that introduces various features, but you don't have to be first to be solid, reliable, and thanks to that... relevant. Personally, I like PHP because after 30 years it can still get better and better.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Queueing in multi-tenant SaaS systems. How to ensure its fairness</title>
      <dc:creator>Michał Kurzeja</dc:creator>
      <pubDate>Wed, 13 Sep 2023 08:27:02 +0000</pubDate>
      <link>https://dev.to/accesto/queueing-in-multi-tenant-saas-systems-how-to-ensure-its-fairness-509l</link>
      <guid>https://dev.to/accesto/queueing-in-multi-tenant-saas-systems-how-to-ensure-its-fairness-509l</guid>
      <description>&lt;p&gt;Queueing in multi-tenant SaaS systems is often introduced to improve the overall platform stability, improve the user experience and scale the system. When you browse the internet or ask some developers, it will often be mentioned as an easy-to-implement solution. Well, it is not always that way, and there are sometimes some pitfalls that you can encounter while doing this step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What multi-tenancy and single-tenancy actually mean
&lt;/h2&gt;

&lt;p&gt;Let's first discuss some basics, so we have a common understanding of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SaaS (Software as a Service) — this is probably self-explanatory as selling software in this form is pretty common now. But quoting Wikipedia:

&lt;ul&gt;
&lt;li&gt;SaaS is a software licensing and delivery model in which software is licensed on a subscription basis and is centrally hosted. SaaS is also known as on-demand software, web-based software, or web-hosted software&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Multi-tenant — this means that the SaaS software you offer is serving multiple tenants/multiple customers using one instance. It does not matter if your instance is a server, multiple servers or serverless, the important thing is you have a shared infrastructure. Most SaaS companies are running using this model as it is way easier to manage and lowers the overall maintenance costs. In fact, only very specialized services, with high charges make sense not to be multi-tenant.&lt;/li&gt;
&lt;li&gt;Single-tenant — opposite to multi-tenant. This means that in order to onboard a new customer, your tech team needs to set up a new instance of the app. Each tenant can also have a different version of the app (although maintaining too many differences often gets too expensive and hard to manage). This means, that in a single-tenancy system, each new customer runs a separate software instance. Each tenant can have multiple users that share the same instance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Single-tenant vs multi-tenant SaaS architecture
&lt;/h2&gt;

&lt;p&gt;The decision between multi-tenancy and single-tenant architecture is out of the scope of this article, but the basic rule of thumb is to choose what is important for you — the ease of management of multiple customers — probably hundreds and thousands, of being able to adjust every single tenant by adding custom code changes. Another decision driver for a single-tenant architecture would be a requirement of separate computing resources, data isolation, data security, and some specific legal requirements. In general, something we could summarize as tenant isolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the problem with queueing in multi-tenant systems?
&lt;/h2&gt;

&lt;p&gt;Let us imagine a system where we have a long-running process that the clients execute. It can be anything — from processing a file (import a CSV, video format conversion), or anything else that makes sense — like crawling a page etc.&lt;/p&gt;

&lt;p&gt;Now in such systems, it could happen, that one customer executes so many tasks that are queued, that his "tasks" pile up and form a long waiting queue to be handled.&lt;/p&gt;

&lt;p&gt;If Client 1 executes 1000 such tasks, all other customers (2, 3 etc.) will have to wait until this one gets his 1000 tasks done. This happens because queues are by default FIFO — first in, first out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3kPK_yEK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/61eeefi3d9oj0476sowq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3kPK_yEK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/61eeefi3d9oj0476sowq.png" alt="Default FIFO queue" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, Client 2 will wait for ages to get his one task done, will get quickly angry and can potentially cancel the subscription. Why would he wait an hour or two to get one simple conversion finished? &lt;/p&gt;

&lt;p&gt;So we have one customer affecting the user experience of many customers, by consuming all the computing resources available.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is fair processing in multi-tenant architecture
&lt;/h2&gt;

&lt;p&gt;Before we jump into a bit more technical discussions let's quickly consider what we think is fair.&lt;/p&gt;

&lt;p&gt;The quick answer is that one client causing a big load, should not cause harm to other clients. So we should process Client 2 and Client 3 from the image, and then continue with Client 1 tasks.&lt;/p&gt;

&lt;p&gt;But to be honest, in lots of SaaS, we have different tiers of clients. Not all clients are equal. A client that has the lowest tier should probably have a bit less priority than a client on an enterprise Tier. This could mean, that f.e. our business decision is to handle clients in the top Tier four times faster than in the lowest one. This does not mean we process all tasks of the top tier first, we just process them a bit faster if the queue is full.&lt;/p&gt;

&lt;p&gt;This might be considered unfair, but I would like to have my target multi-tenant architecture handle such cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-tenant SaaS architecture — things to consider
&lt;/h2&gt;

&lt;p&gt;In order to properly implement a multi-tenant architecture we need to consider the following related problems:&lt;/p&gt;

&lt;h3&gt;
  
  
  Single-tenant vs multi-tenant — data isolation
&lt;/h3&gt;

&lt;p&gt;When you switch from a single-tenant to a multi-tenant approach you need to consider how you partition your data. Your application now stores data from multiple tenants/multiple customers in a single software instance, but the tenant's data should not be shared between them!&lt;/p&gt;

&lt;p&gt;In short, the three multi-tenancy models for data storage are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Silo - when each tenant has a separate instance of the storage, f.e. a separate database server.&lt;/li&gt;
&lt;li&gt;Bridge - when each client data is stored on the same database server, but has a separate schema to store data.&lt;/li&gt;
&lt;li&gt;Pool - when all clients share the same database (including schema), but the tables have columns to inform which tenant data it represents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IFWYKInU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8grdb4u8qjzmdd5lhpb7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IFWYKInU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8grdb4u8qjzmdd5lhpb7.png" alt="Three different multi-tenancy models for data storage" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the Silo approach is quite close to running a single-tenant architecture. In fact, for the db storage you need to create a single instance for each new customer. This can be automated and is a bit easier to manage than running a separate instance of the software per customer, but it still requires quite a lot of work and raises the maintenance costs. When you need to go the Silo way, it's good to consult with cloud service providers — they usually have articles explaining how to implement dedicated instances for storage using their cloud services.&lt;/p&gt;

&lt;p&gt;This is quite an interesting discussion to have when planning your multi-tenant architecture. You can read more about it f.e. in &lt;a href="https://d0.awsstatic.com/whitepapers/Multi_Tenant_SaaS_Storage_Strategies.pdf" rel="nofollow"&gt; this Amazon whitepaper&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Noisy neighbour
&lt;/h3&gt;

&lt;p&gt;Because multi-tenant SaaS software clients share the same hardware resources, it can happen that the activity of one of the tenants will have a negative impact on other tenants. I already touched the queueing part of it, but the same issue applies to other resources. F.e. a client executing many heavy actions that are not queued can cause a partial system outage that also hits other users. In this case, even when a user is not causing a big load on the system, he will encounter slow response times, errors etc. It happens not only when all tenants use a single database instance, but even if they only use the same software instance, as the CPU resources are shared.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yJ3e3TBH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fgxlu3r14oacqnpc7dy2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yJ3e3TBH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fgxlu3r14oacqnpc7dy2.png" alt="Total system capacity multi-tenant problem" width="423" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Uzd4IhLW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dszdpr28yqzv6fhbpp9a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Uzd4IhLW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dszdpr28yqzv6fhbpp9a.png" alt="Insuficient CPU resources for the SaaS queue" width="800" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Figure with 3 tenants, each consuming less the maximum throughput of the solution. In total, the three tenants consume the complete system resources.&lt;/p&gt;

&lt;p&gt;You can read more about it in &lt;a href="https://learn.microsoft.com/en-us/azure/architecture/antipatterns/noisy-neighbor/noisy-neighbor" rel="nofollow"&gt;this Microsoft Documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to fix multi-tenant queueing?
&lt;/h2&gt;

&lt;p&gt;While researching the web I found a couple of different solutions, let us quickly go through them and discuss the pros and cons.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Add more workers
&lt;/h3&gt;

&lt;p&gt;This can definitely be considered as a first aid if the problem hits you. If you have a lot of consumers there might be a need for faster processing = lower waiting times. But let us be honest — this won't solve the problem. One client can still block the processing for others, and there is usually a limit of consumers that you can execute. Next to that, if the processing is using some external systems/APIs, you can easily hit the rate limits.&lt;/p&gt;

&lt;p&gt;It is worth mentioning, that even when you use cloud computing with virtual machines that you can scale easily, there is usually a hard limit enforced by the cloud platform you use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CzgA5eRW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cam0ui0nq0x4hl97ytq9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CzgA5eRW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cam0ui0nq0x4hl97ytq9.png" alt="More queue workers" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Request throttling
&lt;/h3&gt;

&lt;p&gt;Did I just mention an external system could have a rate limit? Yup. When we hear rate limits, we usually tend to think about APIs. But we could introduce something similar in our app. Not always, but in some business flows, you can show an alert to the user, that he added too many tasks, and because of that — needs to wait a bit, or... just upgrade to a higher tier ;)&lt;/p&gt;

&lt;p&gt;This does not solve the queue issue itself but could help limit the problem of a noisy tenant. You can set limits for tasks added within a minute, hour, day etc. Not perfect, but I think it's worth considering.&lt;/p&gt;

&lt;h4&gt;
  
  
  2.1 Request throttling with priority
&lt;/h4&gt;

&lt;p&gt;Priority — connected with the tier the tenant is on, can be actually added to many of the described solutions. If you connect tenant priority with a rate limit, it can lead to some interesting results. Most queues support the priority of a message, and that will allow to process enterprise customers a bit faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Sharding
&lt;/h3&gt;

&lt;p&gt;In this case, you can just split the tasks into multiple queues (each queue can serve multiple tenants). A simple solution could be based on the client number, like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2Tos_Aj2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6c9pmlcia7yuupyvlnci.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2Tos_Aj2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6c9pmlcia7yuupyvlnci.png" alt="Queue sharding for SaaS" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the example, I split the clients into 3 different queues. In this case, when Client 1 starts to process too many tasks, it will "only" affect 33,(3)% of the other tenants. Does not fix the problem, but definitely makes it a bit less painful (at least for some users).&lt;/p&gt;

&lt;h4&gt;
  
  
  3.1 Sharding, but better
&lt;/h4&gt;

&lt;p&gt;This is something I did not see in any articles, but I've learned from one of our projects. You can actually come up with better ideas for sharding, than the client id.&lt;/p&gt;

&lt;p&gt;As an example, we could have a default queue for processing tasks, but we can also add a queue for our "enterprise" tenants. Next to that, we can come up with queues for tenants that are known for high load and just process them next to the default queue.&lt;/p&gt;

&lt;p&gt;In the project I worked for, there was a special "spike" queue, that was activated by a rate-limiting mechanism. If the system detected that a tenant was queueing too many tasks, he was moved to a "spike" queue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BSAEn1GX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aiga79wfhew1w3ragp1x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BSAEn1GX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aiga79wfhew1w3ragp1x.png" alt="Our approach to queue sharding" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Multiple instances - per tenant queue
&lt;/h3&gt;

&lt;p&gt;Easy, elegant, powerful and not always possible. Just create an automation (using Cloudformation or Terraform), that sets up a queue for each new tenant. You can consider this to be the &lt;code&gt;Silo&lt;/code&gt; approach described in the partitioning SaaS part above.&lt;/p&gt;

&lt;p&gt;This is probably the perfect solution if you do not have too many tenants, and there won't be too many new each month/day.&lt;/p&gt;

&lt;p&gt;In our case, we had thousands of tenants register every week, so a per-tenant queue was not an option. It would be a nightmare to manage, but also we would hit the limit of SQS queues within one week.&lt;/p&gt;

&lt;h4&gt;
  
  
  4.1 Processing this amount of queues
&lt;/h4&gt;

&lt;p&gt;Another question is how to handle that many queues. Well, there are at least two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;If the customers pay enough, you could run a consumer for each of them. Not perfect, but I think it would fit the Silo approach quite well.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You could implement a &lt;code&gt;slim&lt;/code&gt; consumer that iterates through all the queues, and fetches max one new task from each tenant queue, then forwards the task to one shared queue. The "output" queue is then connected to your consumers - shared across all tenants. &lt;a href="https://medium.com/thron-tech/multi-tenancy-and-fairness-in-the-context-of-microservices-sharded-queues-e32ee89723fc" rel="nofollow"&gt; This approach is described by Simone Carriero in his article&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SN9PM2HV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ofttwbkp9p2h1pa5p26.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SN9PM2HV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ofttwbkp9p2h1pa5p26.png" alt="Processing queues" width="800" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Switch what is queued
&lt;/h3&gt;

&lt;p&gt;This is our case, and before I describe it I would like to underline, that it fits our needs, and might be considered a bit controversial. Yet, it has worked perfectly for over two years now ;)&lt;/p&gt;

&lt;p&gt;A bit of background on what we had to handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We have quite a lot of new tenants every week;&lt;/li&gt;
&lt;li&gt;Each new tenant starts by importing data from an external system;&lt;/li&gt;
&lt;li&gt;An import might be just a few elements, but might also be thousands of them;&lt;/li&gt;
&lt;li&gt;Data is imported from an external system that has rate limits — both for us and each of our tenants;&lt;/li&gt;
&lt;li&gt;So when we hit the rate limit for Tenant A, we can usually still process Tenant B.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A "Default" approach to this would be to queue each element, as each of them needs to be fetched from the external API, and then processed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2b4v6roo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yhv7laah4t77608r0dcj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2b4v6roo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yhv7laah4t77608r0dcj.png" alt="Standard queueing approach" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is obviously a terrible idea, as one import of 10000 elements will be processed for ~1 hour (due to rate limits), and tenants that import 10 elements will have to wait, although they could be served within 3 seconds.&lt;/p&gt;

&lt;p&gt;So we decided to rethink what exactly we queue, and instead of queueing a certain element, we just queue the fact that tenant X import needs to be processed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kUv0mYYm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/noy7gxghp6qf38szye0l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kUv0mYYm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/noy7gxghp6qf38szye0l.png" alt="Switch what is queued" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next to that, we maintain a separate list of elements to be imported into the database.&lt;/p&gt;

&lt;p&gt;A consumer receives a task to process import no. 1, fetches the next element for it, and after it finishes, it reschedules the task to the queue:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qBPbETpp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9fq9jrcgpc1fywuuuq3b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qBPbETpp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9fq9jrcgpc1fywuuuq3b.png" alt="Another way of queueing for SaaS" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Rate limit adjustments
&lt;/h4&gt;

&lt;p&gt;As mentioned, the system we are connected to has a rate limit enforcement both on our app, but also on the tenant level. Our app rate limits are quite big, and in 99% of cases, we hit the tenant limit. So it makes sense to slow down importing one tenant, in order to process others.&lt;/p&gt;

&lt;p&gt;In the case of our solution, that was pretty easy to implement. First, we had to fetch the rate limit information from the external service. That actually already there in each response header. We have exact info on how many requests are left in a specified timeframe.&lt;/p&gt;

&lt;p&gt;Based on that, we can calculate a delay — the closer we get to the limit, the bigger the delay gets. As the delay grows, the rate limit "recovers", and we can lower the delay.&lt;/p&gt;

&lt;p&gt;Next, we pass the delay to the queueing solution:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--59FvYKvb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t2vnjetea1v3ijntkhik.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--59FvYKvb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t2vnjetea1v3ijntkhik.png" alt="Rate limit adjustments in SaaS queues" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now the queue handles the delay and provides us only with tasks that we can run without hitting the limits.&lt;/p&gt;

&lt;h4&gt;
  
  
  Handling different tiers
&lt;/h4&gt;

&lt;p&gt;As mentioned in the introduction, in most SaaS solutions there are different tiers of users, and we might need to process some users faster. Like a big enterprise account etc.&lt;/p&gt;

&lt;p&gt;In our system, each tier has an assigned "multiplier" that tells us how many import tasks we should schedule for the import task. Based on that, we can manage the pace/velocity at which we import the data:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GPdIPkDQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9g9qf2dhcpyfr0xtdmut.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GPdIPkDQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9g9qf2dhcpyfr0xtdmut.png" alt="Handling different tiers" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the perfect solution for your SaaS?
&lt;/h2&gt;

&lt;p&gt;There is no one-size-fits-all solution for multi-tenancy queueing, and I doubt our approach will be the best one for all of you. I just wanted to light a spark, and make you think about a couple of things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Although we (developers) tend to say queueing is easy, it is not. It is easy to implement, but it comes with quite a lot of problems you can hit, fair processing and data partitioning are two of them.&lt;/li&gt;
&lt;li&gt;You might read about some battle-proven solutions described by well-known companies, but I think it is still beneficial to sit down and re-think the approach for your multi-tenant app. You might come up with something similar to what we did, that works a lot better in your case.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Multi-tenancy in SaaS is usually not easy, but to be honest I think in 95% of cases it is worth investing the time, as it makes maintenance costs way lower. So unless you are in an MVP phase, you should make the investment.&lt;/p&gt;

&lt;p&gt;If you have any other ideas for solving queueing in multi-tenant architecture — &lt;a href="https://accesto.com/contact/"&gt; let me know! &lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
    </item>
    <item>
      <title>Creating Angular Tooltip Directive - Part 2: adding customisation</title>
      <dc:creator>Piotr Gołofit</dc:creator>
      <pubDate>Mon, 17 Oct 2022 13:54:15 +0000</pubDate>
      <link>https://dev.to/accesto/creating-angular-tooltip-directive-part-2-adding-customisation-4o6l</link>
      <guid>https://dev.to/accesto/creating-angular-tooltip-directive-part-2-adding-customisation-4o6l</guid>
      <description>&lt;p&gt;In my &lt;a href="https://accesto.com/blog/how-to-create-angular-tooltip-directive/"&gt;previous blog post&lt;/a&gt;, I explained how to create your own angular tooltip directive without using any third-party component libraries (like using angular material tooltip). Today, I will show you how to add more customisation to build a fully-fledged tooltip module.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: the whole code from this tutorial is on &lt;a href="https://github.com/accesto/angular-tooltip-directive" rel="nofollow"&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Customising Angular tooltips
&lt;/h2&gt;

&lt;p&gt;The tooltip directive created in &lt;a href="https://accesto.com/blog/how-to-create-angular-tooltip-directive/"&gt;tutorial part 1&lt;/a&gt;. should be good enough for many basic use cases. But in more complex situations, you may be tempted to install some dependencies to external angular tooltip libraries. Let's avoid this, and instead, let's take our own tooltip directive to the next level. Today, we will make it way more universal by adding a few features &amp;amp; options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tooltip position (below, above, left or right);&lt;/li&gt;
&lt;li&gt;tooltip themes (dark theme vs. light theme);&lt;/li&gt;
&lt;li&gt;tooltip delay time (separate tooltip delay show &amp;amp; tooltip delay hide);&lt;/li&gt;
&lt;li&gt;dynamic tooltip that follows the mouse cursor;&lt;/li&gt;
&lt;li&gt;support for touch devices / mobile devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without further ado, let's jump straight into the first customisation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tooltip position - angular tooltips in the right place
&lt;/h2&gt;

&lt;p&gt;So far, our tooltip was always displayed below the parent element. Now we will add an additional attribute position that will allow us to display it more flexibly. We will allow it to be placed below, above, left or right of the host element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;TooltipPosition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ABOVE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;above&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;BELOW&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;below&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;LEFT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;RIGHT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;DEFAULT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;above&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To keep our code nice and clean, let's create tooltip.enums.ts file, where we will define enum types for all the available customization properties. In addition to apparent options for TooltipPosition (above/below/left/right), you can also define a default position in our enum.&lt;/p&gt;

&lt;p&gt;Now, let's update the ts file of our directive to accept the position attribute as an @Input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;TooltipDirective&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;tooltip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TooltipPosition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TooltipPosition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEFAULT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;a href="https://accesto.com/blog/how-to-create-angular-tooltip-directive/"&gt;part 1&lt;/a&gt; of this tutorial we created a method setTooltipComponentProperties(). In that method, we calculated the position of our tooltip relative to the HTML element on which the user hovers (to display it right below it). New version of this method will have separate calculations for each of the TooltipPosition options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;setTooltipComponentProperties&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tooltip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tooltip&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elementRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;TooltipPosition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BELOW&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;TooltipPosition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ABOVE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;TooltipPosition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RIGHT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bottom&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;TooltipPosition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LEFT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bottom&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will also define the position property in the .ts file of our TooltipComponent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;TooltipComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TooltipPosition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TooltipPosition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEFAULT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value of the position property is assigned in the setTooltipComponentProperties in the code above. We will use it to set a proper modifier to our CSS tooltip class...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="tooltip"
     [ngClass]="['tooltip--'+position]"
     [style.left]="left + 'px'" [style.top]="top + 'px'"&amp;gt;
  {{tooltip}}
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...to properly align our tooltip in the desired position:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nc"&gt;.tooltip&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nc"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;--below&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;transform&lt;/span&gt;&lt;span class="nd"&gt;:translateX&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-50&lt;/span&gt;&lt;span class="nv"&gt;%&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;--above&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;transform&lt;/span&gt;&lt;span class="nd"&gt;:translate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-50&lt;/span&gt;&lt;span class="nv"&gt;%&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;-100&lt;/span&gt;&lt;span class="nv"&gt;%&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;--left&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;transform&lt;/span&gt;&lt;span class="nd"&gt;:translate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-100&lt;/span&gt;&lt;span class="nv"&gt;%&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;7px&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="nt"&gt;-50&lt;/span&gt;&lt;span class="nv"&gt;%&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;--right&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;transform&lt;/span&gt;&lt;span class="nd"&gt;:translateY&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-50&lt;/span&gt;&lt;span class="nv"&gt;%&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I am using a &lt;a href="https://getbem.com/" rel="nofollow"&gt;BEM&lt;/a&gt; notation, so it will be eg. tooltip--below or tooltip--left, but feel free to adjust it to the CSS conventions of your project.&lt;/p&gt;

&lt;p&gt;Also, if you added a small triangle indicating the tooltip anchor point, you should also adjust it for new positions. Below is just an example of the right-positioned tooltip, feel free to check the repo for the full CSS here: &lt;a href="https://github.com/accesto/angular-tooltip-directive" rel="nofollow"&gt;&lt;/a&gt;&lt;a href="https://github.com/accesto/angular-tooltip-directive"&gt;https://github.com/accesto/angular-tooltip-directive&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nc"&gt;.tooltip&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="err"&gt;...
    &amp;amp;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="nc"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;--right&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;...
    &amp;amp;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;border-top-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border-bottom-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that is it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  &amp;lt;div class="cat-icon" 
    [tooltip]="'Meow on the right!'"
    position="right"&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: if your project setup (in tsconfig.json) requires angularCompilerOptions.strictTemplates you may need to import the enum type and use [position]=TooltipPosition.RIGHT instead of simple position="right".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XVRfegyN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/szqwy0175dva28vo6uxg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XVRfegyN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/szqwy0175dva28vo6uxg.png" alt="How to position tooltip" width="880" height="154"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tooltip themes - angular tooltips that match your UI
&lt;/h2&gt;

&lt;p&gt;Our next customisation will allow us to use our tooltip in both light and dark UIs.&lt;/p&gt;

&lt;p&gt;Again, we will start by defining an enum type for our TooltipTheme options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;TooltipTheme&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;DARK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;LIGHT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;DEFAULT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on the theme, we will adjust the tooltip background, and of course the tooltip text colour. We also have to adjust the tooltip anchor point (small triangle) to match the tooltip background:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nc"&gt;.tooltip&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nc"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;--light&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;--dark&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="nc"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And to make use of these themes, we have to pass the theme property to our component template the same way as we did with the position. I will skip that part here but feel free to check the complete code in the repo: &lt;a href="https://github.com/accesto/angular-tooltip-directive" rel="nofollow"&gt;&lt;/a&gt;&lt;a href="https://github.com/accesto/angular-tooltip-directive"&gt;https://github.com/accesto/angular-tooltip-directive&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rJUx8HXj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gsoq6zkqemtrksapknai.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rJUx8HXj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gsoq6zkqemtrksapknai.png" alt="Tooltip theme customisation" width="796" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tooltip delay time - instead of showing the tooltip immediately
&lt;/h2&gt;

&lt;p&gt;Now, let's play a little bit with the tooltip behaviour. So far we just wanted to show the tooltip immediately when the user's mouse hovers over the selected HTML elements. But common practice is to show the tooltip after the user hovers over the tooltip's trigger element for a longer period (measured in milliseconds) - which may indicate that he needs some help or explanation. Let's add some delay time before we make our tooltip visible. Options are two - we can either instantiate our tooltip component after the delay, or do it immediately, but play with its opacity. I'll go with the second option, as it will give more options to control the potential show animations/transitions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nc"&gt;.tooltip&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nc"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;--visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;opacity&lt;/span&gt; &lt;span class="m"&gt;300ms&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To control visibility, we have to define visible: boolean = false property in our TooltipComponent and use it in the class directive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[class.tooltip--visible]="visible"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's now add setTimeout() to our onMouseEnter() method of our tooltip directive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt; &lt;span class="nx"&gt;onMouseEnter&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="p"&gt;...&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setTooltipComponentProperties&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showTimeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showTooltip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showDelay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;showTooltip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where this.showDelay (defined in milliseconds) will of course be another @Input() property of our angular tooltip directive. For formality, let's also clear the timeout if the user's mouse leaves the tooltip's trigger element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showTimeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same we can do with the hide delay. For that, we need to modify onMouseLeave() method of our angular tooltip directive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;   &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;HostListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouseleave&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;onMouseLeave&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hideTimeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hideDelay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, hideDelay will be an @Input() property that will define closing time in milliseconds. And of course, we also have to clear the timeout of our close delay in case the user hovers again over the parent element.&lt;/p&gt;

&lt;p&gt;And that is it - see an angular tooltip example in action, with delays for showing and hiding the tooltip:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ip01G8Jt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/on6u4x4ok4f7jo7xamo1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ip01G8Jt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/on6u4x4ok4f7jo7xamo1.gif" alt="Tooltip delay customisation" width="794" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You could also add some hiding animation/transition after the hide delay, but I will leave that to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic tooltips - eg. when the user hovers over an area
&lt;/h2&gt;

&lt;p&gt;Tooltips, as the name suggests, are often used to provide additional information on tools that users have in the UI. Let's assume you are building an online graphics editor. It's a common case, to show the mouse coordinates over the editor canvas. For that, we will need a tooltip that won't appear in one place, but will rather follow the user's mouse. For that, we will let our angular tooltips listen to pointer events, and update their position whenever the user moves the cursor. Let's call it a dynamic tooltip position:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;TooltipPosition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ABOVE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;above&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="nx"&gt;DYNAMIC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dynamic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;DEFAULT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;above&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could also define the tooltip position left/right/below/above the mouse cursor, but let's not overcomplicate this and stick to just the default behaviour. We will simply display the tooltip next to the cursor, and will hide the anchor point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="k"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;--dynamic&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Listening to the pointer events is quite straightforward, we just need to add another @HostListener that will update the position of our angular tooltip whenever the user moves the mouse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;HostListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mousemove&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="nx"&gt;onMouseMove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MouseEvent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;TooltipPosition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DYNAMIC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tooltip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tooltip&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may be wondering why we also update the this.componentRef.instance.tooltip? It's because otherwise, we won't be able to dynamically update the tooltip content. This allows us to achieve tooltips like these:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--62od4MOt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yjs84clyy4klpmwmra2t.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--62od4MOt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yjs84clyy4klpmwmra2t.gif" alt="Tooltip dynamic customisation" width="502" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Support for touch devices - mobile tooltips
&lt;/h2&gt;

&lt;p&gt;Last but not least, we will focus on making our tooltip module compatible with mobile devices. What's different on mobile? Lack of the mouse pointer of course! So how we can show our angular tooltip if the user cannot hover his mouse over anything? It's quite simple actually. We just need to adjust our tooltip directive and in addition to pointer events, listen also to touch gestures.&lt;/p&gt;

&lt;p&gt;First, let's determine what kind of gestures we want to detect. Shall we show the tooltip whenever the user taps (touches) some button or other HTML content? That would be quite straightforward to implement, but would actually prevent users from using that button. We still have to allow all the standard taps for the normal usage of the UI. Instead, let's detect the situation when the user long presses the screen over the HTML content. But how do we define the long press?&lt;/p&gt;

&lt;p&gt;Unfortunately, there is no touch event that would express the long press. But no worries, we can combine touchstart and touchend events, to detect if it was indeed a long press.&lt;/p&gt;

&lt;p&gt;So far we have been initialising our tooltip right inside the onMouseEnter method. Now, because we will have more than one trigger than the mouse, let's move the initialisation to a separate method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;HostListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouseenter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;onMouseEnter&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initializeTooltip&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;initializeTooltip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we will use that method also after we detect the long press:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;HostListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;touchstart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="nx"&gt;onTouchStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TouchEvent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;touchTimeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;touchTimeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initializeTooltip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;HostListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;touchend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;onTouchEnd&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;touchTimeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setHideTooltipTimeout&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;setHideTooltipTimeout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hideTimeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hideDelay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple as that, now our angular tooltips are ready for mobile devices. Cheers! 🍺&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://accesto.com/blog/how-to-create-angular-tooltip-directive/"&gt;part 1 of this tutorial&lt;/a&gt;, we created a custom angular tooltip directive for displaying very basic tooltips. Part 2. brought many customisations to our tooltip module. You should now be able to use it in most of your angular applications. Sure it still has fewer features than the best angular tooltip libraries (like an angular material tooltip), but if you miss some features, feel free to extend it further.&lt;/p&gt;

&lt;p&gt;You can play with the default options &amp;amp; default values like default delay. You can add further settings like font size or passing custom css class. Or you can even consider creating an angular tooltip service, for non-manual triggers (to show/hide the tooltip programmatically).&lt;/p&gt;

&lt;p&gt;I will leave it to you. And in the meantime, you can check the complete code of the angular tooltip directive from this tutorial here: &lt;a href="https://github.com/accesto/angular-tooltip-directive" rel="nofollow"&gt;&lt;/a&gt;&lt;a href="https://github.com/accesto/angular-tooltip-directive"&gt;https://github.com/accesto/angular-tooltip-directive&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>javascript</category>
      <category>frontend</category>
    </item>
    <item>
      <title>How to create your own Angular Tooltip Directive - a short tutorial</title>
      <dc:creator>Piotr Gołofit</dc:creator>
      <pubDate>Tue, 09 Aug 2022 14:45:00 +0000</pubDate>
      <link>https://dev.to/accesto/how-to-create-your-own-angular-tooltip-directive-a-short-tutorial-k3i</link>
      <guid>https://dev.to/accesto/how-to-create-your-own-angular-tooltip-directive-a-short-tutorial-k3i</guid>
      <description>&lt;p&gt;Tooltips may seem insignificant, shy, and sometimes neglected. But they are real superheroes of many user interfaces. And like superheroes, they appear from nowhere, right when you need them, exactly where the help is needed. But, what is their superpower?&lt;/p&gt;

&lt;p&gt;Tooltips are really powerful at delivering information. They are a perfect way to provide contextual help, without overloading users with too much information at the first glance. Tooltips appear wherever users need some additional data or explanation. No sooner, no later. This allows you to maintain the UI nice, clean and minimalistic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--E9UpNFb2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z0ffomtq1hgc2n3h5uqv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--E9UpNFb2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z0ffomtq1hgc2n3h5uqv.png" alt="Tooltips are commonly used as a pop up tip to explain the meaning of various tools, buttons and icons" width="880" height="256"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Tooltips are commonly used as a pop up tip to explain the meaning of various tools, buttons and icons&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Providing help in form fields, explaining complex features or tools, displaying additional information over charts... There are many ways tooltips can improve the user experience of your application. So if you are a front-end developer building an app in Angular, there is a huge chance that sooner or later you will be adding tooltips here and there.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to add tooltips to an Angular app?
&lt;/h2&gt;

&lt;p&gt;Before we dive into creating our own Angular Tooltip Directive, let's see some other ways to add tooltips in the Angular app. One of the most popular ways is to use the Angular &lt;a href="https://material.angular.io/components/tooltip/overview"&gt; Material &lt;/a&gt; component library. It provides a large set of components that follow the Google Material Design system. Another common UI library that you can consider is &lt;a href="https://ng-bootstrap.github.io/#/components/tooltip"&gt; ng-bootstrap &lt;/a&gt;- it is an Angular version of the previously most popular HTML/CSS/JS library in the world - Bootstrap. If you seek something more lightweight yet powerful, you can also check &lt;a href="https://www.npmjs.com/package/ng2-tooltip-directive"&gt; ng2 tooltip directive&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OSxj7r1w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/prn5yj43ct2uubefj2pa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OSxj7r1w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/prn5yj43ct2uubefj2pa.png" alt="Examples of Angular Material &amp;amp; NG Bootstrap tooltips" width="880" height="205"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Examples of Angular Material &amp;amp; NG Bootstrap tooltips&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  So why should I create my own Tooltip directive in Angular?
&lt;/h2&gt;

&lt;p&gt;With so many tooltip solutions available, creating your own tooltip directive may seem like reinventing the wheel. But here are some reasons why you still may want to consider building it from the scratch on your own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Because you don't want to rely on yet another npm package or you just want to keep your app lightweight - without adding the whole Angular material to achieve tooltips;&lt;/li&gt;
&lt;li&gt;Because you may need more customisation to your tooltip style (like light theme vs dark theme) or behaviour (eg. dynamic tooltips that follow your mouse - I will cover that in part 2. of this tutorial). You may also want to add some directive options like content type, custom hover options (animation, hide delay, show delay) or a support for touch devices / mobile devices;&lt;/li&gt;
&lt;li&gt;Because you may be building a whole component framework for your app and want tooltips to be part of it;&lt;/li&gt;
&lt;li&gt;Last but not least - because you may want to learn how to build angular structural directives, and although there may be some simpler directives to start with, tooltips will give you a good lesson.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So if you already decided to build your own tooltips, let's see how we can approach that task and what building blocks of Angular shall we use.&lt;/p&gt;
&lt;h2&gt;
  
  
  Tooltip as a Directive, Component or as Service?
&lt;/h2&gt;

&lt;p&gt;It depends, of course 😉 The same way as a custom button in Angular can be implemented as a standalone component tag, or as a directive applied on a standard HTML tag. Today I will show you how to implement a tooltip in Angular as a directive because I find it the most common use case. Usually, tooltips don't live on their own (like Components) and don't need to be invoked programmatically (via Services). They are usually add-ons to existing HTML content, and that is where &lt;a href="https://angular.io/guide/structural-directives"&gt;structural directives&lt;/a&gt; work best.&lt;/p&gt;

&lt;p&gt;But as your use case may be different, you may consider implementing tooltips as an:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Angular Directive - plain and simple, just an add-on to existing HTML code, usually containing just a string or a short text;&lt;/li&gt;
&lt;li&gt;Angular Component - when you need to place inside a tooltip something more sophisticated than a text or an image, eg. some additional data above an interactive chart, or even to make universal tooltips that can display any given content inside the ng template tag;&lt;/li&gt;
&lt;li&gt;Angular Service - mostly if you need to programmatically add or manipulate tooltips from the TypeScript code, eg. from within the functions of other components or services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ULFYVTR1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yrz31lrqzh6h0ai7frq8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ULFYVTR1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yrz31lrqzh6h0ai7frq8.png" alt="Tooltips above charts - potential place where you can consider implementing tooltips as a components" width="880" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Tooltips above charts - potential place where you can consider implementing tooltips as a components&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating Tooltip Directive in Angular - step-by-step recipe
&lt;/h2&gt;

&lt;p&gt;Ok, so let's start from the short &lt;strong&gt;todo list&lt;/strong&gt; with the steps required to build our reusable tooltips:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a tooltip component with a template;&lt;/li&gt;
&lt;li&gt;Define tooltip background and other styles;&lt;/li&gt;
&lt;li&gt;Create a tooltip directive;&lt;/li&gt;
&lt;li&gt;Add basic tooltip logic;&lt;/li&gt;
&lt;li&gt;Wrap it up in the tooltip module;&lt;/li&gt;
&lt;li&gt;Give it a test ride!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And here is a list of necessary ingredients for our Angular tooltip directive recipe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 component - to define a template and a style of our tooltip;&lt;/li&gt;
&lt;li&gt;1 directive - to attach tooltips to HTML elements;&lt;/li&gt;
&lt;li&gt;1 module - to wrap it all up and make it reusable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Component, directive and a module?! You mentioned only a directive before! Yup, you got me! I did cheat on you, but just a little. You can actually achieve all of that with just an Angular directive. But having a component inside a directive (kind of) gives us a more convenient way for styling, and a module will help us to wrap it up and keep our code clean. So apologies, but I had good intentions 🙈&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: you will find the whole code from this tutorial in our repository: &lt;a href="https://github.com/accesto/angular-tooltip-directive"&gt;&lt;/a&gt;&lt;a href="https://github.com/accesto/angular-tooltip-directive"&gt;https://github.com/accesto/angular-tooltip-directive&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Step 0 - set up an Angular project or use your existing one
&lt;/h2&gt;

&lt;p&gt;For those of you willing to reuse your existing project, just skip to step 1. but if you want to start from scratch, please open your favourite IDE, and in the terminal type in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ng&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;angular&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;tooltips&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;tutorial&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And follow the standard installation process of a fresh Angular project. After the project is up and running, replace the contents of app.component.html with a single button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Do&lt;/span&gt; &lt;span class="nx"&gt;something&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, with a &lt;a href="https://github.com/accesto/angular-tooltip-directive/blob/master/src/assets/cat-icon.svg"&gt;cat icon&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 - create a tooltip component
&lt;/h2&gt;

&lt;p&gt;Let's start by generating a tooltip component via Angular CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ng&lt;/span&gt; &lt;span class="nx"&gt;generate&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="nx"&gt;common&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;tooltip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I like to keep all the common UI building blocks (like buttons, tooltips, labels etc.) in app/common/ui/directory, but of course, feel free to adjust to your preferences.&lt;br&gt;
Our tooltip component will actually be just a nice wrapper for a template and some CSS, no fancy logic required, so tooltip.component.ts can be as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tooltip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./tooltip.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./tooltip.component.scss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;TooltipComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nl"&gt;tooltip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nx"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later we will add some customisation, but for now, the only difference from an empty component boilerplate are three properties - tooltip, left and top. These will keep a tooltip text and position (tooltips will be placed globally within the browser window and we will be using position:fixed for locating them).&lt;/p&gt;

&lt;p&gt;In the template, we just need to display tooltip text and attach its location to the left and top properties of a component, so HTML content of tooltip.component.html would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tooltip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;left + 'px'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;top + 'px'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="nx"&gt;tooltip&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple enough, isn't it? Let's now add some basic styles to make template content look like an actual tooltip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 - define tooltip background and other styles
&lt;/h2&gt;

&lt;p&gt;Here is an exemplar styling with a black background, white font and some rounded corners, but feel free to make it your own:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.tooltip&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;fixed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ffffff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="m"&gt;6px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;13px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-50%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As already mentioned, tooltips will work globally, thus position: fixed. Last line with transform:translateX(-50%) will ensure that tooltip self-positions itself horizontally.&lt;br&gt;
You may also want to define some max width or z-index, but I will leave that with you. Right now, let's maybe just add a nice triangle indicating an anchor point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.tooltip&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;(...)&lt;/span&gt;
  &lt;span class="err"&gt;&amp;amp;::before&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the overall tooltip will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--I6pwLhHr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0zgyhzprqe0aj6h25rt1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--I6pwLhHr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0zgyhzprqe0aj6h25rt1.png" alt="Tooltip style" width="638" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As for now, we aim to display the tooltip below the HTML element, but in part 2. of this tutorial I will explain how to add some additional usage options - including tooltip location/direction, show delay or closing time. But first, let's finally create an Angular Tooltip Directive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 - create a tooltip directive
&lt;/h2&gt;

&lt;p&gt;Directives can be created via Angular CLI in the same way as components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ng&lt;/span&gt; &lt;span class="nx"&gt;generate&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="nx"&gt;common&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;tooltip&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;tooltip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mind the additional /tooltip in the path, as we want our directive to be generated in the already existing folder with a component.&lt;/p&gt;

&lt;p&gt;Now, let's declare, inject and import a few objects that we will need inside our directive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;TooltipComponent&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./tooltip.component&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Directive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[tooltip]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;TooltipDirective&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;tooltip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ComponentRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;elementRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ElementRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;appRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApplicationRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;componentFactoryResolver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ComponentFactoryResolver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;injector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Injector&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We start by importing our tooltip component class, as we will create its instance (via component factory) every time we want to show a pop-up tip to your users. We also need a few objects to be injected into our constructor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;elementRef - we will use it to access the HTML element on which we place the directive tag, eg. a button or a link, to check its position in the browser window;&lt;/li&gt;
&lt;li&gt;appRef - reference to a whole app, that will allow us to inject our tooltip to the application HTML content globally;&lt;/li&gt;
&lt;li&gt;componentFactoryResolver - that will provide us with a factory for creating our TooltipComponent programmatically inside the TooltipDirective code;&lt;/li&gt;
&lt;li&gt;injector - that will provide &lt;a href="https://angular.io/guide/dependency-injection"&gt;Angular dependency injection&lt;/a&gt; for our TooltipComponent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then, we declare a componentRef property, to store the reference to a TooltipComponent, which we will be instantiating via the factory.&lt;/p&gt;

&lt;p&gt;Remember also to provide a meaningful selector name for the directive. I just used a [tooltip] for convenience, but you can name it eg. [appTooltip].&lt;/p&gt;

&lt;p&gt;Then, let's define an attribute that we will use to pass as an @Input() to our tooltip. Right now we just need a tooltip content, so you can name it simply a tooltip or something like tooltipText.&lt;/p&gt;

&lt;p&gt;Again, for convenience, I stuck with the tooltip as it allows me to simplify the syntax, and use the directive easier. So instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;appTooltip&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tooltip&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can skip the selector part and simply write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tooltip&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is of course, convenient, but may sometimes cause you trouble if a tooltip property conflicts with some other directives you may have, so watch out!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 - add basic tooltip logic
&lt;/h2&gt;

&lt;p&gt;After having all the groundwork done, let's implement some tooltip logic. We will start by creating an actual tooltip on hover, or more precisely onMouseEnter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;HostListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouseenter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;onMouseEnter&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;componentFactory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
              &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentFactoryResolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolveComponentFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nx"&gt;TooltipComponent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;componentFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;injector&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attachView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hostView&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;domElem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
              &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hostView&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;EmbeddedViewRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rootNodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domElem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setTooltipComponentProperties&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method will be listening (thanks to @HostListener) to mouse pointer events relative to the DOM element, on which we have applied our tooltip directive. Event mouseenter is emitted each time the user's cursor starts to hover above our DOM element. When that happens, we first check if our tooltip does not exist yet (using its reference this.componentRef) and if not, we will move on with its creation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First of all, we need to use the resolveComponentFactory to get the factory that can manufacture our tooltip component;&lt;/li&gt;
&lt;li&gt;Then, we use it to create the component, passing the dependency injector;&lt;/li&gt;
&lt;li&gt;Next, we attach our component to the virtual DOM of the whole app, via the appRef;&lt;/li&gt;
&lt;li&gt;We also attach it to the real DOM via a standard document.body.appendChild;&lt;/li&gt;
&lt;li&gt;Last but not least, we define and invoke methods to set up the position and title of our tooltip.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This method for setting up the properties would like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;setTooltipComponentProperties&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tooltip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tooltip&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;           
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elementRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Method description is quite straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access to this.componentRef.instance allows the use of an actual instance of our component so we can set up its properties like tooltip text (I did access component properties directly, but I encourage you to define some setters instead);&lt;/li&gt;
&lt;li&gt;getBoundingClientRect() - allows us to get the HTML element location and based on that calculate the tooltip coordinates relative to a browser window.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is how we handle hover, now it's time to take care of tooltip hiding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;HostListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouseleave&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;onMouseLeave&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;ngOnDestroy&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detachView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hostView&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two situations in which we have to make sure that our tooltip hides: when we hover out (mouseleave event), and when the element on which we applied our angular tooltip directive is destroyed (ngOnDestroy).&lt;/p&gt;

&lt;p&gt;And... that's it! Let's only wrap it up and we are good to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 - wrap it up in a tooltip module
&lt;/h2&gt;

&lt;p&gt;Again, we will use the Angular CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ng&lt;/span&gt; &lt;span class="nx"&gt;generate&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="nx"&gt;common&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;tooltip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And inside the module, we will declare our component and directive, and will export our final Angular tooltip directive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NgModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CommonModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;TooltipComponent&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./tooltip.component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;TooltipDirective&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./tooltip.directive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;TooltipComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;TooltipDirective&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;CommonModule&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;TooltipDirective&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;TooltipModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And... done. Let's now give it a test ride.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 - give it a test ride!
&lt;/h2&gt;

&lt;p&gt;To use tooltips we just have to add our TooltipModule in app.module.ts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;TooltipModule&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./common/ui/tooltip/tooltip.module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="nx"&gt;TooltipModule&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we are ready to apply the directive like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cat-icon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tooltip&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'Meow!'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or for a button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tooltip&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'Some explanation'&amp;gt;Do something&amp;lt;/button&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that was it - you can now hover above your button (or a cat 😺) and see how our tooltip appears:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SL2XIfPj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gytrsvy0622ohosq002p.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SL2XIfPj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gytrsvy0622ohosq002p.gif" alt="Tooltip appearance" width="549" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How can we improve our Angular tooltips?
&lt;/h2&gt;

&lt;p&gt;That was just a very basic tutorial on building your own angular tooltip directives. Bear with me and wait for part 2. in which we will extend this with many useful customisation and usage options. Part 2. will include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tooltip hide delay / show delay;&lt;/li&gt;
&lt;li&gt;Tooltip position: above/below/left/right;&lt;/li&gt;
&lt;li&gt;Dynamic tooltips that follow the mouse cursor;&lt;/li&gt;
&lt;li&gt;Tooltip themes eg. dark vs tooltip background;&lt;/li&gt;
&lt;li&gt;Default options &amp;amp; default values like default delay;&lt;/li&gt;
&lt;li&gt;Tooltip showing and tooltip hiding animations;&lt;/li&gt;
&lt;li&gt;Support for touch devices / mobile devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cheers! 🍺&lt;/p&gt;

</description>
      <category>angular</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Equal or identical. How to compare variables?</title>
      <dc:creator>Michał Romańczuk</dc:creator>
      <pubDate>Wed, 27 Jul 2022 05:50:13 +0000</pubDate>
      <link>https://dev.to/accesto/equal-or-identical-how-to-compare-variables-5d7f</link>
      <guid>https://dev.to/accesto/equal-or-identical-how-to-compare-variables-5d7f</guid>
      <description>&lt;p&gt;&lt;span&gt;==&lt;/span&gt; or &lt;span&gt;===&lt;/span&gt;? How many equal signs to put up so that it is correct and that nobody in the code review has a problem with it? Why is it so tricky in PHP?&lt;/p&gt;

&lt;h2&gt;
  
  
  Type Juggling
&lt;/h2&gt;

&lt;p&gt;In PHP, the type of a variable is defined by how it was used*. Depending on what we have assigned to variable, &lt;strong&gt;it becomes that type&lt;/strong&gt;&lt;sup id="fnref1"&gt;1&lt;/sup&gt;. What does it mean?&lt;/p&gt;

&lt;p&gt;&lt;small&gt;(* From PHP 7 we can define types of method arguments, from 7.4 it is possible to define types of class properties!&lt;sup id="fnref2"&gt;2&lt;/sup&gt;)&lt;/small&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// $foo is string&lt;/span&gt;
&lt;span class="nv"&gt;$foo&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// $foo is integer&lt;/span&gt;
&lt;span class="nv"&gt;$foo&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// $foo is float&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In example above, string &lt;span&gt;"1"&lt;/span&gt; has been assigned to the variable, so variable is &lt;span&gt;string&lt;/span&gt; type, then &lt;span&gt;integer&lt;/span&gt; &lt;span&gt;0&lt;/span&gt; was added to it so it changes its type to &lt;span&gt;integer&lt;/span&gt;. After that, let's multiply the &lt;span&gt;integer&lt;/span&gt; variable by &lt;span&gt;float&lt;/span&gt; &lt;span&gt;1.5&lt;/span&gt;. The variable is now &lt;span&gt;float&lt;/span&gt; type.&lt;/p&gt;

&lt;p&gt;This example shows how &lt;strong&gt;automatic conversion is performed by operators&lt;/strong&gt;, in this case adding and multiplying. It is enough that one of the sides of the action is of the &lt;span&gt;float&lt;/span&gt; type, then both will be treated as &lt;span&gt;float&lt;/span&gt; and the result is also going to be of this type. The case is similar with &lt;span&gt;integer&lt;/span&gt; although &lt;span&gt;float&lt;/span&gt; is "stronger" and in the case of &lt;span&gt;integer&lt;/span&gt; + &lt;span&gt;float&lt;/span&gt; both sides will be treated as &lt;span&gt;float&lt;/span&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unequal equal
&lt;/h2&gt;

&lt;p&gt;Let's get to equality, first look at the php documentation&lt;sup id="fnref3"&gt;3&lt;/sup&gt; :&lt;/p&gt;

&lt;blockquote&gt;Equal: $a == $b, TRUE if $a is equal to $b after type juggling.&lt;/blockquote&gt;

&lt;p&gt;Sounds trivial and looks natural. Type juggling results in that, there is no need to think too much about what we compare, &lt;strong&gt;all is done automaticall&lt;/strong&gt;y... ;) but is it?&lt;/p&gt;

&lt;p&gt;Time for a quiz, will the following conditions be met?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"eleven"&lt;/span&gt;
&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"wtf-1.3e3"&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\r&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"10 - 10"&lt;/span&gt;
&lt;span class="s2"&gt;"-1300"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"-1.3e3"&lt;/span&gt;
&lt;span class="mi"&gt;9223372036854775807&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"9223372036854775811"&lt;/span&gt;
&lt;span class="s2"&gt;"1.00000000000000001"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"1.00000000000000002"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now attention, drums... in &lt;strong&gt;each of above cases it will be &lt;span&gt;true&lt;/span&gt;&lt;/strong&gt;. What just happened here? The, so far, highly intuitive comparison operator has now behaved in a manner rather unnatural to the eye.&lt;/p&gt;

&lt;p&gt;How does the comparison operator actually work? To find out, it's best to look at how it was implemented&lt;sup id="fnref4"&gt;4&lt;/sup&gt;. However, this is not a simple reading, because the code is very complicated there. In short, the operation of &lt;span&gt;==&lt;/span&gt; &lt;strong&gt;depends on the type of its arguments&lt;/strong&gt;. Pairs of argument types are defined. If a given pair is supported (it is known how to compare it), the result of the comparison is returned, but if the given pair of types is not supported, the arguments are cast to another type (&lt;em&gt;Type Juggling&lt;/em&gt;) and are only then compared.&lt;br&gt;
In most cases this works in a predictable manner, but there are pairs of types that can surprise.&lt;/p&gt;
&lt;h3&gt;
  
  
  numeric value - string with a number and something extra
&lt;/h3&gt;

&lt;p&gt;When &lt;span&gt;string&lt;/span&gt; is compared with &lt;span&gt;integer&lt;/span&gt; or &lt;span&gt;float&lt;/span&gt;, then the &lt;span&gt;string&lt;/span&gt; is cast to a numeric value, but this casting can sometimes be surprising.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"10 - 10"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, the value of &lt;span&gt;string&lt;/span&gt; is cast on to the &lt;span&gt;integer&lt;/span&gt;, if &lt;span&gt;string&lt;/span&gt; starts with a number, then &lt;strong&gt;whatever goes after that number is ignored&lt;/strong&gt;. In this case &lt;span&gt;"10 - 10"&lt;/span&gt; will not be the result of subtraction, so not &lt;span&gt;0&lt;/span&gt; but &lt;span&gt;10&lt;/span&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  numeric value - string with a non-number at the beginning
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Lorem ipsum dolor sit 0"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;span&gt;string&lt;/span&gt; does not start with a number, then along with a numeric value &lt;strong&gt;it will always be cast on to &lt;span&gt;0&lt;/span&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  numeric value - string with a big number
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="mi"&gt;9223372036854775807&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"9223372036854775811"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;span&gt;integer&lt;/span&gt; from this comparison is the maximum &lt;span&gt;integer&lt;/span&gt; value - PHP_INT_MAX (on 64-bit platforms), the &lt;span&gt;string&lt;/span&gt; on the other hand includes a number slightly higher, just at 4 ;). In this case the &lt;span&gt;integer&lt;/span&gt; will be cast to &lt;span&gt;float&lt;/span&gt;, so the &lt;span&gt;string&lt;/span&gt; eventually also to &lt;span&gt;float&lt;/span&gt;. Such is the nature of &lt;span&gt;float&lt;/span&gt;.&lt;/p&gt;

&lt;blockquote&gt;
So never trust floating number results to the last digit, 
and do not compare floating point numbers directly for equality.
&lt;/blockquote&gt;

&lt;p&gt;It's a warning from PHP documentation&lt;sup id="fnref5"&gt;5&lt;/sup&gt; so &lt;strong&gt;it's better not to compare &lt;span&gt;float&lt;/span&gt; values directly&lt;/strong&gt;, but use dedicated functions:&lt;/p&gt;

&lt;h3&gt;
  
  
  numeric value - string with a number and white characters
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\r&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;White characters are ignored when casting to a numeric value. If you look at the example above you'll find &lt;strong&gt;one &lt;span&gt;"\n\r&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;\t"&lt;/span&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  string - string
&lt;/h3&gt;

&lt;p&gt;When comparing two &lt;span&gt;string&lt;/span&gt; values it would be natural if it were &lt;strong&gt;&lt;span&gt;true&lt;/span&gt; when they are the same&lt;/strong&gt; ;) but what does it mean "the same".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;1"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s2"&gt;1"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="s2"&gt;"1.0"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;1"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="s2"&gt;"1e0"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="s2"&gt;"-0.5e-2"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"-0.005"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="s2"&gt;"1.00000000000000001"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"1.00000000000000009"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;

&lt;span class="s2"&gt;"10"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"0xA"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// true  PHP &amp;lt; 7&lt;/span&gt;
                 &lt;span class="c1"&gt;// false PHP &amp;gt;= 7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When comparing &lt;span&gt;string&lt;/span&gt; type arguments, at the beginning both arguments are cast to a numeric value. If it succeeds, they are compared as numerical values. Interestingly, &lt;strong&gt;PHP can do a lot of tricks when searching for a number in string&lt;/strong&gt;&lt;sup id="fnref6"&gt;6&lt;/sup&gt;. Scientific notation or leading whitespace are not a problem, as well as hexadecimal notation up to version 7.0.&lt;/p&gt;

&lt;h3&gt;
  
  
  object - object
&lt;/h3&gt;

&lt;p&gt;If it was too clear and predictable up to now, it works a little different when comparing objects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Example&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nv"&gt;$property&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Example&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$bar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Example&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$bar&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$foo&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nv"&gt;$bar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="nv"&gt;$foo&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$bar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Considering what I wrote earlier, everything in the example above is correct. The &lt;span&gt;$foo&lt;/span&gt; and &lt;span&gt;$bar&lt;/span&gt; variables are of the same type (Example class), their property (&lt;span&gt;$property&lt;/span&gt;) in &lt;span&gt;$foo&lt;/span&gt; is equal to 1 (&lt;span&gt;integer&lt;/span&gt;) and in &lt;span&gt;\$bar&lt;/span&gt; is equal to 1.0 (&lt;span&gt;float&lt;/span&gt;). Thus, the result of the &lt;span&gt;==&lt;/span&gt; comparison is &lt;span&gt;true&lt;/span&gt;. Because the values match, types don't have to (&lt;span&gt;integer&lt;/span&gt; vs. &lt;span&gt;float&lt;/span&gt;). &lt;strong&gt;For &lt;span&gt;===&lt;/span&gt; the result is &lt;span&gt;false&lt;/span&gt;&lt;/strong&gt; because the class property types do not match?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$baz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Example&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$qux&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Example&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$baz&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nv"&gt;$qux&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="nv"&gt;$baz&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$qux&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happened here? Both variables are of the same type and their property is of the same value and of the same type, and &lt;strong&gt;still the result of &lt;span&gt;===&lt;/span&gt; is &lt;span&gt;false&lt;/span&gt;&lt;/strong&gt;.&lt;br&gt;
When comparing objects, identity (&lt;span&gt;===&lt;/span&gt;) will return as &lt;span&gt;true&lt;/span&gt; only if the same instance of the same class is compared&lt;sup id="fnref7"&gt;7&lt;/sup&gt; . Yes. It is not enough for a variable's type (class) and all its properties to match, it must be same object. Comparison &lt;span&gt;==&lt;/span&gt; will compare the object type and compare all its properties (also with &lt;span&gt;==&lt;/span&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  So how should I compare?
&lt;/h2&gt;

&lt;p&gt;In the examples I showed, it is clear that using the &lt;span&gt;==&lt;/span&gt; comparison, sometimes may give a result different from what we expect. How to deal with this? Every time you write &lt;span&gt;==&lt;/span&gt; (two equal signs), a red light should start flashing. We should always use the identity operator &lt;span&gt;===&lt;/span&gt; (three equal signs), which also compares the type of the variable and does not cast anything before that. Well, almost always.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The only time we can use the equality &lt;span&gt;==&lt;/span&gt; instead of identity operator &lt;span&gt;===&lt;/span&gt; is when we do it intentionally and we have reasons for that&lt;/strong&gt;!&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://www.php.net/manual/en/language.types.type-juggling.php"&gt;&lt;/a&gt;&lt;a href="https://www.php.net/manual/en/language.types.type-juggling.php"&gt;https://www.php.net/manual/en/language.types.type-juggling.php&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;&lt;a href="https://www.php.net/manual/en/migration74.new-features.php"&gt;&lt;/a&gt;&lt;a href="https://www.php.net/manual/en/migration74.new-features.php"&gt;https://www.php.net/manual/en/migration74.new-features.php&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;&lt;a href="https://www.php.net/manual/en/language.operators.comparison.php"&gt;&lt;/a&gt;&lt;a href="https://www.php.net/manual/en/language.operators.comparison.php"&gt;https://www.php.net/manual/en/language.operators.comparison.php&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;&lt;a href="https://github.com/php/php-src/blob/PHP-7.4.2/Zend/zend_operators.c#L2022"&gt;&lt;/a&gt;&lt;a href="https://github.com/php/php-src/blob/PHP-7.4.2/Zend/zend_operators.c#L2022"&gt;https://github.com/php/php-src/blob/PHP-7.4.2/Zend/zend_operators.c#L2022&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;&lt;a href="https://www.php.net/manual/en/language.types.float.php"&gt;&lt;/a&gt;&lt;a href="https://www.php.net/manual/en/language.types.float.php"&gt;https://www.php.net/manual/en/language.types.float.php&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;&lt;a href="https://github.com/php/php-src/blob/PHP-7.4.2/Zend/zend_operators.c#L2908"&gt;&lt;/a&gt;&lt;a href="https://github.com/php/php-src/blob/PHP-7.4.2/Zend/zend_operators.c#L2908"&gt;https://github.com/php/php-src/blob/PHP-7.4.2/Zend/zend_operators.c#L2908&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn7"&gt;
&lt;p&gt;&lt;a href="https://www.php.net/manual/en/language.oop5.object-comparison.php"&gt;&lt;/a&gt;&lt;a href="https://www.php.net/manual/en/language.oop5.object-comparison.php"&gt;https://www.php.net/manual/en/language.oop5.object-comparison.php&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>php</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Strangler pattern approach to migrating applications - pros and cons</title>
      <dc:creator>Michał Kurzeja</dc:creator>
      <pubDate>Mon, 11 Jul 2022 07:10:27 +0000</pubDate>
      <link>https://dev.to/accesto/strangler-pattern-approach-to-migrating-applications-pros-and-cons-40fk</link>
      <guid>https://dev.to/accesto/strangler-pattern-approach-to-migrating-applications-pros-and-cons-40fk</guid>
      <description>&lt;p&gt;The strangler pattern is a common approach to rewriting, modernizing and migrating existing (legacy) software to a new approach/solution/implementation. We already covered some topics related with it in the past. Mentioned it as one of the &lt;a href="https://accesto.com/blog/handle-technical-debt-in-legacy-application-4-possible-scenarios/"&gt;alternatives for system rewriting&lt;/a&gt;, and described a &lt;a href="https://dev.tocase-study%20migration"&gt;case-study migration&lt;/a&gt; in one of our projects. But it seems, that strangler pattern is gaining more and more visibility in recent years, and this is not a surprise for us.&lt;/p&gt;

&lt;p&gt;Strangler pattern comes with a good set of pros that it brings to a project. Also, the toolset that are now available to developers allow to easily perform this kind of migrations on living systems. Let’s take a deeper look at some basics, and the pros and cons that arise from them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strangler pattern approach
&lt;/h2&gt;

&lt;p&gt;Let’s focus on the very basics first. What does the strangler pattern mean?&lt;/p&gt;

&lt;p&gt;Well, in this pattern, we start with a legacy system that we need to rewrite, modernize or migrate to something newer. Quite often such legacy systems use some outdated technologies, frameworks, libraries, and their overall complexity makes it very hard or even impossible to refactor the code bit by bit.&lt;/p&gt;

&lt;p&gt;Our first step with the strangler pattern is to put an intermediary facade in front of the old system. In that case, all requests need to go through the facade, before they reach the legacy system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ia7PgPvp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdp5c5u4muk803wvw0lp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ia7PgPvp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdp5c5u4muk803wvw0lp.png" alt="Introducing a strangler pattern facade" width="880" height="295"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a next step, we decide what part of the system (what service) we would like to migrate. Let’s assume we work on an e-commerce system. We could decide to migrate the home page first. In that case, we write this part of the system from scratch, using a greenfield approach. You can choose the tech stack, the architecture - everything. There are no major constrains. In our case, we usually decide to use Symfony, and an event-driven architecture.&lt;/p&gt;

&lt;p&gt;The new system is built next to the old system:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z6J4KjMA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hoqyb4j84nmvrz8grufm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z6J4KjMA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hoqyb4j84nmvrz8grufm.png" alt="Introducing a new system" width="880" height="295"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the team is finished with the development, and the new code is tested, we can adjust the facade and move the traffic for home page from our old system to our new service.&lt;/p&gt;

&lt;p&gt;Following this approach, we can migrate the legacy application part by part. There is no need to work with the legacy code, all new features can be implemented in the new system. You maintain the old application, and at the same time add functionality to the new system. By following the strangler pattern approach, you can pay back the technical debt and add new features at the same time. Eventually, the new architecture will be completely replacing old code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pros and cons of strangler pattern implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pros
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Minimize risk of migration by splitting the migration process into smaller, separated chunks of work. Divide and conquer!&lt;/li&gt;
&lt;li&gt;Allows to quickly deliver business value and achieve visible results - rewriting a small subset of features takes significantly less than rewriting the whole system.&lt;/li&gt;
&lt;li&gt;Has no major constraints regarding the software architecture and technology behind the new system.&lt;/li&gt;
&lt;li&gt;In most cases, the development effort required to rewrite the entire application is lower than in other approaches&lt;/li&gt;
&lt;li&gt;Easy to implement and understand. The code complexity is rather low.&lt;/li&gt;
&lt;li&gt;Works well even when refactoring a complex system.&lt;/li&gt;
&lt;li&gt;Allows to implement microservices architecture and split the work between multiple development teams. But as there are no constraints on the software architecture, you can also apply it to a monolithic application.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cons
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Requires a lot of attention to the boundaries between old and new parts. Connection points, data exchange, etc.&lt;/li&gt;
&lt;li&gt;As a result of the above, might require to write lots of Adapters.&lt;/li&gt;
&lt;li&gt;It is quite easy to break some best practices and end with a dependency hell.&lt;/li&gt;
&lt;li&gt;Sometimes rollback scenarios might be hard to implement, and you obviously should have them.&lt;/li&gt;
&lt;li&gt;When implemented wrongly, the parts might become tightly coupled.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;As always, using a software pattern comes with some consequences. You literally exchange one problem for another one. There are no silver bullets, but by knowing different approaches you can decide on what problems mean less risk for you.&lt;/p&gt;

&lt;p&gt;In case of strangler pattern, you exchange the huge risk of rewriting a legacy monolithic application into smaller, less risky changes. You iterate faster, in a more agile way. There are obviously some consequences that arise, and you should be aware of, but smaller failures are always easier to fix, and that's what the Strangler fig pattern offers you.&lt;/p&gt;

</description>
      <category>architecture</category>
    </item>
    <item>
      <title>Boy scout rule in 6 examples - the basic principle of web development</title>
      <dc:creator>Michał Romańczuk</dc:creator>
      <pubDate>Mon, 20 Jun 2022 09:23:56 +0000</pubDate>
      <link>https://dev.to/accesto/boy-scout-rule-in-6-examples-the-basic-principle-of-web-development-nnl</link>
      <guid>https://dev.to/accesto/boy-scout-rule-in-6-examples-the-basic-principle-of-web-development-nnl</guid>
      <description>&lt;p&gt;Not everyone was a scout in his youth. Most of programmers, however, have probably heard of the &lt;strong&gt;Boy Scout Rule in software development&lt;/strong&gt;. But can you name a few of the activities that are part of that rule? When I started to explore this topic, looking for what exactly needs to be done to say &lt;strong&gt;"I just applied the boy scout rule"&lt;/strong&gt;, I only found one thing: "Leave your code better than you found it" - everyone quotes Uncle Bob. But what does it mean? What am I supposed to do exactly?&lt;/p&gt;

&lt;p&gt;In scouting it is easier, everyone can see if the surroundings are clean, what can be cleaned to make it cleaner, but in the code? I will try to show you a few examples from my daily work where I try to stick to this principle.&lt;/p&gt;

&lt;p&gt;Most of us use some IDE, they are getting smarter and in many cases, they keep the code clean, suggest better solutions, "clean up" automatically with one click. However, &lt;strong&gt;it's worth keeping an eye on what's happening in the code&lt;/strong&gt; and not rely entirely on the IDE.&lt;/p&gt;

&lt;h2&gt;
  
  
  The campground, not an entire forest!
&lt;/h2&gt;

&lt;p&gt;I am allergic to multiple blank lines. When doing a code review, I also pay attention to what the code looks like. &lt;strong&gt;The code must be readable&lt;/strong&gt;, multiple blank lines, in my opinion, are disturbing. So I sometimes leave a comment in CR like "one blank line is enough (everywhere)". One day my colleague got it a little bit differently than I meant. That merge request before my first review contained 7 changed files. What was my surprise when I saw it after applying the changes - 350 files changed… he removed multiple lines from the whole project. You can not do this, such changes introduce a huge disruption to the code, the MR becomes completely unreadable. &lt;/p&gt;

&lt;p&gt;You can't go overboard with cleaning. “Leave the campground cleaner than you found it”, &lt;strong&gt;the campground, not an entire forest!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Code cleanup should be limited to the files we work with. If we add a new method to the class and we notice something that can be improved/cleaned up "next to" - this is the place where we should apply the Boy Scout Rule. &lt;/p&gt;

&lt;h2&gt;
  
  
  Examples in practice
&lt;/h2&gt;

&lt;h3&gt;
  
  
  a) deprecations
&lt;/h3&gt;

&lt;p&gt;If you use a smart IDE, it probably detects deprecated methods. The IDE I'm using strikes out deprecated names. Below, you can see crossed-out annotations &lt;em&gt;@ Route&lt;/em&gt; and &lt;em&gt;@Method&lt;/em&gt;. Something like that is very noticeable, it's hard to miss it.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--52eH83IM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vqgnbrqui0yobzsiow5p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--52eH83IM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vqgnbrqui0yobzsiow5p.png" alt="Deprecations example 1 - before" width="502" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inside the classes of these deprecated annotations, I found lines like this:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tEwhzg0K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c7de32dfju9xu0bal1n6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tEwhzg0K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c7de32dfju9xu0bal1n6.png" alt="Deprecations example 1 - hint 1" width="880" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--83v6kqWG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/atbeek3lqman90kf84gl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--83v6kqWG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/atbeek3lqman90kf84gl.png" alt="Deprecations example 1 - hint 2" width="880" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a very easy change to apply. They say what exactly we have to do. Just like in the screen below. A few seconds change and one thing less hurts the eyes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Dvv4x6RE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a5cgcni4vl3gpnbaxq08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Dvv4x6RE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a5cgcni4vl3gpnbaxq08.png" alt="Deprecations example 1 - after" width="708" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It was an outdated annotation, now an example of outdated method. More crossed letters below. Method &lt;em&gt;setData()&lt;/em&gt;. Just like a moment ago. It cannot be overlooked.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xxfxcxOL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j4m6a3fzyvm9mrvzciku.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xxfxcxOL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j4m6a3fzyvm9mrvzciku.png" alt="Deprecations example 2 -  before" width="855" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A good scout will not ignore it. &lt;strong&gt;It is like a plastic bottle on the ground&lt;/strong&gt; in the campground, that needs to be cleaned up. So just check this method, maybe the author left a hint. And I did it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FBnceCwQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/om537gu4c6ygyan158fy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FBnceCwQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/om537gu4c6ygyan158fy.png" alt="Deprecations example 2 - hint" width="880" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, we have exactly what to replace. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Pmt3c1WL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/it7msy6yyhxn21i8pxqr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Pmt3c1WL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/it7msy6yyhxn21i8pxqr.png" alt="Deprecations example 2 - after" width="731" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the last one. We're using some vendor class here that is deprecated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6iEAxvEc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uyj73z024w4ha3lw7b3y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6iEAxvEc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uyj73z024w4ha3lw7b3y.png" alt="Deprecations example 3 - before" width="880" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just open it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HWE8bdWw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e28l6vcx2s8pzbcpem3y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HWE8bdWw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e28l6vcx2s8pzbcpem3y.png" alt="Deprecations example 3 - hint" width="880" height="116"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And another quick change&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7XgzS1kF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kempzztpvl1g0xjx9aav.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7XgzS1kF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kempzztpvl1g0xjx9aav.png" alt="Deprecations example 3 - after" width="880" height="131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, if you are using a less wise IDE, looking for deprecations is not that easy. Nobody in their right mind opens every class and method they use to look for information about deprecations. We just use what we know, usually without much thought. Therefore, &lt;strong&gt;it is worth using tools that support our work&lt;/strong&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  b) unused code
&lt;/h3&gt;

&lt;p&gt;The code changes very often, especially in the early stages of development. With changes, sometimes you miss to delete an unused import, variable, or method.&lt;/p&gt;

&lt;p&gt;A smart IDE detects unused code, for example, turns unused private methods to gray. This also works for unused imports, unused private properties and parameters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4wHE3eag--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h95gsuh0grkq9gl7rn16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4wHE3eag--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h95gsuh0grkq9gl7rn16.png" alt="Unused code - example 1" width="763" height="87"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gbl2FfMS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mbqnaxnqtospazhufrvu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gbl2FfMS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mbqnaxnqtospazhufrvu.png" alt="Unused code - example 2" width="312" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--om8Xlfih--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cl5fun7x8a5foitv8x24.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--om8Xlfih--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cl5fun7x8a5foitv8x24.png" alt="Unused code - example 3" width="479" height="90"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But you have to be careful, &lt;strong&gt;I never trust the IDE completely&lt;/strong&gt;, it is better to double-check that deleting a piece of code won't break something. Of course, your code is fully test covered, so you can be sure nothing's broken ;).&lt;/p&gt;

&lt;p&gt;It's not that easy with public methods, they don't turn gray automatically ;). You need to be more careful with them. When you stop using some public method, let's just check to see if it’s used somewhere else. If not and no one needs it anymore, let's get rid of it. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We should remove unused code.&lt;/strong&gt; That's what we use version-control systems for, so we don't have to see "maybe it will be useful someday" unused code. (YAGNI)&lt;/p&gt;

&lt;h3&gt;
  
  
  c) naming
&lt;/h3&gt;

&lt;p&gt;We have to name many things in our work. Variables, methods, classes, everything must have a name. What should a good name look like? Not too long, not too short, and clear to anyone who can see it. It is not easy at all. I believe that &lt;strong&gt;this is a key part of our work&lt;/strong&gt;. A badly named variable can mess up a lot and add a lot of work to others who later have to get into our code. It should be understandable without thinking. After reading the name of the method, the names of its arguments, and what it returns, we should know exactly what it does, without looking inside. &lt;/p&gt;

&lt;p&gt;When I see the name of the variable $a, $var, $arr or $array in the code review, I immediately get a fever. &lt;/p&gt;

&lt;p&gt;And below is an example of a variable with the two-letter name "ac". To know what it will contain, you need to look where something is assigned, it is easy to find if it is a constructor, and not always it will be there. It is obvious to the author of this code at the time of writing that "ac" stands for "authorizationChecker". But not everyone will figure it out, even the author after a while may not be sure what "ac" meant.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D_0qDS-P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ni1165u1qnkoufsd340w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D_0qDS-P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ni1165u1qnkoufsd340w.png" alt="Naming - before" width="551" height="126"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A good scout, seeing such a variable, will quickly change its name to clean up a bit. For example, such a change is enough:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2OXtZkBf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b3ryif4q0n0qcvuri79o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2OXtZkBf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b3ryif4q0n0qcvuri79o.png" alt="Naming - after" width="687" height="127"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't save on letters or you'll pay for it later&lt;/strong&gt; trying to understand what the abbreviations mean.&lt;/p&gt;

&lt;h3&gt;
  
  
  d) code formatting
&lt;/h3&gt;

&lt;p&gt;The worst thing I have seen about improving code formatting is to use autoformat in the IDE all over the project directory ("not an entire forest"!). Let's clean around the place where we work, but not too far, &lt;strong&gt;one file or one method is enough&lt;/strong&gt;. Someone who reviews the code should be able to focus on important things and not scroll through hundreds of non-essential changes.&lt;/p&gt;

&lt;p&gt;"My formatting is the clearest and most readable" - every programmer. Everyone has their own taste and habits, so you need to agree on the details of code formatting in your team. You probably have your standards in the project you work in. However, there is one thing everyone should follow - the PSR-12 (If you missed, the PSR-2 is deprecated).&lt;/p&gt;

&lt;p&gt;Let’s see some examples.&lt;/p&gt;

&lt;h4&gt;
  
  
  one-line IF.
&lt;/h4&gt;

&lt;p&gt;Very unreadable:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CrgmGzoN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0h1g88agdpxli8cmel3y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CrgmGzoN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0h1g88agdpxli8cmel3y.png" alt="Code formatting - example 1 - before" width="671" height="117"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just add a few spaces and new lines&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---b0hpWSy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dtve6nokdi3rraq2w6jv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---b0hpWSy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dtve6nokdi3rraq2w6jv.png" alt="Code formatting - example 1 - after 1" width="345" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;or a short IF syntax&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pIpck9UC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u2fzq6rg3ggh9m27biy9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pIpck9UC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u2fzq6rg3ggh9m27biy9.png" alt="Code formatting - example 1 - after 2" width="345" height="106"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  multiple blank lines
&lt;/h4&gt;

&lt;p&gt;I don't know why, but very common&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--11IVHupM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wq7xt1lu6rwq1jztlrta.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--11IVHupM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wq7xt1lu6rwq1jztlrta.png" alt="Code formatting - example 2 - before" width="378" height="103"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just be a good scout and remove unnecessary blank lines&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RhaGEXgz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/08ntnehti8gkg88aunkx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RhaGEXgz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/08ntnehti8gkg88aunkx.png" alt="Code formatting - example 2 - after" width="349" height="57"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  strange indentation in the code.
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D0V_QUMf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y07qxg6fzidtb0xvi0he.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D0V_QUMf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y07qxg6fzidtb0xvi0he.png" alt="Code formatting - example 3 - before" width="362" height="146"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Are you uncomfortable reading it too? When you see something like this in the code, &lt;strong&gt;you can easily do the right thing and correct the indentation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kW5j1gr8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nqmje16461361oc2huy3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kW5j1gr8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nqmje16461361oc2huy3.png" alt="Code formatting - example 3 - after" width="345" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  all other things incompatible with PSR-12
&lt;/h4&gt;

&lt;p&gt;Boy Scout is not looking for whom to blame, but clean code himself, leaving the code better than he found (DO NOT apply this rule in code review, such things must be eliminated immediately! ;))&lt;/p&gt;

&lt;h3&gt;
  
  
  e) complex conditions
&lt;/h3&gt;

&lt;p&gt;How many times have you wasted time trying to understand a complicated if condition? For me - too much. Worse, with many conditions, it is often easy to get confused (if you have tests, you are a lucky scout)&lt;/p&gt;

&lt;p&gt;I made up an example below. let's say it's a method that handle some response. It first checks if the response is valid, if so, does something with it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r2tmct7C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mmrvg81ni5xdcu9e7jcf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r2tmct7C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mmrvg81ni5xdcu9e7jcf.png" alt="Complex conditions - before" width="531" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is not overly complicated to analyze, but it does take a moment to analyze what is being checked here. &lt;/p&gt;

&lt;p&gt;A good scout will find a few things here that he can clean up so that others will enjoy reading the code. But the most important thing is to &lt;strong&gt;separate the entire condition&lt;/strong&gt; into a separate method whose name will clearly say what the condition checks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mMv7kTtx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0xlse45bp545i4m59949.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mMv7kTtx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0xlse45bp545i4m59949.png" alt="Complex conditions - after" width="523" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, in this example condition, many other things can be improved ;). But the point here was that &lt;strong&gt;we took the time to understand what it checks, so let's make it easy for other people&lt;/strong&gt; not to waste that time. &lt;/p&gt;

&lt;h3&gt;
  
  
  f) code duplication
&lt;/h3&gt;

&lt;p&gt;Why do we find duplicate code in projects? If something works in one place, it will work in another ;). But there is one problem. If you want to change or correct something later, you must remember to do it in all places where you copied the code fragment. &lt;strong&gt;Mindless copy-pasting is unacceptable&lt;/strong&gt; and no one should do it. But we often see that it happens, all the time. When a good scout sees duplicate code, he just clean it. The smart IDE notices that the code is duplicated, it should alert you.&lt;/p&gt;

&lt;p&gt;Here is an example to show what I'm talking about&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--V6bBn4Xy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4yrpnn6p6520488x99s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V6bBn4Xy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4yrpnn6p6520488x99s.png" alt="Code duplications - before" width="880" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have two methods here, even if you search, you won't notice the difference other than the function name. &lt;/p&gt;

&lt;p&gt;The first method sends an email with some positive decision, and the second method sends an email with some negative decision. For some reason, instead of extracting common code, someone just copied it. &lt;/p&gt;

&lt;p&gt;Then, you have the task to change the title of sent emails, and you noticed what it looks like. Now you have two options what to do. First one is to only change the title in both places. If it works, why not? Well, NO! &lt;strong&gt;A good scout would do more than that. He'd clean up that piece of code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r1O7M2bv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7abxof1mc3o3jz1j9qsf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r1O7M2bv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7abxof1mc3o3jz1j9qsf.png" alt="Code duplications - after" width="880" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It doesn't take much time, and the code is a bit cleaner and easier to maintain.&lt;/p&gt;

&lt;h3&gt;
  
  
  g) what else?
&lt;/h3&gt;

&lt;p&gt;Anything that will improve the readability of the code or its maintenance. You are not doing it for others, you are doing it for yourself in the future. &lt;br&gt;
Doing more serious refacotring (eg. to ensure &lt;a href="https://accesto.com/blog/solid-php-solid-principles-in-php/"&gt;SOLID princinples&lt;/a&gt;) may not be a quick job. But all the examples shown above can be done in less than a minute. This is well invested time. It will pay off in the future. &lt;/p&gt;

&lt;h2&gt;
  
  
  Need further help?
&lt;/h2&gt;

&lt;p&gt;Boy Scout rule is a good practice that helps to keep the &lt;a href="https://accesto.com/blog/technical-debt-the-number-one-reason-why-software-development-projects-get-derailed/"&gt;technical debt&lt;/a&gt; low. But sometimes it is just not enough. If you are no longer able to cope with the code of your web application just contact us. We help our clients fight the &lt;a href="https://accesto.com/blog/handle-technical-debt-in-legacy-application-4-possible-scenarios/"&gt;technical debt in legacy PHP &amp;amp; Javascript applications&lt;/a&gt;. &lt;br&gt;
__&lt;/p&gt;

</description>
      <category>php</category>
      <category>codequality</category>
    </item>
    <item>
      <title>Making sure your website won't crash again</title>
      <dc:creator>Piotr Gołofit</dc:creator>
      <pubDate>Fri, 17 Jun 2022 06:55:26 +0000</pubDate>
      <link>https://dev.to/accesto/making-sure-your-website-wont-crash-again-53ba</link>
      <guid>https://dev.to/accesto/making-sure-your-website-wont-crash-again-53ba</guid>
      <description>&lt;p&gt;In my &lt;a href="https://accesto.com/blog/why-new-users-can-kill-your-web-application/" title="Why new users can kill your web application?"&gt;previous blog post&lt;/a&gt;, I wrote about situations when an increase in user traffic could cause your website a lot of trouble. Unexpected popularity, instead of bringing you new customers, could cause the web services to go down. Today I would like to share with you a story from my own experience, a lesson learned from the &lt;b&gt;spectacular crash of a popular startup&lt;/b&gt;. How did it happen? How did it recover? And how this spectacular disaster hardened the website and made it ready for the future?&lt;/p&gt;

&lt;h2&gt;
  
  
  A successful startup gaining traction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://szukamlapka.pl/" title="SzukamLapka.pl"&gt;SzukamLapka&lt;/a&gt; is a very popular polish website that &lt;b&gt;advises on purchasing laptops&lt;/b&gt;. It helps both IT newbies, and more tech-savvy customers as well. Its AI-driven algorithms review specs, benchmarks and offers to "understand" the industry and generate &lt;b&gt;personalized rankings of the best gear&lt;/b&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Pq-TOFqo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zyrf92ei8z0u1ey0uq39.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Pq-TOFqo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zyrf92ei8z0u1ey0uq39.jpg" alt="NoteookRank homepage - advice on purchasing laptop for tech and non-tech peopl" width="880" height="527"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;center&gt;Advice on purchasing laptop for non-tech and more tech-savvy customers&lt;/center&gt;
&lt;br&gt;

&lt;p&gt;As &lt;a href="https://accesto.com/" title="Accesto.com"&gt;Accesto&lt;/a&gt;, we have been their partners since early, MVP stages of SzukamLapka. This year they are &lt;b&gt;launching a global version&lt;/b&gt; under the brand &lt;a href="https://notebookrank.com/" title="NotebookRank.com"&gt;NotebookRank&lt;/a&gt;, but a few years ago, they were just a local startup, &lt;b&gt;growing and gaining traction&lt;/b&gt; on a polish market. But in the late Monday evening of August 2017, this growth got out of control...&lt;/p&gt;

&lt;h2&gt;
  
  
  Surviving the hug of death
&lt;/h2&gt;

&lt;p&gt;Almost every country has its equivalent to Reddit or Digg websites - places where users share, rate, and discuss &lt;b&gt;useful links&lt;/b&gt;. In Poland, it is &lt;a href="https://wykop.pl/" title="Wykop.pl" rel="nofollow"&gt;Wykop.pl&lt;/a&gt;, and on that day, someone who considered SzukamLapka to be very useful shared this knowledge with Wykop users. And it wasn’t the only person who found the named service worth using...&lt;/p&gt;

&lt;p&gt;In the first hour after being mentioned the &lt;b&gt;number of visitors doubled&lt;/b&gt;, but that was just a beginning. The exponential growth continued for the next 5 hours, reaching almost 10,000% late evening. That is a &lt;b&gt;hundred times more users than usual!&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sHo6F7Tb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gjiv6xpi2790dkwitnny.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sHo6F7Tb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gjiv6xpi2790dkwitnny.jpg" alt="Google Analytics: 1,000% increase in day-to-day traffic, and 10,000% in the peak!" width="812" height="413"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;center&gt;Google Analytics: 1,000% increase in day-to-dday traffic, and 10,000% in the peak!&lt;/center&gt;
&lt;br&gt;

&lt;p&gt;Usually, it is called a Reddit Hug of Death, or a Slashdot Effect, and in Poland... a Wykop Effect. It derives from the names of popular websites that caused many other sites to &lt;b&gt;crash due to the abrupt increase in short term traffic&lt;/b&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what happened?
&lt;/h2&gt;

&lt;p&gt;Probably you won't be surprised if I tell you that the website crashed. But, to be honest, we were a little bit surprised back then. The website was &lt;b&gt;already prepared to handle quite high volumes of traffic&lt;/b&gt;. It was optimized and performed well. Even when we did load testing with 20× more users than on an average day, all the rankings were generated in under 1 second. But sometimes you &lt;b&gt;can't spot the bottlenecks until you see them stuck&lt;/b&gt;.&lt;/p&gt;

&lt;p&gt;So what may be the bottleneck of a website that displays laptop rankings? The thing is that it isn’t just a simple website. Like most of the modern digital products, it is a complex web application, a multi-layer platform that consists of many services. And the bottleneck emerged between two of such services. The one that does all the complex calculations, and the other that uses these bare technical data and makes them more human-friendly. The question arose, why even though these two were connected via a robust and lightspeed API, the problem occurred?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--k9uR5tVP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o5ciwb9w1d7psnqc4f1e.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k9uR5tVP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o5ciwb9w1d7psnqc4f1e.jpg" alt="504 error seen when site went down" width="880" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both services were performing very well, but sometimes the &lt;b&gt;connection between them was lost for a few short milliseconds&lt;/b&gt;. And that was the part that we did not take into account. Although this was a very short break in communication, one of the services, instead of retrying to connect, &lt;b&gt;kept hanging for around 3 minutes&lt;/b&gt;. We didn’t notice it in advance as it happened only once every few thousand page views. And of course, we had a few services like this working in parallel. So if one user waited too long, he could easily refresh the page and get the results immediately from another non-hanging service.&lt;/p&gt;

&lt;p&gt;But on that day, the volume of users was so high that this quite rare situation of stuck service happened on all the services, and users started to get 504 errors - indicating that the server did not respond in time. &lt;b&gt;The site went completely down&lt;/b&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solving the problem
&lt;/h2&gt;

&lt;p&gt;The temporary solution was quite simple. When we diagnosed the problem, we quickly &lt;b&gt;increased the number of available services and restarted all the stuck ones&lt;/b&gt;. We had to repeat this few times, as new services kept hanging. But in general, that temporarily solved the problem and allowed us to survive till the morning.&lt;/p&gt;

&lt;p&gt;The next day, we started by making sure that last night's nightmare won't happen again. We did it in 4 steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: adopt the fail-safe approach in your web application
&lt;/h3&gt;

&lt;p&gt;We already knew that there could be temporary connection issues with the API, and to handle this, we &lt;b&gt;added proper timeouts and retries&lt;/b&gt;. So if the connection is lost, service does not wait long, and after a second or so, tries to connect again. And there are few attempts, each with slightly longer timeouts (to get the response even if the network connection is slow for some reason).&lt;/p&gt;

&lt;p&gt;According to Murphy's law, if something can fail, it will fail, and it is true also for &lt;b&gt;web applications&lt;/b&gt;. It is good practice to take potential failures into account and make web apps ready for them by design. This is known as a &lt;b&gt;fail-safe approach&lt;/b&gt; and is especially important if you integrate with some external, third party API’s - which may not always be available, or behave in the way we expect.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: make sure you calculate same things only once
&lt;/h3&gt;

&lt;p&gt;Although the connection with the API should no longer cause the trouble, we knew that this was the most compute-heavy part of the platform. So the fewer requests are sent to that API, &lt;b&gt;the better the website performance&lt;/b&gt; would be. We used &lt;a href="https://blackfire.io" title="blackfire.io" rel="nofollow"&gt;Blackfire.io&lt;/a&gt; to check how often the web app uses the API and we got interesting results. We noticed that the homepage, the most frequently visited page, used the API twice per each view. Why would a rather static page even require any API calls?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ItpA6KKt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e8fv0tw43weebmjz15lr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ItpA6KKt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e8fv0tw43weebmjz15lr.jpg" alt="Ranking in a header of SzukamLapka website" width="880" height="312"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;center&gt;Ranking sneak peak in header was calculated on every page view&lt;/center&gt;
&lt;br&gt;

&lt;p&gt;The reason was that on the homepage, there was a small sneak peek of the ranking, to engage users. There were also a couple of statistics shown, as the total number of laptops or offers analyzed. But how often did these numbers and results change? Every few hours. And how often did we calculate them? Every single page view. Huh. Adding an &lt;b&gt;HTML cache for the whole homepage&lt;/b&gt; worked like a charm, making it just a static HTML resource, that can be served by servers in a quite effortless way.&lt;/p&gt;

&lt;p&gt;Blackfire is a very good tool that &lt;b&gt;helps to find bottlenecks&lt;/b&gt;. To learn more, make sure to read our recent blog post &lt;a href="https://accesto.com/blog/my-homepage-is-slowing-down/" title="My homepage is slowing down"&gt;“My homepage is slowing down”&lt;/a&gt; in which our developer describes how he reduced page loading time by 93% in just 4 hours of work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: learn your user's behavior and use it to plan the performance
&lt;/h3&gt;

&lt;p&gt;Another optimization became easy when we analyzed data from Google Analytics. SzukamLapka generates personalized rankings based on the individual needs and preferences of the users. But according to Google Analytics, most of the users go for the predefined presets, like gaming, office, or mobile laptops. So why generate them for every user separately? &lt;b&gt;Adding a Redis cache&lt;/b&gt; for most frequently selected rankings reduced the computations by almost 70%.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: use CDN to cache your static resources
&lt;/h3&gt;

&lt;p&gt;Last but not least - we added &lt;a href="https://www.cloudflare.com/" rel="nofollow"&gt;Cloudflare&lt;/a&gt;. It is a service that provides an additional cache layer in front of the whole website and distributes all its static resources in CDNs. It makes the assets, like images or scripts, &lt;b&gt;highly available for users, and light for the server&lt;/b&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0-_fWg5P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ist62k5h52jrfsn8ajfo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0-_fWg5P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ist62k5h52jrfsn8ajfo.jpg" alt="Cloudflare reduced bandwidth by 60% and page load time by 80%" width="880" height="472"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;center&gt;Cloudflare reduced bandwidth by 60% and page load time by 80%&lt;/center&gt;
&lt;br&gt;

&lt;p&gt;Adding Cloudflare saved around 60% bandwidth traffic, making the website significantly lighter for the server. At the same time, the &lt;b&gt;user experience improved thanks to the reduction of the total load time to less than 1 second!&lt;/b&gt; To learn more about caching with Cloudflare, check the blog post of our CTO on &lt;a href="https://accesto.com/blog/top-5-hacks-to-fix-slow-web-applications/" title="Top 5 hacks to fix slow web applications"&gt;"Top 5 hacks to fix slow web applications"&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fast forward
&lt;/h2&gt;

&lt;p&gt;Thanks to this lesson learned, the website was adjusted, and right now can handle a &lt;b&gt;significantly large number of users&lt;/b&gt;. After a few years SzukamLapka is playing an important role in consumer decision making in Poland, generating millions in revenue for its partners. This year, SzukamLapka is launching its &lt;b&gt;global version&lt;/b&gt; in multiple countries around the world. Thanks to the lessons learned, it is prepared to become popular in the new markets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GJkft5A---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/istzk2zzi8hstzhb2nlv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GJkft5A---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/istzk2zzi8hstzhb2nlv.jpg" alt="PageSpeed" width="590" height="413"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;center&gt;Current version requires less than 1 second to be full interactive!&lt;/center&gt;
&lt;br&gt;
&lt;h2&gt;
  
  
  How about your web application?
&lt;/h2&gt;

&lt;p&gt;The story of the SzukamLapka startup &lt;b&gt;can happen to almost every online business&lt;/b&gt;. If you want to check how your web application can be hardened just drop me a message. At Accesto we look after, monitor, and improve web applications to make sure they are &lt;b&gt;secure, scalable, and ready for growth&lt;/b&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://accesto.com/contact/" id="let_us_help_you"&gt;Let us help you&lt;/a&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>performance</category>
    </item>
    <item>
      <title>Optimizing website? Careful with lazy loading!</title>
      <dc:creator>Piotr Gołofit</dc:creator>
      <pubDate>Thu, 05 May 2022 10:02:30 +0000</pubDate>
      <link>https://dev.to/accesto/optimizing-website-site-careful-with-lazy-loading-522g</link>
      <guid>https://dev.to/accesto/optimizing-website-site-careful-with-lazy-loading-522g</guid>
      <description>&lt;p&gt;A few weeks ago I was asked if Accesto could help to &lt;strong&gt;optimize a popular online store&lt;/strong&gt; selling natural cosmetics. Of course we can! I thought. That is what we do for a living, we improve existing web applications like SaaS, marketplaces, or eCommerce sites. Thus I thought it would be an optimization like every other. But it wasn’t.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick wins for website speed
&lt;/h2&gt;

&lt;p&gt;Although every online product is unique, they usually struggle with similar &lt;strong&gt;web performance pitfalls&lt;/strong&gt;. Non-optimal database queries, poor caching, too large images/resources, or unused imports, to name a few. Usually, there are &lt;strong&gt;a few things that can be fixed relatively quickly&lt;/strong&gt; and will result in quite a speed boost. Our CTO recently gathered such tweaks for you in his blogpost on &lt;a href="https://accesto.com/blog/top-5-hacks-to-fix-slow-web-applications/" rel="noopener noreferrer"&gt;top 5 hacks to fix slow web applications&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Top performance, which will hold the exponential growth of users, cannot be achieved overnight. But if your website is slowing down, and you haven't yet put a lot of effort into its optimization, there is a high chance that it can be refined in a quite reasonable time. Like in one of our &lt;a href="https://accesto.com/blog/my-homepage-is-slowing-down/" rel="noopener noreferrer"&gt;recent stories&lt;/a&gt; where we managed to &lt;strong&gt;decrease website loading time by 97% in just 4 hours of work&lt;/strong&gt;.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfeniuf04lw58o9bt6k0.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfeniuf04lw58o9bt6k0.jpeg" alt="Initial PageSpeed results"&gt;&lt;/a&gt; &lt;/p&gt;


&lt;center&gt;Initial PageSpeed results for online store selling natural cosmetics&lt;/center&gt;
&lt;br&gt;

&lt;p&gt;The case of natural cosmetics eCommerce seemed similar. The store owner sent me an email on Friday morning with the Google PageSpeed Insights report. It &lt;strong&gt;evaluated the overall site performance&lt;/strong&gt; on &lt;span&gt;61&lt;/span&gt;/100 for desktop, and &lt;span&gt;42&lt;/span&gt;/100 for mobile. Not a tragedy, but indeed a lot of room for improvement. Great! We will apply a few quick tweaks that will speed up the website and I will have good news for a customer even before the weekend!&lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s start with a website audit &amp;amp; checking Core Web Vitals
&lt;/h2&gt;

&lt;p&gt;We already knew the &lt;strong&gt;overall score&lt;/strong&gt; for this website - our starting point for optimization. This score is a &lt;strong&gt;composition of 6 metrics evaluated by PageSpeed&lt;/strong&gt;, which for that particular site presented like this:&lt;/p&gt;

&lt;p&gt;](&lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/50jualml4g4hqdi74z7f.jpeg" rel="noopener noreferrer"&gt;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/50jualml4g4hqdi74z7f.jpeg&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Quick look at the metrics and we see that &lt;strong&gt;4 out of 6 metrics look fine&lt;/strong&gt; (&lt;a href="https://web.dev/vitals/" rel="nofollow noopener noreferrer"&gt;Web Vitals&lt;/a&gt;), but the remaining two: &lt;em&gt;Speed Index&lt;/em&gt; and &lt;em&gt;Largest Contentful Paint (LCP)&lt;/em&gt; seem way too high. Understanding these metrics helps to &lt;strong&gt;decide where we shall start our optimization&lt;/strong&gt;, how much we should improve, and whether we can find some low-hanging fruits. Each of these &lt;strong&gt;metrics has a different weight&lt;/strong&gt; (importance). To see how exactly they influence the overall score you can use the &lt;a href="https://googlechrome.github.io/lighthouse/scorecalc/" rel="nofollow noopener noreferrer"&gt;scoring calculator&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsapdl8ov2n0ya5u81n8n.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsapdl8ov2n0ya5u81n8n.jpeg" alt="Lighthouse scoring calculator"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although PageSpeed evaluated both SI and LCP as poor ones, &lt;strong&gt;LCP has a higher weight&lt;/strong&gt; (25%), so improving it &lt;strong&gt;will have the highest impact on the overall score&lt;/strong&gt;. LCP is one of 3 important metrics introduced by Google, called &lt;strong&gt;Core Web Vitals&lt;/strong&gt;. Ok, but what exactly is this LCP? In short - it is the &lt;strong&gt;time at which the largest visible part of website content is painted&lt;/strong&gt;. This content could be an image, a large block of text, or any other element that occupies a significant part of the screen when the website loads. And the faster this element is displayed to the user, the better. &lt;/p&gt;

&lt;p&gt;Knowing that, we could assume that perhaps some product image or banner is too large, not scaled properly, or maybe not compressed. Or maybe images are served by PHP and not cached? Few basic checks should show us what is going on.&lt;/p&gt;

&lt;p&gt;We used a Lighthouse tool built-in Chrome web browser to &lt;strong&gt;profile the website&lt;/strong&gt;. We were looking for any performance pitfalls, but the audit results were… surprisingly good! &lt;strong&gt;The site wasn’t performing that bad at all!&lt;/strong&gt; Server response time was quite decent, images were served in a modern format and with proper sizing. All the site assets were properly optimized and served with proper cache policy. They even used some of the Progressive Web App capabilities to make the site work smoothly. &lt;br&gt;&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp6lbxotaowietgkp75hb.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp6lbxotaowietgkp75hb.jpeg" alt="Lighthouse page speed optimization tool in Google Chrome"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;center&gt;Lighthouse built in Google Chrome Developer Tools can be used to audit the website performance&lt;/center&gt;
&lt;br&gt;

&lt;p&gt;Ok, so maybe it was a &lt;strong&gt;traffic related issue&lt;/strong&gt;? Maybe site speed is good as long as the number of users using it is low? Worth a try. It is quite a common situation, many websites are tested only on a small number of users. &lt;strong&gt;When the user base grows website starts to clog&lt;/strong&gt;. More on that topic you can read in my post on &lt;a href="https://accesto.com/blog/why-new-users-can-kill-your-web-application/" rel="noopener noreferrer"&gt;why new users can kill you web application&lt;/a&gt;. I did a few checks with the website load testing tool &lt;a href="https://k6.io/" rel="nofollow noopener noreferrer"&gt;k6.io&lt;/a&gt;, but nothing, still a decent response time. &lt;/p&gt;

&lt;p&gt;We also dug into the code to do some checks, but it only confirmed what was already obvious to us. Someone already &lt;strong&gt;did a great job to optimize this website&lt;/strong&gt;. We weren't asked just to do the basics, they know how to do that themselves. We were asked because none of the popular tweaks worked. Great, finally some challenge! &lt;/p&gt;

&lt;h2&gt;
  
  
  Profiling the website performance
&lt;/h2&gt;

&lt;p&gt;It was a time to dive deeper and do a step by step analysis. First of all, we had to figure out &lt;strong&gt;which site element was taken to calculate the LCP&lt;/strong&gt;. Performance tool built-in Google Chrome shows a precise timeline of all the elements requested, loaded, and painted. It also &lt;strong&gt;indicates moments when LCP and other metrics are measured&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9k9q8mu2a2ni2d23k51s.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9k9q8mu2a2ni2d23k51s.jpeg" alt="Chrome Developer tools - performance timeline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Analysis of this timeline confirmed our assumptions - the Largest Contentful Paint element was &lt;strong&gt;one of the product images on the homepage&lt;/strong&gt;. Why is this image displayed so late to the user? We checked the image format and size, and all looked good. Actually, the &lt;strong&gt;image itself was loaded in just 122 milliseconds&lt;/strong&gt;. So why did this image appear after almost 6 seconds if it loads in just a fraction of that?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs4zi3r79p1m3uxywsvo5.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs4zi3r79p1m3uxywsvo5.jpeg" alt="Chrome Developer tools - network - timing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We analyzed the timing of that particular image, and what surprised us was that &lt;strong&gt;it started to load after 5.7s since the website was requested&lt;/strong&gt;. But why? Server response time was decent and all images should begin to load as soon as the browser receives the HTML from the server, right? Well…&lt;/p&gt;

&lt;h2&gt;
  
  
  Lazy loading done wrong
&lt;/h2&gt;

&lt;p&gt;The title of this article already revealed the diagnosis - yes, &lt;strong&gt;it was a lazy loader that delayed fetching that image&lt;/strong&gt;. But why? Isn’t the lazy loader one of the most common mechanisms that &lt;strong&gt;improve the website speed&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxghlnkuxx0ndgmx1idve.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxghlnkuxx0ndgmx1idve.jpeg" alt="Lazy loader in action"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lazy loader prevents images from being fetched if they are not in the current viewport. This &lt;strong&gt;minimizes the amount of data transferred&lt;/strong&gt; in situations when only part of the site is seen by the user (eg. user views only the top of the page). Usually, it is good and makes the website load visibly faster. Usually...&lt;/p&gt;

&lt;p&gt;Unfortunately, it also &lt;strong&gt;has a side effect&lt;/strong&gt; that not every web developer is aware of. The lazy loader is simply a javascript code, that checks the image position and calculates whether it is in the current viewport. But this means that before the image is fetched, the web browser has to fetch the Javascript code and run it. The &lt;strong&gt;user won’t see the image until Javascript decides&lt;/strong&gt; that he should see it. And that causes a delay.  &lt;/p&gt;

&lt;p&gt;And of course, there are many ways to implement the lazy loader so that delay is minimized. But in our case, the code responsible for lazy loading used an external, third-party javascript library that had to be fetched first. And what is more, this code was implemented in an &lt;em&gt;onload&lt;/em&gt; event handler, which means that it was &lt;strong&gt;executed only after all website elements finished loading&lt;/strong&gt; - including styles, fonts, scripts etc. And that all took 5.7s. It's quite a long time to decide if an image should be downloaded or not, isn't it?&lt;/p&gt;

&lt;h2&gt;
  
  
  Results of website optimization
&lt;/h2&gt;

&lt;p&gt;There are many ways to solve this problem. We could get rid of the dependency on the external library or execute the lazy loader earlier than in &lt;em&gt;onload&lt;/em&gt;. But the quick fix was to simply &lt;strong&gt;switch off the lazy loader for images that are always seen above the fold&lt;/strong&gt; (the portion of the webpage that is visible without scrolling). This simple solution took just one line of code and did a job. Just see how the website speed improved:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fauz1b9r6uq0jb9m03h78.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fauz1b9r6uq0jb9m03h78.jpeg" alt="Final results of site speed optimization"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It took us a few hours to find the cause of the poor LCP and finally add this single line of code. Website optimization is usually not about coding, it is analytical work of searching, digging, and investigating. Of course, this wasn’t the end of the optimization, still room for improvement. But we were surprised &lt;strong&gt;how much Core Web Vitals score improved with a simple fix in a lazy loader&lt;/strong&gt;. This made me curious so I also checked other popular eCommerce websites and I was surprised. Many of them did have the same issue! Seems that lazy loader is seen as a great, out of the box solution for optimization, but &lt;strong&gt;very often added carelessly&lt;/strong&gt;. And adding it too quickly (too lazy?) may actually harm the overall site speed. &lt;strong&gt;So be careful and don’t add stuff to your site just because everyone does it!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ycwy143jw7dmz8tbsxo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ycwy143jw7dmz8tbsxo.jpg" alt="Email after successful website optimization"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>seo</category>
      <category>ux</category>
      <category>webdev</category>
      <category>webperf</category>
    </item>
    <item>
      <title>Legacy code - strangle or tame?</title>
      <dc:creator>klobermajer</dc:creator>
      <pubDate>Mon, 18 Apr 2022 07:51:11 +0000</pubDate>
      <link>https://dev.to/accesto/legacy-code-strangle-or-tame-691</link>
      <guid>https://dev.to/accesto/legacy-code-strangle-or-tame-691</guid>
      <description>&lt;p&gt;For me, as probably for every programmer, &lt;strong&gt;legacy code is something that I do not want to deal with&lt;/strong&gt;. It is always not well written, hard to read, and very complicated. &lt;/p&gt;

&lt;p&gt;Unfortunately, the world is not a perfect place and we must do it on a daily basis. The first thing we want to do when we are starting to work with a new legacy project is to &lt;strong&gt;rewrite everything from scratch&lt;/strong&gt;. Such an approach in most cases will not work too well, it will take much time to deliver, it will stop introducing new features for a long time and do not bring any value to customers during this period.&lt;/p&gt;

&lt;p&gt;A better approach is to rewrite step by step, one module at a time. Let legacy live with a new codebase, arm in arm. It can be achieved by using a &lt;a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/strangler" rel="nofollow"&gt;strangler pattern&lt;/a&gt;. Unfortunately, such an approach is also not always possible. Sooner or later you will face the situations when the &lt;strong&gt;requested feature will touch almost every part of the system&lt;/strong&gt; or there will be no part left that can be rewritten at once in a reasonable time.&lt;/p&gt;

&lt;h2&gt;
  
  
  From strangling to taming
&lt;/h2&gt;

&lt;p&gt;Let me tell you a story about one of our projects which we started two years ago. It is a task management software with a lot of features and integrations with external systems. This software was developed a few years before we took it. It was used by many customers back then and nowadays it is getting more popular from month to month. Our main goal at the beginning was to &lt;strong&gt;make it faster, more robust, and more scalable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We started as always by collecting as much &lt;strong&gt;knowledge about the domain&lt;/strong&gt; as we can, then extracted some modules as refactoring candidates and get into coding. Refactoring a few modules took us almost a year. Almost a year with no big visible changes for the users.&lt;/p&gt;

&lt;p&gt;The big disadvantage of &lt;strong&gt;refactoring legacy code&lt;/strong&gt; is that it has no visible value for customers, even if it is a huge improvement for us, developers. Our hard work at this stage will pay off in the future for sure, but we should also give some &lt;strong&gt;value to users&lt;/strong&gt; during the whole process, not only at the end.&lt;/p&gt;

&lt;p&gt;This is why it is so important to properly design new codebase architecture and use possibilities it gives to &lt;strong&gt;parallelly refactor and introduce new features&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qRFGqt9L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8294ie17gfzcuekjlmqt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qRFGqt9L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8294ie17gfzcuekjlmqt.png" alt="Image description" width="547" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  New architecture
&lt;/h2&gt;

&lt;p&gt;Architecture should be always adjusted to project requirements, each project has specific characteristics and requirements. In our case, because of external systems integrations, and more integrations on the horizon, events were a very important part that we wanted to introduce. Based on that we decided to go with CQRS.&lt;/p&gt;

&lt;h4&gt;
  
  
  CQRS - Command Query Responsibility Segregation
&lt;/h4&gt;

&lt;p&gt;Probably you heard about this more than once. It is known for some time and a commonly used approach. It allows us to separate responsibilities, to separate domain, and make code clear and easily extensible. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Base principles:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;each change must be done by Command&lt;/li&gt;
&lt;li&gt;each Command has one Handler&lt;/li&gt;
&lt;li&gt;each change can dispatch Event&lt;/li&gt;
&lt;li&gt;each Event can have multiple Handlers&lt;/li&gt;
&lt;li&gt;fetching data from the database is performed by Query&lt;/li&gt;
&lt;li&gt;each Query has one Handler&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Taming
&lt;/h2&gt;

&lt;p&gt;At some point in time, we decided to &lt;strong&gt;reduce refactoring activities and focus more on introducing new features&lt;/strong&gt;, to finally make something visible to the customers. And so we did. We introduced a couple of new features in already refactored parts. Unfortunately, quite soon, we hit a wall. It was no longer possible to &lt;strong&gt;add something without touching legacy code&lt;/strong&gt;, and such refactoring will always require too much time and it will probably end up with refactoring the whole system. Too many parts are connected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use events
&lt;/h3&gt;

&lt;p&gt;Is the refactoring always really needed? Maybe we can &lt;strong&gt;add new features without refactoring?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the features that we wanted to implement was the notifications module. It is obvious that notifications are connected with many modules. And it is also obvious that from a business perspective it is &lt;strong&gt;not acceptable to refactor all these modules just because of notifications&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Luckily it is not required, we can &lt;strong&gt;prepare domain events for legacy code&lt;/strong&gt; and dispatch such events when needed. The actual job will be done by a new code in event handlers.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example
&lt;/h4&gt;

&lt;p&gt;In this example, I will show you how we can notify the user when he was assigned to a task in our system.&lt;/p&gt;

&lt;p&gt;Let’s start with the code currently responsible for assigning users to the task.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Legacy\Controller&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TaskController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;assignUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'users'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       &lt;span class="nv"&gt;$taskHelper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'taskhelper'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

       &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;taskExists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'NOK'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       &lt;span class="p"&gt;}&lt;/span&gt;

       &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="nv"&gt;$usersIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$users&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

           &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$usersIds&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
               &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;userIsAssignedToTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                   &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
               &lt;span class="p"&gt;}&lt;/span&gt;

               &lt;span class="nv"&gt;$taskHelper&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assignUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
           &lt;span class="p"&gt;}&lt;/span&gt;
       &lt;span class="p"&gt;}&lt;/span&gt;

       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Legacy\Service&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TaskHelper&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$repository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


    &lt;span class="c1"&gt;// … &lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;assignUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assignUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is just an example code, but you can get the idea of what kind of legacy we are dealing with. Of course, we can inject some service from the new codebase to TaskHelper and use it to create a notification. Such a solution will work but will be less extensible. The solution which I recommend here is to &lt;strong&gt;dispatch a domain event and let the handler do the job&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Task\Domain\Event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserWasAssignedToTask&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;taskId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// … &lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Notification\Infrastructure\Event\Handler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Task\Domain\Event\UserWasAssignedToTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotifyUserAboutTaskAssignment&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;UserWasAssignedToTask&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// here you can do everything you want&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have a nice event and handler in the new codebase, let use it in legacy to tie everything up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Legacy\Service&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Task\Domain\Event\UserWasAssignedToTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TaskHelper&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$eventBus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$repository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;assignUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assignUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;eventBus&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UserWasAssignedToTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when we have it all in place, each time when a user is assigned to a task, a new event will be dispatched and handled. We can easily extend the functionality of our handler, &lt;strong&gt;we can also add more handlers&lt;/strong&gt;, e.g. log this in some event log, or propagate this change to some external system.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if you want to reuse legacy code?
&lt;/h3&gt;

&lt;p&gt;Another new feature we wanted to implement were automated actions. The user can set some rules which will perform actions based on some condition. For example, when a task is finished it should be assigned to the proper user. Not too complicated, looks quite easy to implement. &lt;/p&gt;

&lt;p&gt;But what if all actions which should be performed are in legacy (in a non-reusable form)? E.g. Method in Symfony controller handles the whole logic.&lt;br&gt;
Should we refactor all those places? How much time will it take?&lt;/p&gt;
&lt;h3&gt;
  
  
  Use command
&lt;/h3&gt;

&lt;p&gt;What we can do is to &lt;strong&gt;add commands and handlers for all actions we need&lt;/strong&gt;. Commands will contain all data needed and the handler will wrap up legacy code. We will not refactor anything in this process, just take it as it is and put in a new shiny box called “command handler”. Of course, this will not pay off the &lt;a href="https://accesto.com/blog/technical-debt-the-silent-villain-of-web-development/"&gt;technical debt&lt;/a&gt; we had in legacy but allows to introduce new features easier and faster. And what is most important, will allow us to &lt;strong&gt;refactor smaller parts in the future&lt;/strong&gt;. One command handler is for sure easier to refactor than the whole legacy controller/service/module.&lt;/p&gt;

&lt;p&gt;Thanks to that we can easily use this code in a new codebase, not only it will be reusable but also it will be in line with the new approach. &lt;/p&gt;

&lt;p&gt;And this was what we did, code we needed was extracted to handlers, and replaced everywhere in legacy by dispatching command.&lt;/p&gt;
&lt;h4&gt;
  
  
  Example
&lt;/h4&gt;

&lt;p&gt;I will use code from the previous example. We will refactor TaskController to a new approach. Of course, this example will be shortened to just show the concept. In our project, there is much more logic behind this feature.&lt;/p&gt;

&lt;p&gt;Let first prepare the command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Task\Domain\Command&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AssignUserToTask&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;taskId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// …&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and handler&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Task\Application\CommandHandler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Task\Domain\Command\AssignUserToTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Task\Domain\Exception\TaskNotExistException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AssignUserToTaskHandler&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$taskHelper&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;AssingUserToTask&lt;/span&gt; &lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$taskId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTaskId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getUserId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;taskExists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TaskNotExistException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;userIsAssignedToTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;taskHelper&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assignUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
        &lt;span class="c1"&gt;// now we can also move event dispatching from TaskHelper to&lt;/span&gt;
        &lt;span class="c1"&gt;// this command handler &lt;/span&gt;
            &lt;span class="c1"&gt;// (but I will leave this at it is for now)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// … &lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Probably you noticed that I moved some parts of TaskController to the handler also, it is always a good idea to make such small improvements &lt;a href="https://accesto.com/blog/Boy-scout-rule-in-6-examples-the-basic-principle-of-web-development/"&gt;boy scout rule&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now we can adjust TaskController.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Legacy\Controller&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Task\Domain\Command\AssignUserToTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TaskController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;assignUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'users'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       &lt;span class="nv"&gt;$commandBus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'command_bus'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

       &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="nv"&gt;$usersIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$users&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

           &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$usersIds&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nv"&gt;$commandBus&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AssignUserToTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
           &lt;span class="p"&gt;}&lt;/span&gt;
       &lt;span class="p"&gt;}&lt;/span&gt;

       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks quite easy, right? Probably you are wondering why we did this, not much changed in the code, the handler is still the same legacy code but in a different place. TaskController looks almost the same.&lt;/p&gt;

&lt;p&gt;Let me remind why we did this. We did this to introduce automated actions. &lt;br&gt;
Thanks to the command/handler approach we can easily do this.&lt;/p&gt;

&lt;p&gt;Our example automated action should work as follow: When the task is finished then assign the proper user to this task.&lt;/p&gt;

&lt;p&gt;Let first add TaskFinished event&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; 

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Task\Domain\Event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TaskFinished&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can add a handler for our automated action.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Automations\Infrastructure\Event\Handler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Task\Domain\Command\AssignUserToTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Task\Domain\Event\TaskFinished&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AssignUserOnTaskFinished&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$commandBus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;TaskFinished&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$taskId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTaskId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isActionEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getUserIdForAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;commandBus&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AssignUserToTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// … &lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is of course some kind of compromise, &lt;strong&gt;we will not get rid of legacy, it will still be there, but tamed&lt;/strong&gt;. Now you can easily use legacy code even without knowing that in fact legacy code will be executed. Everything will look nice and as good as your new codebase. &lt;/p&gt;

&lt;p&gt;You can later (when pressure for new features will be lower)  take such AssignUserToTaskHanlder and refactor it properly to get rid of the legacy TaskHelper service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with legacy?
&lt;/h2&gt;

&lt;p&gt;And what is your approach to legacy? Strangling, taming, rewriting? To learn more on that topic, make sure to check our CTO's blogpost on &lt;a href="https://accesto.com/blog/handle-technical-debt-in-legacy-application-4-possible-scenarios/"&gt;possible scenarios of handling technical debt in legacy applications&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>php</category>
      <category>codequality</category>
      <category>programming</category>
      <category>symfony</category>
    </item>
    <item>
      <title>Simple Docker setup for Symfony project</title>
      <dc:creator>Michał Romańczuk</dc:creator>
      <pubDate>Fri, 15 Apr 2022 13:43:47 +0000</pubDate>
      <link>https://dev.to/accesto/simple-docker-setup-for-symfony-project-1p9e</link>
      <guid>https://dev.to/accesto/simple-docker-setup-for-symfony-project-1p9e</guid>
      <description>&lt;p&gt;It is no longer the case at Accesto that we work on projects without Docker. Of course, projects are not born dockerized, someone has to do it. Sometimes I do. I am a PHP developer and I work the most with &lt;a href="https://accesto.com/blog/what-is-php-framework-symfony-explained-for-executives/"&gt;Symfony framework&lt;/a&gt; on a daily basis. In this article, I will present an example development &lt;strong&gt;Docker configuration for the Symfony&lt;/strong&gt; project.&lt;/p&gt;

&lt;h2&gt;
  
  
  So let's use a Docker in a Symfony project.
&lt;/h2&gt;

&lt;p&gt;When it comes to Docker setup, &lt;strong&gt;there is no one-size-fits-all solution&lt;/strong&gt;. It always depends on particular project needs. But some good practices should be followed, for example, those described in the official &lt;a rel="nofollow" href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/"&gt;Docker documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's imagine we take over an existing project, a simple one, &lt;strong&gt;PHP, Symfony, MySQL database&lt;/strong&gt;, that's all. But there is no Docker. So instead of installing a local web server, the appropriate PHP version, the appropriate database engine, etc., the first thing to do is ... dockerize!&lt;/p&gt;

&lt;p&gt;Why should I spend time on dockerizing first? Because I will do it once, and everyone working on this project will benefit from it. You can read more about why it is worth using the Docker in &lt;a href="https://accesto.com/blog/what-is-docker-and-why-to-use-it/"&gt;the article&lt;/a&gt; of our CEO Piotr. &lt;/p&gt;

&lt;p&gt;I assume you already have &lt;a rel="nofollow" href="https://docs.docker.com/engine/install/"&gt;Docker Engine&lt;/a&gt; and &lt;a rel="nofollow" href="https://docs.docker.com/compose/install/"&gt;Docker Compose&lt;/a&gt; installed, if not, you should do so.&lt;/p&gt;

&lt;p&gt;Now let's focus on what we need. This project is currently running on Apache, PHP 7.4 and MySQL 5.7.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Docker using docker-compose.yml
&lt;/h2&gt;

&lt;p&gt;I have added the &lt;em&gt;docker-compose.yml&lt;/em&gt; file in the main project directory, and I defined our first container in it - the one with the database. File &lt;em&gt;docker-compose.yml&lt;/em&gt; is used by (Docker) Compose to manage and run Docker applications.&lt;/p&gt;

&lt;p&gt;At the beginning of the file, you can (from version v1.27.0 it is optional) define the version of the configuration. Different versions of Docker Engine + Docker Compose work with different versions of the configuration, which you can &lt;a rel="nofollow" href="https://docs.docker.com/compose/compose-file/"&gt;check here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  MySql database in Docker container
&lt;/h2&gt;

&lt;p&gt;Let's start by defining the first service, the database. First, the name, I chose “db”:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that I had to define what &lt;em&gt;db&lt;/em&gt; service is. For example, we can use the &lt;em&gt;image&lt;/em&gt; option. As an &lt;em&gt;image&lt;/em&gt;, I had to select the appropriate Docker image. &lt;strong&gt;We can use ready-made images&lt;/strong&gt; available on the web for this. Most of the tools come with Docker images. And to search for these images, we can use the &lt;a rel="nofollow" href="https://hub.docker.com/"&gt;search engine&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In my case, the project uses MySql version 5.7 and such an image is also available so I could just use its name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql:5.7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As per the &lt;a rel="nofollow" href="https://hub.docker.com/_/mysql"&gt;documentation&lt;/a&gt; for this image, some things in this container can be &lt;strong&gt;configured using environment variables&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One variable is mandatory, this is &lt;code&gt;MYSQL_ROOT_PASSWORD&lt;/code&gt;. As you might guess, this is the MySql root superuser password. But there are a few other variables that are helpful, I used the following: &lt;code&gt;MYSQL_DATABASE&lt;/code&gt;, a database with this name will be created during image startup. If we define &lt;code&gt;MYSQL_USER&lt;/code&gt;, &lt;code&gt;MYSQL_PASSWORD&lt;/code&gt;, a user with this name and password will be created, and he will have granted superuser access to the database defined in &lt;code&gt;MYSQL_DATABASE&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql:5.7&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MYSQL_ROOT_PASSWORD=somerootpass&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MYSQL_PASSWORD=somepass&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MYSQL_DATABASE=dockerizeme_db&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MYSQL_USER=someuser&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Symfony Docker image
&lt;/h2&gt;

&lt;p&gt;But &lt;strong&gt;there are no ready-made images for everything&lt;/strong&gt;. For example, there is no ready image for a web server configured according to the needs of our application.&lt;/p&gt;

&lt;p&gt;So while defining the container for our web server with PHP, I did not use the &lt;em&gt;image&lt;/em&gt; option with the name of the ready image, but I used the &lt;em&gt;build&lt;/em&gt; option with directory (.) that contains the file (&lt;em&gt;Dockerfile&lt;/em&gt;) from which the image will be built. But about &lt;em&gt;Dockerfile&lt;/em&gt; soon.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# cut for readability, see above&lt;/span&gt;
    &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Going further. It would be nice to add a basic Apache configuration. I added the &lt;em&gt;docker&lt;/em&gt; directory in the main project directory, where I will keep files related to the Docker environment configuration. Then, in that directory, I added the &lt;em&gt;apache.conf&lt;/em&gt; file. This file is a very basic configuration of a web server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;VirtualHost *:80&amp;gt;
    DocumentRoot /var/www/public

    &amp;lt;Directory /var/www/public&amp;gt;
        AllowOverride None
        Order Allow,Deny
        Allow from All

        &amp;lt;IfModule mod_rewrite.c&amp;gt;
            RewriteEngine On
            RewriteCond %{REQUEST_FILENAME} !-f
            RewriteRule ^(.*)$ index.php [QSA,L]
        &amp;lt;/IfModule&amp;gt;
    &amp;lt;/Directory&amp;gt;
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's make our container accessible "from the outside". I used the &lt;em&gt;ports&lt;/em&gt; option for this. Port 80 from the inside of the container will be exposed to the outside at port 8080.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# cut for readability, see above&lt;/span&gt;
    &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# cut for readability, see above&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8080:80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why didn't I put it on port 80 but 8080? Because we often have Apache running locally on port 80, so we would have a conflict and the Docker container wouldn't start.&lt;/p&gt;

&lt;p&gt;Finally docker-compose.yml looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql:5.7&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MYSQL_ROOT_PASSWORD=somerootpass&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MYSQL_PASSWORD=somepass&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MYSQL_DATABASE=dockerizeme_db&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MYSQL_USER=someuser&lt;/span&gt;
    &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8080:80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;em&gt;Dockerfile&lt;/em&gt; for Apache and PHP part
&lt;/h2&gt;

&lt;p&gt;Now we need to take a step back. Let's go into the details of our web image. To define it, I added a &lt;em&gt;Dockerfile&lt;/em&gt; file to the main project directory. Of course, we will use the ready-made public image as a starting point, and slightly adjust it to our needs. I used the &lt;em&gt;php:7.4-apache&lt;/em&gt; image as a base.&lt;/p&gt;

&lt;p&gt;At the beginning of &lt;em&gt;Dockerfile&lt;/em&gt;, I defined the Docker image which is our base:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; php:7.4-apache&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I enabled Apache &lt;code&gt;mod_rewrite&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;a2enmod rewrite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that I did the classic update, several packages installed such as &lt;code&gt;libzip-dev&lt;/code&gt; (needed for zip which is needed for composer) &lt;code&gt;git wget&lt;/code&gt; via apt-get install. And some extensions such as &lt;code&gt;pdo mysqli pdo_mysql zip&lt;/code&gt; via docker-php-ext-install mechanism, more about that you can find in base image &lt;a rel="nofollow" href="https://hub.docker.com/_/php"&gt;documentation&lt;/a&gt;. In the meantime, I also deleted files that are unnecessary in the container, they would only take up space.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; libzip-dev git wget &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get clean &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt; /tmp/&lt;span class="k"&gt;*&lt;/span&gt; /var/tmp/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;docker-php-ext-install pdo mysqli pdo_mysql zip&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application uses &lt;a rel="nofollow" href="https://getcomposer.org/"&gt;Composer&lt;/a&gt; to manage dependencies in our application, so I installed in the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;wget https://getcomposer.org/download/2.0.9/composer.phar &lt;span class="se"&gt;\ &lt;/span&gt;
    &amp;amp;&amp;amp; mv composer.phar /usr/bin/composer &amp;amp;&amp;amp; chmod +x /usr/bin/composer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I copied the Apache configuration file to the appropriate place in the container, and our project files to &lt;em&gt;/var/www&lt;/em&gt; in the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; docker/apache.conf /etc/apache2/sites-enabled/000-default.conf&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /var/www&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instruction WORKDIR sets the working directory for other instructions, for example for the RUN command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /var/www&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And last command to run Apache in the container foreground:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["apache2-foreground"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To sum up, we have such &lt;em&gt;Dockerfile&lt;/em&gt; for web container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; php:7.4-apache&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;a2enmod rewrite

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; libzip-dev git wget &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get clean &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt; /tmp/&lt;span class="k"&gt;*&lt;/span&gt; /var/tmp/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;docker-php-ext-install pdo mysqli pdo_mysql zip&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;wget https://getcomposer.org/download/2.0.9/composer.phar &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mv &lt;/span&gt;composer.phar /usr/bin/composer &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chmod&lt;/span&gt; +x /usr/bin/composer

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; docker/apache.conf /etc/apache2/sites-enabled/000-default.conf&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /var/www&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /var/www&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["apache2-foreground"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Is that all? &lt;strong&gt;As for the minimum, yes!&lt;/strong&gt; Now if I run the &lt;code&gt;docker-compose up -d&lt;/code&gt; in project directory in the console, the containers will be created and the whole project will start running.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YNFb6l2C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4fp88dp721pbzuk5fbzp.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YNFb6l2C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4fp88dp721pbzuk5fbzp.gif" alt="Run Symfony on docker" width="571" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But wait a minute. Let's see if it really works:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bN4UJCtf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zayda6x9xjju6kv0l612.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bN4UJCtf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zayda6x9xjju6kv0l612.png" alt="Run Symfony on docker 2" width="880" height="183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course not ;). Our project needs a few more steps. For example, what about &lt;code&gt;composer install&lt;/code&gt;? Database migrations? Now we can describe these steps in the Readme of the project. It would be something like “enter the container (&lt;em&gt;docker-compose exec web bash&lt;/em&gt;) and run composer install in the container, then run migrations (&lt;em&gt;bin/console doc:mig:mig&lt;/em&gt;)”.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improvements in startup a dockerized project
&lt;/h2&gt;

&lt;p&gt;We can try to "automate" these steps, for example by using Entrypoint. Using Entrypoint is not the best solution, but it will &lt;strong&gt;facilitate development&lt;/strong&gt; at this stage. Let's assume that I want to run every time the container is started:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;composer install&lt;/li&gt;
&lt;li&gt;new database migrations to be loaded
&lt;/li&gt;
&lt;li&gt;fresh fixtures (dummy/test data) in the database.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I added &lt;em&gt;entrypoint.sh&lt;/em&gt; in &lt;em&gt;docker&lt;/em&gt; directory with a simple script to do it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

composer &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt;
bin/console doc:mig:mig &lt;span class="nt"&gt;--no-interaction&lt;/span&gt;
bin/console doc:fix:load &lt;span class="nt"&gt;--no-interaction&lt;/span&gt;

&lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I had to slightly modify our &lt;em&gt;Dockerfile&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; php:7.4-apache&lt;/span&gt;

&lt;span class="c"&gt;# … cut for readability&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; docker/apache.conf /etc/apache2/sites-enabled/000-default.conf&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; docker/entrypoint.sh /entrypoint.sh&lt;/span&gt;

&lt;span class="c"&gt;# … cut for readability&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /entrypoint.sh

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["apache2-foreground"]&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/entrypoint.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I COPY the new script file to the container and add executable permissions to that file. At the end of the &lt;em&gt;Dockerfile&lt;/em&gt;, I defined that the file is Entrypoint&lt;/p&gt;

&lt;p&gt;Now I had to rebuild the Docker image to apply these changes &lt;code&gt;docker-compose build web&lt;/code&gt;, and restart the containers. Let's check our application:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JmdDEclu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ylquzpo6tidq4y72v7f3.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JmdDEclu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ylquzpo6tidq4y72v7f3.gif" alt="app" width="803" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It works! Now, anyone who downloads this project and has Docker &lt;strong&gt;can simply run it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Can you also work with Docker on a Mac? You can read about it in the latest post by Krzysiek &lt;a href="https://accesto.com/blog/docker-on-mac-how-to-speed-it-up/"&gt;here&lt;/a&gt;  :)&lt;/p&gt;

&lt;p&gt;Docker is a powerful tool, I only showed you a minimal snippet that will allow you to start your project in a box. You can find more practical tips and &lt;strong&gt;advanced Docker optimization techniques&lt;/strong&gt; in our &lt;strong&gt;free e-book&lt;/strong&gt; &lt;a href="https://accesto.com/books/docker-deep-dive/"&gt;Docker Deep Dive&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://accesto.com/books/docker-deep-dive/"&gt;&lt;br&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3q2MLbUH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4rlvwyhxbekczgalcmvi.png" alt="Docker Deep Dive Book" width="880" height="446"&gt;&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>symfony</category>
      <category>php</category>
    </item>
  </channel>
</rss>
