<?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: Tim</title>
    <description>The latest articles on DEV Community by Tim (@t1m4lc).</description>
    <link>https://dev.to/t1m4lc</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F152982%2Fa39bbb62-0d86-434f-9b12-6041caa7dd6e.jpeg</url>
      <title>DEV Community: Tim</title>
      <link>https://dev.to/t1m4lc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/t1m4lc"/>
    <language>en</language>
    <item>
      <title>How I built a multi-country price comparator as a solo dev: static-first, Workers, D1 and a lot of feed matching</title>
      <dc:creator>Tim</dc:creator>
      <pubDate>Wed, 11 Nov 2026 13:31:46 +0000</pubDate>
      <link>https://dev.to/t1m4lc/how-i-built-a-multi-country-price-comparator-as-a-solo-dev-static-first-workers-d1-and-a-lot-of-1ff8</link>
      <guid>https://dev.to/t1m4lc/how-i-built-a-multi-country-price-comparator-as-a-solo-dev-static-first-workers-d1-and-a-lot-of-1ff8</guid>
      <description>&lt;p&gt;A price comparator looks like a simple product: show a product, show a list of prices, redirect to a shop. I built one alone, and the simple part turned out to be about ten percent of the work. This post walks through the architecture of &lt;a href="https://runometry.com/en" rel="noopener noreferrer"&gt;Runometry&lt;/a&gt;, a comparator for running shoes, bikes and sports electronics, and what I would tell myself before starting.&lt;/p&gt;

&lt;p&gt;The numbers, so you know the scale: more than 600 000 prices, more than 20 shops, 15 delivery countries, a site in 10 languages and more than 2 500 products in the catalog. It is small by marketplace standards, but big enough that every shortcut shows up as a bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea: static-first
&lt;/h2&gt;

&lt;p&gt;Most comparator pages do not change every minute. A shoe's spec sheet changes when a human edits it. Prices change hourly at best. So I split the problem in two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Slow data&lt;/strong&gt; (models, brands, specs, texts, taxonomy) lives in the repository as JSON and is compiled into pages at build time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast data&lt;/strong&gt; (prices, availability) lives in a database and is served through a small API that the page reads after it loads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The front end is AnalogJS, which is Angular with Vite, file-based routing and SSR plus prerendering. Pages are prerendered to HTML, so a crawler or a slow phone gets real content immediately, and Angular hydrates on top. Anything that needs the browser (a price list that depends on the visitor's country, a size filter) runs after the first render, and the template for it is deferred.&lt;/p&gt;

&lt;p&gt;The rule I enforce in code review, even though I am the only reviewer: nothing that touches the browser runs during server render, and nothing blocks a crawler. It sounds obvious. It is the rule I broke most often early on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The platform: Workers, D1, R2, KV
&lt;/h2&gt;

&lt;p&gt;Everything runs on Cloudflare, and I picked it mostly for the boring reasons: one bill, one deploy tool, and edge latency for free.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Workers&lt;/strong&gt; serve the redirect that sends a visitor to a shop, the prices API, scheduled jobs and a small admin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;D1&lt;/strong&gt; (SQLite) stores prices and offers. The schema is plain tables with indexes shaped by the queries the pages actually run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;R2&lt;/strong&gt; stores images and pre-rendered pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;KV&lt;/strong&gt; holds small, read-heavy things that are annoying to query: configuration snapshots and caches.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;D1 taught me the first real lesson. SQLite at the edge is fast for reads and unforgiving about heavy writes. Early on, an ingest run that wrote rows one by one overloaded the database and the API started returning errors for visitors. The fix was boring: batch the writes, skip rows that have not changed, and keep a clear separation between the tables that visitors read and the tables the ingest writes while it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hourly ingest Worker
&lt;/h2&gt;

&lt;p&gt;Prices come from retailer feeds, which are files published through affiliate networks. A scheduled Worker runs every hour. Per feed, it does roughly 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="c1"&gt;// Illustrative pipeline, not the real code&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchFeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&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="n"&gt;offers&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="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;rows&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="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;matchToCatalog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// model, colourway, size&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="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unmatched&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&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="n"&gt;offers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;toOffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row&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;// price, stock, link&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;upsertChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;              &lt;span class="c1"&gt;// only rows that differ&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;retireMissing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// gone from the feed = gone from the site&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three details mattered more than the code shape:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dry runs are the default.&lt;/strong&gt; Running ingest on my machine never writes to production. Writing happens only at release, through the pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unmatched rows are a first-class output.&lt;/strong&gt; A report of what did not match is more useful than a match rate. It tells me which model is missing from the catalog, or which mapping is wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retire what disappears.&lt;/strong&gt; A comparator that keeps showing a price the shop removed yesterday is worse than one with fewer prices.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The hard part: matching feed rows to catalog models
&lt;/h2&gt;

&lt;p&gt;This is where the time went. A retailer feed row is a product title, a price, a stock flag, sometimes a size, sometimes an EAN, and a free-form description. My catalog has a canonical model, with colourways, and sizes beneath those.&lt;/p&gt;

&lt;p&gt;The matching has to answer three questions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which model is this?&lt;/strong&gt; Titles lie. The same shoe appears as "Brand Model 14", "Brand Model v14 Men's" and "Model 14 Running Shoe Black". Brands reuse names across versions, so "Model 14" and "Model 15" must never merge, while "Model" in a title alone matches too many things. I ended up with a layered approach: exact identifier (EAN) first when the feed has one, then a normalised brand plus model plus version match, then fuzzy rules that only ever produce a candidate for review, never an automatic match.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which colourway?&lt;/strong&gt; Feeds describe colour in marketing names, in codes, or not at all. I treat colour as a filter on a model page, not as a page of its own. That decision removed a whole class of duplicate-content problems and kept the number of URLs sane.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which size?&lt;/strong&gt; Sizes are the quiet killer. A pair of shoes is priced per size, and shops list sizes in EU, UK, US or centimetres, with half sizes written four different ways. If you compare the cheapest price without knowing the size is in stock, you are showing a price nobody can use. So the offer is per size, and the visitor filters by their size. Bikes added another version of the same problem: frame sizes are letters in one feed and numbers in another.&lt;/p&gt;

&lt;p&gt;EAN looks like the clean answer, and it helps a lot, but it is not a silver bullet. Some feeds omit it, some share one EAN across sizes, and a few reuse them across colourways. I treat it as strong evidence, not as truth, and check it against the model it points to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Offers follow the market, not the language
&lt;/h2&gt;

&lt;p&gt;This is the design decision I am happiest about, and the one that is easiest to get wrong.&lt;/p&gt;

&lt;p&gt;A site in German is not a site for Germany. A German-speaking visitor can be in Austria or Switzerland. A French site is read in Belgium, in Switzerland, and in overseas territories that belong to France. If a language decides which shops you see, someone in Zurich reading German gets prices from a shop that does not ship to Switzerland.&lt;/p&gt;

&lt;p&gt;So I model two separate things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Locale&lt;/strong&gt;: the language of the interface.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market&lt;/strong&gt;: the country the goods are delivered to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A locale lists the countries it can show, with a default. A page shows the offers delivered to the visitor's market when it is known and allowed by the locale, and falls back to the locale's default country otherwise. The language never filters offers. I also made one rule explicit and tested: a locale never silently falls back to another country's offers, because showing the wrong country's prices is worse than showing nothing.&lt;/p&gt;

&lt;p&gt;The outbound link follows the language, though. If a shop has a site in the visitor's language, the click lands there; otherwise it lands on its storefront for that market.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delivered price
&lt;/h2&gt;

&lt;p&gt;A cheap shoe with expensive shipping is not cheap. Delivery rules live in data (per shop, per market: thresholds, flat fees, free delivery above a basket value), and the site can show a price with delivery included so you compare what you actually pay. By default prices are shown without delivery, and the delivered price is a toggle. Both are computed from the same data, so they never disagree.&lt;/p&gt;

&lt;h2&gt;
  
  
  The deal rule: a median, not a struck-through price
&lt;/h2&gt;

&lt;p&gt;Every comparator is tempted by the same shortcut: take the shop's crossed-out "was" price and show a big discount. Those prices are often meaningless. A reference price can be set high and never actually charged.&lt;/p&gt;

&lt;p&gt;I use a rule that depends only on my own observations. A price counts as a deal when it is at least 25 percent below the median of the daily lowest prices observed over the last 90 days. Two guards go with it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A discount above 70 percent is discarded. It is almost always a data error or a clearance of something that is not really what it claims to be.&lt;/li&gt;
&lt;li&gt;The shop's struck-through price is never used, in any form.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Illustrative&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;median&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;medianOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dailyLowest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;90&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;isDeal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;median&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;median&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The effect is that a "deal" on Runometry means cheaper than it has recently been across the market, not cheaper than a number a shop chose. It also means a brand-new model has no deal status until there is enough history, which is the correct behaviour.&lt;/p&gt;

&lt;h2&gt;
  
  
  A zod-validated catalog in git
&lt;/h2&gt;

&lt;p&gt;All the slow data is JSON in the repository: shoes, bikes, electronics, brands, shops, delivery rules, translations, editorial text. Every file is validated with zod at build time, and a failing schema fails the build.&lt;/p&gt;

&lt;p&gt;Why git instead of a CMS or a database?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Review.&lt;/strong&gt; A spec change is a diff. I can see exactly what changed and why.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provenance.&lt;/strong&gt; Each sourced value carries a status (verified, reviewed, disputed) and a date. A verified value is never overwritten by an automatic pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollback.&lt;/strong&gt; A bad import is a revert.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed.&lt;/strong&gt; The build reads files. There is no query to run for a spec sheet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cost is that the repo grows, and tooling matters: scripts that normalise, validate and report are part of the product. I would not do it differently, but I would write the report tools earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Solve matching before you scale shops.&lt;/strong&gt; Adding a shop is easy. Adding a shop whose rows match correctly is the work. Count unmatched rows, not shops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the market a first-class concept on day one.&lt;/strong&gt; Retrofitting "country" into a language-based design is painful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep production writes out of local development.&lt;/strong&gt; A dry-run-by-default ingest saved me more than once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static-first is a feature for SEO and for a solo dev.&lt;/strong&gt; Fewer moving parts at request time means fewer pages at 3 a.m.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be honest in the data model.&lt;/strong&gt; The deal rule and the delivered price exist because I did not want to show numbers I could not defend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test the thing you changed, not everything.&lt;/strong&gt; A full test run is slow. Targeted specs while working, the whole suite at release.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is next
&lt;/h2&gt;

&lt;p&gt;More product universes beyond shoes, bikes and electronics, more shops per country, and better matching for the long tail of models. If you have built something similar, I would like to hear how you handled sizes.&lt;/p&gt;

&lt;p&gt;You can browse the &lt;a href="https://runometry.com/en/products" rel="noopener noreferrer"&gt;full catalog&lt;/a&gt; to see the result of all this. Runometry is funded by partner links.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>cloudflare</category>
      <category>angular</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>Angular, AnalogJS, Spartan and Claude Code: shipping a 6-language comparison site fast</title>
      <dc:creator>Tim</dc:creator>
      <pubDate>Thu, 01 Oct 2026 12:35:41 +0000</pubDate>
      <link>https://dev.to/t1m4lc/angular-analogjs-spartan-and-claude-code-shipping-a-6-language-comparison-site-fast-2ec5</link>
      <guid>https://dev.to/t1m4lc/angular-analogjs-spartan-and-claude-code-shipping-a-6-language-comparison-site-fast-2ec5</guid>
      <description>&lt;p&gt;I built Runometry, a running shoe database and price comparator (841 shoes, 16 brands, 6 locales), mostly with Claude Code and parallel agents. Notes on the stack and the workflow.&lt;/p&gt;

&lt;h1&gt;
  
  
  Stack
&lt;/h1&gt;

&lt;p&gt;Angular + AnalogJS: static rendering and file-based routing, a good fit for catalog pages generated from data. Signals and standalone components make modern Angular a joy.&lt;br&gt;
Spartan (spartan.ng): shadcn-style, accessible UI components for Angular. Consistent design system, fully customizable.&lt;br&gt;
Tailwind for styling.&lt;br&gt;
Cloudflare for static hosting, with images served as WebP from a CDN.&lt;br&gt;
hreflang and localized routes from day one.&lt;/p&gt;

&lt;p&gt;Page types generated from the catalog Shoe, brand and category pages, A vs B comparator pages restricted to comparable pairs (to avoid thin or nonsensical pages), guides and blog.&lt;/p&gt;

&lt;h1&gt;
  
  
  Workflow with Claude Code
&lt;/h1&gt;

&lt;p&gt;Clear conventions up front (structure, naming, component patterns), so agents produce code that fits the project.&lt;br&gt;
Parallel agents on independent areas: comparator logic, templates, translations, data validation.&lt;br&gt;
Me reviewing diffs, running the build, and checking anything factual.&lt;br&gt;
Small, frequent commits so every agent change is easy to review or revert.&lt;/p&gt;

&lt;p&gt;What I'd do the same way Keep the stack I know best. Agents shine when you can judge their output quickly.&lt;/p&gt;

&lt;p&gt;What's next Daily price collection as a time series: price history, a price drop radar, alerts. I'm weighing feeds vs APIs vs scraping and would love input.&lt;/p&gt;

&lt;p&gt;Live: runometry.com&lt;/p&gt;

</description>
      <category>angular</category>
      <category>ai</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>The uncomfortable part of building an AI product</title>
      <dc:creator>Tim</dc:creator>
      <pubDate>Thu, 11 Jun 2026 09:36:01 +0000</pubDate>
      <link>https://dev.to/t1m4lc/the-uncomfortable-part-of-building-an-ai-product-14an</link>
      <guid>https://dev.to/t1m4lc/the-uncomfortable-part-of-building-an-ai-product-14an</guid>
      <description>&lt;p&gt;One of the most uncomfortable moments as a founder is realizing that you may have spent weeks or months building something in a way that is not quite right.&lt;/p&gt;

&lt;p&gt;Not because the vision is wrong.&lt;/p&gt;

&lt;p&gt;But because the path you took to reach it may not be the right one.&lt;/p&gt;

&lt;p&gt;That is the kind of realization I recently had while building Mozart.&lt;/p&gt;




&lt;h2&gt;
  
  
  The first path made sense
&lt;/h2&gt;

&lt;p&gt;Mozart started as a local-first desktop app to run AI agents on files and projects.&lt;/p&gt;

&lt;p&gt;The first version naturally spoke to developers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;worktrees&lt;/li&gt;
&lt;li&gt;branches&lt;/li&gt;
&lt;li&gt;local repositories&lt;/li&gt;
&lt;li&gt;coding agents&lt;/li&gt;
&lt;li&gt;CLI-based providers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And honestly, it made sense.&lt;/p&gt;

&lt;p&gt;Developers are early adopters. They understand the technical concepts. They already feel the value of AI agents. They know why local-first matters. They understand why giving agents access to files and project context can be powerful.&lt;/p&gt;

&lt;p&gt;But the more I thought about it, the more I realized something important.&lt;/p&gt;

&lt;p&gt;The real opportunity is probably not only about developers.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bigger problem
&lt;/h2&gt;

&lt;p&gt;Most people who work behind a computer already feel that AI can help them.&lt;/p&gt;

&lt;p&gt;But they are still stuck in a copy-paste loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ChatGPT on one side&lt;/li&gt;
&lt;li&gt;files on another&lt;/li&gt;
&lt;li&gt;emails somewhere else&lt;/li&gt;
&lt;li&gt;documents in a folder&lt;/li&gt;
&lt;li&gt;business tools in different tabs&lt;/li&gt;
&lt;li&gt;workflows living only in their head&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They do not need raw AI power only.&lt;/p&gt;

&lt;p&gt;They need a way to turn that power into actual work.&lt;/p&gt;

&lt;p&gt;That is where the product needs to evolve.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI is the fuel, not the vehicle
&lt;/h2&gt;

&lt;p&gt;I keep coming back to this metaphor:&lt;/p&gt;

&lt;p&gt;AI models are becoming the new fuel.&lt;br&gt;
Tokens are the oil.&lt;/p&gt;

&lt;p&gt;But fuel alone does not take you anywhere.&lt;/p&gt;

&lt;p&gt;You still need a car.&lt;br&gt;
You need a cockpit.&lt;br&gt;
You need an itinerary.&lt;/p&gt;

&lt;p&gt;And in software, that itinerary is business logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;workflows&lt;/li&gt;
&lt;li&gt;templates&lt;/li&gt;
&lt;li&gt;skills&lt;/li&gt;
&lt;li&gt;tools&lt;/li&gt;
&lt;li&gt;context&lt;/li&gt;
&lt;li&gt;company-specific processes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The value is not only in the model.&lt;/p&gt;

&lt;p&gt;The value is in the way you connect models, files, tools, workflows, and context to help someone go from point A to point B.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I want Mozart to become
&lt;/h2&gt;

&lt;p&gt;That is the direction I’m exploring with &lt;a href="https://mozart.build" rel="noopener noreferrer"&gt;Mozart&lt;/a&gt;: a local-first AI workspace designed to make agents usable on real files, workflows, and business context.&lt;/p&gt;

&lt;p&gt;Not just another tool for developers.&lt;/p&gt;

&lt;p&gt;Not just another chat interface.&lt;/p&gt;

&lt;p&gt;Not just another wrapper around an AI model.&lt;/p&gt;

&lt;p&gt;The goal is to build a cockpit where people can use agents on their own files, with their own workflows, without needing to understand all the complexity underneath.&lt;/p&gt;

&lt;p&gt;Maybe the first path was too technical.&lt;/p&gt;

&lt;p&gt;But I still believe the direction is right.&lt;/p&gt;




&lt;p&gt;I wrote more about this realization here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mozart.build/blog/wrong-path-right-direction/" rel="noopener noreferrer"&gt;Read the full post&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>startup</category>
      <category>product</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>From Burnout to Building: One Indie Dev's Story Behind Mozart</title>
      <dc:creator>Tim</dc:creator>
      <pubDate>Mon, 18 May 2026 19:52:22 +0000</pubDate>
      <link>https://dev.to/t1m4lc/from-burnout-to-building-one-indie-devs-story-behind-mozart-4k80</link>
      <guid>https://dev.to/t1m4lc/from-burnout-to-building-one-indie-devs-story-behind-mozart-4k80</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"I'm sick of working for other people's dreams."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the sentence that started it all. Not a rational decision, not a business plan, not a flash of genius, just exhaustion turning into clarity. One evening, in Montpellier, after weeks of chasing freelance gigs and hitting walls.&lt;/p&gt;

&lt;p&gt;A few days later, I started building &lt;a href="https://mozart.build" rel="noopener noreferrer"&gt;Mozart&lt;/a&gt;. This blog tells what comes next.&lt;/p&gt;

&lt;p&gt;But first, let me rewind a bit.&lt;/p&gt;

&lt;p&gt;I'm a self-taught developer. I discovered code at 14, and I learned everything on my own, through documentation, failed projects, and YouTube tutorials until 5 in the morning. For the last two years, I've done a few freelance gigs for small companies while travelling: Vietnam, Turkey, Portugal, Morocco, Thailand. Between stopovers, I tried to launch my own apps. None of them took off. I ended up dropping code for a while, I have this annoying tendency to overdose on what I love. But the passion always comes back.&lt;/p&gt;

&lt;p&gt;When I picked code back up after a few months without a computer, it hit me hard. LLMs had made a massive leap.&lt;/p&gt;

&lt;p&gt;I realised that the way we code was changing at a deep level. The craft of code is mutating: developers aren't just the ones writing each line anymore, they're becoming the ones who orchestrate, direct, and arbitrate.&lt;/p&gt;

&lt;p&gt;A code manager.&lt;/p&gt;

&lt;p&gt;Arthur Mensch, co-founder of Mistral, made the point recently &lt;a href="https://youtu.be/kKWOkWv6pJM" rel="noopener noreferrer"&gt;in front of the French National Assembly&lt;/a&gt;: the bottleneck is no longer code production. It has shifted toward design and coordination. The more agents you stack, the more productivity you gain, but that productivity plateaus quickly if you don't know how to pilot them.&lt;/p&gt;

&lt;p&gt;That's exactly the problem I want to solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dormant dream
&lt;/h2&gt;

&lt;p&gt;Around 2014, I discovered Y Combinator. At the time, I wasn't a developer yet. I had a civil engineering technician degree and worked in a lab, mostly on the road inspecting sports facilities. In the car, I listened to podcasts by Oussama Ammar (co-founder of The Family). A controversial figure, you love him or you don't, but he infused YC culture into France and inspired my generation. In parallel, I was reading Paul Graham's essays. All of that shaped how I saw startups, but only as a spectator. Watching from the sidelines, not daring to get my hands dirty.&lt;/p&gt;

&lt;p&gt;Later, when I got seriously into code, the urge to start my own company never left me. But I always had an excellent excuse to put it off: &lt;em&gt;"I'm self-taught"&lt;/em&gt;, &lt;em&gt;"I lack experience"&lt;/em&gt;, &lt;em&gt;"I still need to earn my stripes"&lt;/em&gt;, &lt;em&gt;"It's not the right time"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In hindsight, I know what was happening. &lt;strong&gt;There's never a right time.&lt;/strong&gt; The real blocker wasn't my level. It was that I hadn't yet found the idea. The one that makes you say: &lt;em&gt;this is it, this is now, I'm not letting it pass.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Crossing the desert
&lt;/h2&gt;

&lt;p&gt;Finding your path is a chaotic process. At least it was for me. I regularly cross deserts, long stretches where nothing sticks, where doubt sets in. And then, every so often, I stumble onto an oasis. That's enough to get me moving again.&lt;/p&gt;

&lt;p&gt;Before Mozart, I launched several projects. Not all of them finished, not all of them I'd own up to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://chromewebstore.google.com/detail/magic-social/jmhegjhgbgnnlpohnmicbploiiohnhdg" rel="noopener noreferrer"&gt;A Chrome extension&lt;/a&gt; to automate social-media replies via LLM. Not a bad idea, mediocre execution, zero distribution.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://raceup.org" rel="noopener noreferrer"&gt;A sports platform built around events&lt;/a&gt; in France. Around 200 visitors a day, but zero paying customers so far. Alive, but small.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lesson fits in one word: &lt;strong&gt;distribution.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In &lt;em&gt;The Hitchhiker's Guide to the Galaxy&lt;/em&gt;, Douglas Adams tells the story of a civilisation that has a brilliant idea: ship off everyone deemed "useless", salespeople, marketers, consultants, hairdressers… At first, everything's great. Fewer meetings. Less posturing. The planet feels better.&lt;/p&gt;

&lt;p&gt;Then comes the boring detail: without those people, civilisation collapses. Moral of the story: never underestimate distribution… or hairdressers lol.&lt;/p&gt;

&lt;p&gt;You can build without distributing for a while. But not forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  The genesis: memory first
&lt;/h2&gt;

&lt;p&gt;The first serious intuition wasn't actually about &lt;a href="https://mozart.build" rel="noopener noreferrer"&gt;Mozart&lt;/a&gt;. It was a thought about memory.&lt;/p&gt;

&lt;p&gt;I was sick of having to repeat the same context over and over.&lt;/p&gt;

&lt;p&gt;From one prompt to the next, memory is very limited. From one LLM to another, it disappears entirely. You explain your project to ChatGPT, then you start over with Claude, then with a third tool.&lt;/p&gt;

&lt;p&gt;Even when memory exists, it tends to stay opaque, frustrating, and hard to control.&lt;/p&gt;

&lt;p&gt;The starting idea: build a universal memory layer, pluggable into any model. A space that belongs to you, that stores your notes, your code, your projects, and exposes itself to the LLMs of your choice.&lt;/p&gt;

&lt;p&gt;I quickly realised the topic was far more complex than it looked: embeddings, entity graphs, retrieval… every path opened up new problems. And if people much more advanced than me are already working on it without a clear solution emerging, it's probably because it isn't that simple.&lt;/p&gt;

&lt;p&gt;The idea didn't disappear: it became the first pillar of Mozart.&lt;/p&gt;

&lt;p&gt;Then one evening, the second click. &lt;strong&gt;If multiple LLMs can share a common context, why stick to a single agent?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every AI giant is pushing its own closed interface. ChatGPT has one, Claude has one. But none of them is designed to make several agents work together, from different models, on interconnected tasks, in parallel.&lt;/p&gt;

&lt;p&gt;Mozart was born out of that void. A cockpit: you launch your agents, they each move forward on their own work, and you keep your hand on the whole thing.&lt;/p&gt;

&lt;p&gt;First step: look at the competition. It exists, it's well-funded, further along. The first instinct is to get discouraged. But an empty market would be far more worrying.&lt;/p&gt;

&lt;p&gt;Competitors prove one thing: the problem is real. Serious teams believe in it, investors too. But no one seems to have definitively cracked it yet.&lt;/p&gt;

&lt;p&gt;The window is probably still open.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is &lt;a href="https://mozart.build" rel="noopener noreferrer"&gt;Mozart&lt;/a&gt;?
&lt;/h2&gt;

&lt;p&gt;Here are the three founding pillars:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;For developers (at first)&lt;/strong&gt;, Mozart is designed for technical profiles who work with files and code, and who like to configure their own tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local-first &amp;amp; Markdown&lt;/strong&gt;, your files live on your machine, readable by you, ideal for LLMs, easily synced via GitHub. Ultra-low latency and more privacy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-agent &amp;amp; extensible&lt;/strong&gt;, orchestration will be the heart of the product. Coordinating agents, distributing tasks, aggregating results without depending on a single AI vendor. This logic will eventually extend beyond code (admin, ops, business).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where things stand
&lt;/h2&gt;

&lt;p&gt;Mozart isn't public yet. I'm building the MVP. Or rather an &lt;em&gt;"MVP++"&lt;/em&gt;, because I have this annoying tendency to over-polish, a real flaw when you know how important &lt;em&gt;"talk to users"&lt;/em&gt; is. I'm working on it.&lt;/p&gt;

&lt;p&gt;The next step is a beta with a few peers and early adopters, before opening up publicly ASAP.&lt;/p&gt;

&lt;p&gt;I already know where my pain point will be: distribution. Knowing how to build, that's fine. Getting it known, that's another story.&lt;/p&gt;

&lt;p&gt;Hello, world.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is the English version of an &lt;a href="https://mozart.build/blog/hello-world" rel="noopener noreferrer"&gt;article originally written in French&lt;/a&gt;. Originally published on &lt;a href="https://mozart.build/blog/hello-world" rel="noopener noreferrer"&gt;mozart.build&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>coding</category>
      <category>devs</category>
    </item>
    <item>
      <title>Turbocharge your Angular project: A config tutorial</title>
      <dc:creator>Tim</dc:creator>
      <pubDate>Thu, 09 May 2024 17:56:45 +0000</pubDate>
      <link>https://dev.to/t1m4lc/turbocharge-your-angular-project-a-config-tutorial-58g4</link>
      <guid>https://dev.to/t1m4lc/turbocharge-your-angular-project-a-config-tutorial-58g4</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome aboard, Angular aficionados! You've embarked on a journey to supercharge your Angular project with some configurations and tools I like to use.&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll guide you through setting up essential configurations, integrating handy tools, and optimizing your workflow for a smoother development experience. Get ready to elevate your Angular game to the next level!&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Your Angular Project
&lt;/h2&gt;

&lt;p&gt;To kick things off, let's create a new Angular project using the &lt;a href="https://angular.io/cli" rel="noopener noreferrer"&gt;Angular CLI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Open your terminal and run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng new your_project &lt;span class="nt"&gt;--minimal&lt;/span&gt; &lt;span class="nt"&gt;--standalone&lt;/span&gt; &lt;span class="nt"&gt;--package-manager&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pnpm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command initializes a new Angular project named 'your_project' with minimal setup (without tests) using standalone and 'pnpm' as the package manager (&lt;a href="https://pnpm.io/" rel="noopener noreferrer"&gt;pnpm site&lt;/a&gt;). Ignore this flag if you prefer using &lt;strong&gt;&lt;code&gt;npm&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;yarn&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Integrating Tailwind CSS&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Tailwind CSS offers a utility-first approach to styling your applications. Let's integrate it into our project. Run the following commands in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bashCopy code
pnpm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; tailwindcss postcss autoprefixer
npx tailwindcss init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands install Tailwind CSS and its dependencies and initialize a Tailwind configuration file.&lt;/p&gt;

&lt;p&gt;Add to your &lt;strong&gt;&lt;code&gt;style.scss&lt;/code&gt;&lt;/strong&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="k"&gt;@tailwind&lt;/span&gt; &lt;span class="nt"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="nt"&gt;components&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="nt"&gt;utilities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to add to &lt;strong&gt;&lt;code&gt;tailwind.config.js&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;content&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;./src/**/*.{html,ts}&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;h2&gt;
  
  
  &lt;strong&gt;Adding ESLint for Code Linting&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Code quality matters! Let's add ESLint, a popular code linter, to our project. Execute the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng add @angular-eslint/schematics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command integrates ESLint into your Angular project seamlessly.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Incorporating Prettier for Code Formatting&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Next up, let's enhance code formatting using Prettier. Run the following command to install Prettier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; prettier
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally, we need to prevent conflicts between Prettier and ESLint. Execute the following commands to install necessary plugins:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; prettier-eslint eslint-config-prettier eslint-plugin-prettier
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Fine-tuning Tailwind CSS with Prettier&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Tailwind CSS and Prettier can work harmoniously together. Let's ensure Tailwind classes are formatted correctly. Install the Tailwind Prettier plugin using the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; prettier-plugin-tailwindcss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Optimizing Configuration Files&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now, let's optimize our configuration files for a seamless development experience. Follow the steps below:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a &lt;strong&gt;&lt;code&gt;.prettierignore&lt;/code&gt;&lt;/strong&gt; file and copy-paste the contents of your &lt;strong&gt;&lt;code&gt;.gitignore&lt;/code&gt;&lt;/strong&gt; file into it.&lt;/li&gt;
&lt;li&gt;Ignore &lt;strong&gt;&lt;code&gt;node_modules&lt;/code&gt;&lt;/strong&gt; in ESLint by echoing &lt;strong&gt;&lt;code&gt;"node_modules" &amp;gt; .eslintignore&lt;/code&gt;&lt;/strong&gt; into your terminal.&lt;/li&gt;
&lt;li&gt;Verify and update your &lt;strong&gt;&lt;code&gt;.eslintrc&lt;/code&gt;&lt;/strong&gt; file with the provided JSON configuration.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"root"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ignorePatterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"projects/**/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"overrides"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*.ts"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"extends"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"eslint:recommended"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"plugin:@typescript-eslint/recommended"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"plugin:@angular-eslint/recommended"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"plugin:@angular-eslint/template/process-inline-templates"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"plugin:prettier/recommended"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@angular-eslint/directive-selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"attribute"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"camelCase"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@angular-eslint/component-selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"element"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kebab-case"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*.html"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"extends"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"plugin:@angular-eslint/template/recommended"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"plugin:@angular-eslint/template/accessibility"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"plugin:@angular-eslint/template/recommended"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Enhancing Visual Studio Code Setup&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Visual Studio Code is our development playground. Let's configure it for optimal productivity. Update your &lt;strong&gt;&lt;code&gt;.vscode/extensions.json&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;.vscode/settings.json&lt;/code&gt;&lt;/strong&gt; files with the provided configurations.&lt;/p&gt;

&lt;p&gt;Add to &lt;strong&gt;&lt;code&gt;.vscode/extensions.json&lt;/code&gt;&lt;/strong&gt; to recommend and install extensions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recommendations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"angular.ng-template"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"dbaeumer.vscode-eslint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"esbenp.prettier-vscode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"bradlc.vscode-tailwindcss"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add to &lt;strong&gt;&lt;code&gt;.vscode/extensions.json&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"editor.formatOnSave"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eslint.validate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"json"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"files.autoSave"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"onFocusChange"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"[html, scss]"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"editor.defaultFormatter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"esbenp.prettier-vscode"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"[typescript]"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"editor.defaultFormatter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dbaeumer.vscode-eslint"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"search.exclude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"**/node_modules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"**/dist"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"files.exclude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"node_modules/"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dist/"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;".angular/"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;&lt;code&gt;files.exclude&lt;/code&gt;&lt;/strong&gt; true option helps to save memory during builds.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Optimizing Angular Schematics&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Customize Angular schematics to generate elements in the format that interests us. Update your &lt;strong&gt;&lt;code&gt;angular.json&lt;/code&gt;&lt;/strong&gt; file with the provided schematics configurations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"schematics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@schematics/angular:component"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"inlineTemplate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"inlineStyle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scss"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipTests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"standalone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"changeDetection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OnPush"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@schematics/angular:class"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipTests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@schematics/angular:directive"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipTests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"standalone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@schematics/angular:guard"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipTests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"functional"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@schematics/angular:interceptor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipTests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@schematics/angular:pipe"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipTests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"standalone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@schematics/angular:resolver"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipTests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"functional"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@schematics/angular:service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipTests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Adding Pre-commit Hooks and lint-staged&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Ensure code quality before each commit using pre-commit hooks. Install Husky and lint-staged with the commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm i &lt;span class="nt"&gt;-D&lt;/span&gt; husky lint-staged
npx husky init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add at the end of your &lt;strong&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"lint-staged"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"*.(ts|js|html|css|scss|json|md)"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"prettier --write"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"*.(ts|js)"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"eslint --fix"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, update your &lt;strong&gt;&lt;code&gt;.husky/pre-commit&lt;/code&gt;&lt;/strong&gt; file with the provided script:&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 sh&lt;/span&gt;
&lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/_/husky.sh"&lt;/span&gt;

npx lint-staged
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Implementing Commit Linting&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Maintain a consistent commit message format with commitlint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm i D @commitlint/cli @commitlint/config-conventional
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;strong&gt;&lt;code&gt;commitlint.config.js&lt;/code&gt;&lt;/strong&gt; file with the provided configuration.&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="nb"&gt;touch &lt;/span&gt;commitlint.config.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And add it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;extends&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;@commitlint/config-conventional&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally, update your &lt;strong&gt;&lt;code&gt;.husky/commit-msg&lt;/code&gt;&lt;/strong&gt; file with the provided script:&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 sh&lt;/span&gt;
&lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/_/husky.sh"&lt;/span&gt;

npx &lt;span class="nt"&gt;--no&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; commitlint &lt;span class="nt"&gt;--edit&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Congratulations! You've successfully configured your Angular project with essential tools and optimizations. With Tailwind CSS for styling, ESLint and Prettier for code quality, and seamless integration of pre-commit hooks and commit linting, your development workflow is now smoother and more efficient than ever.&lt;/p&gt;

&lt;p&gt;Let us know in the comments if there are any tools or configurations you like to use in your projects?&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;🚀 Follow me on &lt;a href="https://twitter.com/alcaidio" rel="noopener noreferrer"&gt;X.com&lt;/a&gt; or &lt;a href="https://github.com/alcaidio" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; for more tips and updates on Angular development!&lt;/p&gt;

</description>
      <category>angular</category>
      <category>configuration</category>
    </item>
    <item>
      <title>Un opérateur RXJS pour debugger avec élégance ?</title>
      <dc:creator>Tim</dc:creator>
      <pubDate>Wed, 27 Sep 2023 17:26:29 +0000</pubDate>
      <link>https://dev.to/t1m4lc/un-operateur-rxjs-pour-debugger-avec-elegance--565e</link>
      <guid>https://dev.to/t1m4lc/un-operateur-rxjs-pour-debugger-avec-elegance--565e</guid>
      <description>&lt;p&gt;Tous les développeurs utilisant RXJS qui ont voulu déboguer ont déjà fait :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;obs$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="c1"&gt;// ou pour les plus malins.&lt;/span&gt;
&lt;span class="nx"&gt;obs$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;C'est pas très fun. Pour y remédier, on va créer un opérateur personnalisé qui nous permettra d'avoir : &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%2Fobleiosnd6c5bnpj1fdi.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%2Fobleiosnd6c5bnpj1fdi.png" alt="rxjs operateur debug output" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Plus sympa comme logs non ? &lt;br&gt;
Pour l'utiliser, il suffit de le passer dans le pipe :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&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;debug&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;./custom.operators.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;obs$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&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;Côté implémentation, c’est simple :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Untitled&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;production&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="nx"&gt;tap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tag&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="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`%c[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: Next]`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;background: #84cc16; color: #fff; padding: 3px; font-size: 9px; font-weight: 500;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;value&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: Error]`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;background: #ef4444; color: #fff; padding: 3px; font-size: 9px; font-weight: 500;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;error&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`%c[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]: Complete`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;background: #6b7280; color: #fff; padding: 3px; font-size: 9px; font-weight: 500;&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="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;En bonus, j'ai ajouté une petite ligne en haut pour ne pas logger en production.&lt;/p&gt;

&lt;p&gt;Voilà :) Happy coding !&lt;/p&gt;

</description>
      <category>rxjs</category>
      <category>operator</category>
      <category>angular</category>
    </item>
    <item>
      <title>Unsubscribe, un jeu d’enfant avec takeUntilDestroyed</title>
      <dc:creator>Tim</dc:creator>
      <pubDate>Wed, 26 Jul 2023 07:50:00 +0000</pubDate>
      <link>https://dev.to/t1m4lc/unsubscribe-un-jeu-denfant-avec-takeuntildestroyed-4lf1</link>
      <guid>https://dev.to/t1m4lc/unsubscribe-un-jeu-denfant-avec-takeuntildestroyed-4lf1</guid>
      <description>&lt;p&gt;Avoir un moyen d'unsubscribe à une observable facilement ça vous dis ? &lt;/p&gt;

&lt;p&gt;En effet, tout développeur Angular c’est un jour trouvé confronté à répéter du code pour unsubscribe à des observables qui ont été subscribes manuellement (c’est-à-dire sans utiliser le pipe async fourni par Angular qui s’occupe d’unsubscribe automatiquement à notre place).&lt;/p&gt;

&lt;p&gt;Et oui, qui dit observable dit subscribe et qui dit subscribe dit unsubscribe ! Tout oubli d'unsubscribe implique une fuite de mémoire et les pertes de performance qui vont avec.&lt;/p&gt;

&lt;p&gt;Unsubscribe peut vite devenir un processus répétitif qui va réduire la lisibilité de notre code. Mais ça c’était avant&amp;nbsp;! &lt;/p&gt;

&lt;p&gt;Depuis Angular &lt;a href="https://github.com/angular/angular/releases/tag/16.0.0" rel="noopener noreferrer"&gt;v16&lt;/a&gt; l'opérateur &lt;code&gt;takeUntilDestroyed&lt;/code&gt; va nous permettre de nous simplifier la vie.&lt;/p&gt;

&lt;p&gt;Pour l’utiliser, c’est simple, tu l’importes depuis &lt;code&gt;@angular/core/rxjs-interop&lt;/code&gt; et tu le chaines à une observable. Par exemple&amp;nbsp;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;takeUntilDestroyed&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/rxjs-interop&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="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="nc"&gt;ExempleComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;takeUntilDestroyed&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&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;L'opérateur&amp;nbsp;&lt;code&gt;takeUntilDestroyed&lt;/code&gt; est conçu pour être utilisé à l'intérieur d'un&amp;nbsp;contexte d'injection. C’est pourquoi il est utilisé dans le&amp;nbsp;&lt;code&gt;constructeur&lt;/code&gt;&amp;nbsp;de&amp;nbsp;&lt;code&gt;ExempleComponent&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Si tu souhaites utiliser l'opérateur en dehors d’un contexte d’injection, tu dois lui passer explicitement le fournisseur&amp;nbsp;&lt;a href="https://angular.io/api/core/DestroyRef" rel="noopener noreferrer"&gt;DestroyRef&lt;/a&gt;. Voici un exemple :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;takeUntilDestroyed&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/rxjs-interop&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="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="nc"&gt;ExempleComponent&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="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;destroyRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DestroyRef&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;takeUntilDestroyed&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;destroyRef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&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;En conclusions, on a vu que l’opérateur &lt;code&gt;takeUntilDestroyed&lt;/code&gt; nous permet de complete automatiquement et simplement un observable lorsque le contexte d'appel est détruit. Cela contribue donc à éviter les fuites de mémoire et améliore l’expérience de dev en apportant un code plus lisible.&lt;/p&gt;

&lt;p&gt;Merci d'être ici 😇 n'oublier pas de liker, commenter ou partager ça m'aide à continuer de poster du contenu 👨🏻‍💻&lt;/p&gt;

</description>
      <category>angular</category>
      <category>rxjs</category>
      <category>observable</category>
      <category>unsubscribe</category>
    </item>
    <item>
      <title>L’option transform des inputs Angular 🪄</title>
      <dc:creator>Tim</dc:creator>
      <pubDate>Sun, 23 Jul 2023 11:56:00 +0000</pubDate>
      <link>https://dev.to/t1m4lc/ajoutez-des-super-pouvoirs-a-vos-inputs-angular-jep</link>
      <guid>https://dev.to/t1m4lc/ajoutez-des-super-pouvoirs-a-vos-inputs-angular-jep</guid>
      <description>&lt;p&gt;Depuis Angular &lt;a href="https://github.com/angular/angular/releases/tag/16.1.0" rel="noopener noreferrer"&gt;v16.1.0&lt;/a&gt;, il existe une alternative facile à l’utilisation de setter et getter pour transformer les inputs. &lt;/p&gt;

&lt;p&gt;Le décorateur @Input inclut désormais une option de transformation qui accepte une fonction permettant de transformer l'input avant de l'affecter à l'instance du composant (ou directive).&lt;/p&gt;

&lt;p&gt;A quoi ça sert me direz-vous ? &lt;/p&gt;

&lt;p&gt;Voici un premier exemple pour comprendre : &lt;/p&gt;

&lt;p&gt;Imaginons que nous ayons un composant &lt;code&gt;TowerComponent&lt;/code&gt; qui représente une tour (une simple div avec un gris en background 😅) le composant prend en input une hauteur.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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="kr"&gt;number&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="nf"&gt;isNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;app-tower&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;div class="tower"[style.height.px]="height"&amp;gt;&amp;lt;/div&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`
    .tower {
      background: #555;
      width: 10px;
    }
  `&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="nc"&gt;TowerComponent&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="na"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;toNumber&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;Alors qu'avec le pattern setter/getter on aurait eu :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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="nc"&gt;ButtonComponent&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="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;height&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_height&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="kr"&gt;number&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;_height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;toNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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;_height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;Plus lisible avec transform n'est-ce pas ?&lt;/p&gt;

&lt;p&gt;La fonction de transformation &lt;code&gt;toNumber&lt;/code&gt; se chargera de la conversion, transformant ainsi la valeur de string en nombre. &lt;/p&gt;

&lt;p&gt;Dans le composant parent on aura :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;tower&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"42"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;C'est pas tout, Angular fournit également des fonctions utilitaires dans &lt;code&gt;@angular/core&lt;/code&gt; permettant de couvrir les cas d’utilisation courant :  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;numberAttribute&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;booleanAttribute&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On peut donc supprimer la fonction &lt;code&gt;toNumber&lt;/code&gt; et refactoriser notre &lt;code&gt;TowerComponent&lt;/code&gt; en :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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="nc"&gt;TowerComponent&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="na"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;numberAttribute&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;Un autre scénario où cette fonctionnalité est pratique est lors de la manipulation d'attributs HTML. &lt;/p&gt;

&lt;p&gt;Les attributs booléens sont considérés comme vrais s'ils sont présents sur un nœud DOM et comme faux s'ils sont omis. &lt;/p&gt;

&lt;p&gt;Cependant, Angular interprète tous les attributs statiques comme des chaînes de caractères, ce qui entraîne souvent un problème. Par exemple, le code suivant &lt;code&gt;disabled&lt;/code&gt; renverrait une chaîne vide :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&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;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;app-button&lt;/span&gt;&lt;span class="dl"&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;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ButtonComponent&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;disabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;button&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;L’utilisation de l’utilitaire &lt;code&gt;booleanAttribute&lt;/code&gt; avec l’option de transformation permet de résoudre ce problème sans utiliser le pattern setter/getter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&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;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;app-button&lt;/span&gt;&lt;span class="dl"&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;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ButtonComponent&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="na"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;booleanAttribute&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="nx"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;La fonction de transformation &lt;code&gt;booleanAttribute&lt;/code&gt; s’occupe de gérer la conversion en booléen. Grâce à ça, lorsque nous utilisons le composant &lt;code&gt;ButtonComponent&lt;/code&gt; et que nous spécifions l'attribut disabled :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;button&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La fonction de transformation interprétera correctement la présence de l'attribut comme vrai.&lt;/p&gt;

&lt;p&gt;Pour conclure nous avons vu que l'option de transformation dans le décorateur @Input simplifie le processus de transformation d'Input, offrant une approche qui rend notre code plus lisible par rapport à l’approche traditionnelles. L'utilisation des fonctions &lt;code&gt;numberAttribute&lt;/code&gt; et &lt;code&gt;booleanAttribute&lt;/code&gt; nous permet de couvrir la majorité des cas d’utilisation.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>input</category>
    </item>
  </channel>
</rss>
