Ninja Squad2024-03-14T07:55:44+00:00https://blog.ninja-squad.com/Ninja Squadcontact@ninja-squad.comWhat's new in Angular 17.3?2024-03-13T00:00:00+00:00https://blog.ninja-squad.com/2024/03/13/what-is-new-angular-17.3<p>Angular 17.3.0 is here!</p>
<p style="text-align: center;">
<a href="https://github.com/angular/angular/releases/tag/17.3.0">
<img class="rounded img-fluid" style="max-width: 60%" src="/assets/images/angular_gradient.png" alt="Angular logo" />
</a>
</p>
<p>This is a minor release with some nice features: let’s dive in!</p>
<h2 id="typescript-54-support">TypeScript 5.4 support</h2>
<p>Angular v17.3 now supports TypeScript 5.4. This means that you can use the latest version of TypeScript in your Angular applications. You can check out the <a href="https://devblogs.microsoft.com/typescript/announcing-typescript-5-4/">TypeScript 5.4 release notes</a> to learn more about the new features.</p>
<h2 id="new-template-compiler">New template compiler</h2>
<p>Angular now uses a new template compiler! The work on this compiler started more than a year ago and has been done in parallel with the other features we saw in the previous releases to eventually pass all of the existing tests. It’s now the case, and this new compiler is now the default in Angular 17.3.</p>
<p>This compiler is based on an intermediate representation of template operations,
a common concept in compilers, for example in LLVM.
This <em>IR</em> semantically encodes what needs to happen at runtime to render and change-detect the template.
Using an <em>IR</em> allows for different concerns of template compilation to be processed independently,
which was not the case with the previous implementation.
This new compiler is easier to maintain and extend,
so it is a great foundation for future improvements in the framework.</p>
<p>Note that the compiler emits the same code as the previous one,
so you should not see any difference in the generated code.</p>
<h2 id="output-functions">output functions</h2>
<p>A new (developer preview) feature was added to allow the declaration
of outputs similarly to the <code class="language-plaintext highlighter-rouge">input()</code> function.</p>
<p>As for inputs, you can use the <code class="language-plaintext highlighter-rouge">output()</code> function to define an output:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">ponySelected</span> <span class="o">=</span> <span class="nx">output</span><span class="o"><</span><span class="nx">PonyModel</span><span class="o">></span><span class="p">();</span>
<span class="c1">// ^? OutputEmitterRef<PonyModel></span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">output()</code> function returns an <code class="language-plaintext highlighter-rouge">OutputEmitterRef<T></code>
that can be used to emit values.
<code class="language-plaintext highlighter-rouge">OutputEmitterRef</code> is an Angular class,
really similar to a simplified <code class="language-plaintext highlighter-rouge">EventEmitter</code>
but that does not rely on <code class="language-plaintext highlighter-rouge">RxJS</code>
(to limit the coupling of Angular with RxJS).</p>
<p>The function accepts a parameter to specify options,
the only one available for now is <code class="language-plaintext highlighter-rouge">alias</code> to alias the output.</p>
<p>As with <code class="language-plaintext highlighter-rouge">EventEmitter</code>, you can use the <code class="language-plaintext highlighter-rouge">emit()</code> method to emit a value:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">ponySelected</span> <span class="o">=</span> <span class="nx">output</span><span class="o"><</span><span class="nx">PonyModel</span><span class="o">></span><span class="p">();</span>
<span class="c1">// ^? OutputEmitterRef<PonyModel></span>
<span class="nx">select</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">ponySelected</span><span class="p">.</span><span class="nx">emit</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">ponyModel</span><span class="p">());</span>
<span class="p">}</span>
</code></pre></div></div>
<p>You can also declare an output without a generic type,
and the <code class="language-plaintext highlighter-rouge">OutputEmitterRef</code> will be of type <code class="language-plaintext highlighter-rouge">OutputEmitterRef<void></code>.
You can then call <code class="language-plaintext highlighter-rouge">emit()</code> without a parameter on such an output.</p>
<p><code class="language-plaintext highlighter-rouge">OutputEmitterRef</code> also exposes a subscribe method
to manually subscribe to the output.
This is not something you’ll do often, but it can be handy in some cases.
If you manually subscribe to an output,
you’ll have to manually unsubscribe as well.
To do so, the subscribe method returns an <code class="language-plaintext highlighter-rouge">OutputRefSubscription</code> object with an <code class="language-plaintext highlighter-rouge">unsubscribe</code> method.</p>
<p>Two new functions have been added to the <code class="language-plaintext highlighter-rouge">rxjs-interop</code> package to convert an output to an observable,
and an observable to an output.</p>
<p>Angular always had the capability of using observables other than <code class="language-plaintext highlighter-rouge">EventEmitter</code> for outputs.
This is not something that is largely used, but it’s possible.
The new <code class="language-plaintext highlighter-rouge">outputFromObservable</code> function allows you to convert an observable to an output:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">ponyRunning$</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">BehaviorSubject</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
<span class="nx">ponyRunning</span> <span class="o">=</span> <span class="nx">outputFromObservable</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">ponyRunning$</span><span class="p">);</span>
<span class="c1">// ^? OutputRef<boolean></span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">outputFromObservable</code> function returns an <code class="language-plaintext highlighter-rouge">OutputRef<T></code>, and not an <code class="language-plaintext highlighter-rouge">OutputEmitterRef<T></code>,
as you can’t emit values on an output created from an observable.
The output emits every event that is emitted by the observable.</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">startRunning</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">ponyRunning$</span><span class="p">.</span><span class="nx">next</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>It is also possible to convert an output to an observable using the <code class="language-plaintext highlighter-rouge">outputToObservable</code> function if needed.
You can then use <code class="language-plaintext highlighter-rouge">.pipe()</code> and all the RxJS operators on the converted output.</p>
<p>These interoperability functions will probably be rarely used.
<code class="language-plaintext highlighter-rouge">output()</code>, on the other hand, will become the recommended way to declare outputs in Angular components.</p>
<h2 id="hostattributetoken">HostAttributeToken</h2>
<p>Angular has always allowed to inject the value of an attribute of the host element.
For example, to get the type of an input, you can use:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@</span><span class="nd">Directive</span><span class="p">({</span>
<span class="na">selector</span><span class="p">:</span> <span class="dl">'</span><span class="s1">input</span><span class="dl">'</span><span class="p">,</span>
<span class="na">standalone</span><span class="p">:</span> <span class="kc">true</span>
<span class="p">})</span>
<span class="kd">class</span> <span class="nx">InputAttrDirective</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(@</span><span class="nd">Attribute</span><span class="p">(</span><span class="dl">'</span><span class="s1">type</span><span class="dl">'</span><span class="p">)</span> <span class="k">private</span> <span class="kd">type</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// type would be 'text' if `<input type="text" /></span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Since Angular v14, injection can be done via the <code class="language-plaintext highlighter-rouge">inject()</code> function as well,
but there was no option to get an attribute value with it.</p>
<p>This is now possible by using a special class <code class="language-plaintext highlighter-rouge">HostAttributeToken</code>:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="o">=</span> <span class="nx">inject</span><span class="p">(</span><span class="k">new</span> <span class="nx">HostAttributeToken</span><span class="p">(</span><span class="dl">'</span><span class="s1">type</span><span class="dl">'</span><span class="p">));</span>
</code></pre></div></div>
<p>Note that <code class="language-plaintext highlighter-rouge">inject</code> throws if the attribute is not found (unless you pass a second argument <code class="language-plaintext highlighter-rouge">{ optional: true }</code>).</p>
<h2 id="routertestingmodule-deprecation">RouterTestingModule deprecation</h2>
<p>The <code class="language-plaintext highlighter-rouge">RouterTestingModule</code> is now deprecated.
It is now recommended to <code class="language-plaintext highlighter-rouge">provideRouter()</code> in the <code class="language-plaintext highlighter-rouge">TestBed</code> configuration instead.</p>
<h2 id="new-router-types">New router types</h2>
<p>The router now has new types to model the result of guards and resolvers.</p>
<p>For example, the CanActivate guard was declared like this:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">type</span> <span class="nx">CanActivateFn</span> <span class="o">=</span> <span class="p">(</span><span class="nx">route</span><span class="p">:</span> <span class="nx">ActivatedRouteSnapshot</span><span class="p">,</span> <span class="nx">state</span><span class="p">:</span> <span class="nx">RouterStateSnapshot</span><span class="p">)</span> <span class="o">=></span> <span class="nx">Observable</span><span class="o"><</span><span class="nx">boolean</span> <span class="o">|</span> <span class="nx">UrlTree</span><span class="o">></span> <span class="o">|</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">boolean</span> <span class="o">|</span> <span class="nx">UrlTree</span><span class="o">></span> <span class="o">|</span> <span class="nx">boolean</span> <span class="o">|</span> <span class="nx">UrlTree</span><span class="p">;</span>
</code></pre></div></div>
<p>This is because the guard can return a boolean to allow or forbid the navigation, or an <code class="language-plaintext highlighter-rouge">UrlTree</code> to trigger a redirection to another route. This result can be synchronous or asynchronous.</p>
<p>The signature has been updated to:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">type</span> <span class="nx">CanActivateFn</span> <span class="o">=</span> <span class="p">(</span><span class="nx">route</span><span class="p">:</span> <span class="nx">ActivatedRouteSnapshot</span><span class="p">,</span> <span class="nx">state</span><span class="p">:</span> <span class="nx">RouterStateSnapshot</span><span class="p">)</span> <span class="o">=></span> <span class="nx">MaybeAsync</span><span class="o"><</span><span class="nx">GuardResult</span><span class="o">></span><span class="p">;</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">GuardResult</code> is a new type equal to <code class="language-plaintext highlighter-rouge">boolean | UrlTree</code>,
and <code class="language-plaintext highlighter-rouge">MaybeAsync<T></code> is a new generic type equal to <code class="language-plaintext highlighter-rouge">T | Observable<T> | Promise<T></code>.</p>
<p>A resolver function now also returns a <code class="language-plaintext highlighter-rouge">MaybeAsync<T></code>.
You can keep using the older signatures but the new ones are more concise.</p>
<h2 id="angular-cli">Angular CLI</h2>
<p>Angular CLI v17.3 doesn’t bring a lot of new features,
but we can note that <code class="language-plaintext highlighter-rouge">deployUrl</code> is now supported in the application builder.
It was initially marked as deprecated but was re-introduced after community feedback.</p>
<h2 id="summary">Summary</h2>
<p>That’s all for this release.
The next stop is v18, where we should see some developer preview features becoming stable.
Stay tuned!</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Angular 17.2?2024-02-14T00:00:00+00:00https://blog.ninja-squad.com/2024/02/14/what-is-new-angular-17.2<p>Angular 17.2.0 is here!</p>
<p style="text-align: center;">
<a href="https://github.com/angular/angular/releases/tag/17.2.0">
<img class="rounded img-fluid" style="max-width: 60%" src="/assets/images/angular_gradient.png" alt="Angular logo" />
</a>
</p>
<p>This is a minor release with some nice features: let’s dive in!</p>
<h2 id="queries-as-signals">Queries as signals</h2>
<p>A new developer preview feature has been added to allow the use of queries as signals. <code class="language-plaintext highlighter-rouge">viewChild()</code>, <code class="language-plaintext highlighter-rouge">viewChildren()</code>, <code class="language-plaintext highlighter-rouge">contentChild()</code>, and <code class="language-plaintext highlighter-rouge">contentChildren()</code> functions have been added in @angular/core and return signals.</p>
<p>Let’s go through a few examples.</p>
<p>You can use viewChild to query the template:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// <canvas #chart></canvas></span>
<span class="nx">canvas</span> <span class="o">=</span> <span class="nx">viewChild</span><span class="o"><</span><span class="nx">ElementRef</span><span class="o"><</span><span class="nx">HTMLCanvasElement</span><span class="o">>></span><span class="p">(</span><span class="dl">'</span><span class="s1">chart</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// ^? Signal<ElementRef<HTMLCanvasElement> | undefined></span>
<span class="c1">// <form></form> with FormsModule</span>
<span class="nx">form</span> <span class="o">=</span> <span class="nx">viewChild</span><span class="p">(</span><span class="nx">NgForm</span><span class="p">);</span>
<span class="c1">// ^? Signal<NgForm | undefined></span>
</code></pre></div></div>
<p>As you can see, the return type is a Signal containing the queried <code class="language-plaintext highlighter-rouge">ElementRef<HTMLElement></code> or <code class="language-plaintext highlighter-rouge">undefined</code>, or the queried component/directive or <code class="language-plaintext highlighter-rouge">undefined</code>.</p>
<p>You can specify that the queried element is required to get rid of <code class="language-plaintext highlighter-rouge">undefined</code>:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">canvas</span> <span class="o">=</span> <span class="nx">viewChild</span><span class="p">.</span><span class="nx">required</span><span class="o"><</span><span class="nx">ElementRef</span><span class="o"><</span><span class="nx">HTMLCanvasElement</span><span class="o">>></span><span class="p">(</span><span class="dl">'</span><span class="s1">chart</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// ^? Signal<ElementRef<HTMLCanvasElement>></span>
</code></pre></div></div>
<p>If the element is not found, you’ll have a runtime error:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>'NG0951: Child query result is required but no value is available.
Find more at https://angular.io/errors/NG0951'
</code></pre></div></div>
<p>This error can also happen if you try to access the query result too soon, for example in the constructor of the component. You can access the query result in the <code class="language-plaintext highlighter-rouge">ngAfterViewInit</code>/<code class="language-plaintext highlighter-rouge">ngAfterViewChecked</code> lifecycle hooks, or in the <code class="language-plaintext highlighter-rouge">afterNextRender</code>/<code class="language-plaintext highlighter-rouge">afterRender</code> functions.</p>
<p>You can also use <code class="language-plaintext highlighter-rouge">viewChildren</code> to query multiple elements. In that case, you get a Signal containing a readonly array of elements, or an empty array if no element is found (we no longer need QueryList \o/):
chart.component.ts</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">canvases</span> <span class="o">=</span> <span class="nx">viewChildren</span><span class="o"><</span><span class="nx">ElementRef</span><span class="o"><</span><span class="nx">HTMLCanvasElement</span><span class="o">>></span><span class="p">(</span><span class="dl">'</span><span class="s1">chart</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// ^? Signal<ReadonlyArray<ElementRef<HTMLCanvasElement>>></span>
</code></pre></div></div>
<p>The functions accept the same option as <code class="language-plaintext highlighter-rouge">@ViewChild</code> and <code class="language-plaintext highlighter-rouge">@ViewChildren</code>, so you can specify the <code class="language-plaintext highlighter-rouge">read</code> option to query a directive or provider on an element.</p>
<p>As you can imagine, the same is possible for <code class="language-plaintext highlighter-rouge">contentChild</code> and <code class="language-plaintext highlighter-rouge">contentChildren</code>.</p>
<p>For example, if we want to build a <code class="language-plaintext highlighter-rouge">TabsComponent</code> that can be used like this:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><ns-tabs></span>
<span class="nt"><ns-tab</span> <span class="na">title=</span><span class="s">"Races"</span> <span class="nt">/></span>
<span class="nt"><ns-tab</span> <span class="na">title=</span><span class="s">"About"</span> <span class="nt">/></span>
<span class="nt"></ns-tabs></span>
</code></pre></div></div>
<p>We can build a <code class="language-plaintext highlighter-rouge">TabDirective</code> to represent a tab:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@</span><span class="nd">Directive</span><span class="p">({</span>
<span class="na">selector</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ns-tab</span><span class="dl">'</span><span class="p">,</span>
<span class="na">standalone</span><span class="p">:</span> <span class="kc">true</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">TabDirective</span> <span class="p">{</span>
<span class="nx">title</span> <span class="o">=</span> <span class="nx">input</span><span class="p">.</span><span class="nx">required</span><span class="o"><</span><span class="kr">string</span><span class="o">></span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>then build the <code class="language-plaintext highlighter-rouge">TabsComponent</code> with <code class="language-plaintext highlighter-rouge">contentChildren</code> to query the directives:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@</span><span class="nd">Component</span><span class="p">({</span>
<span class="na">selector</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ns-tabs</span><span class="dl">'</span><span class="p">,</span>
<span class="na">template</span><span class="p">:</span> <span class="s2">`
<ul class="nav nav-tabs">
@for (tab of tabs(); track tab) {
<li class="nav-item">
<a class="nav-link">{{ tab.title() }}</a>
</li>
}
</ul>
`</span><span class="p">,</span>
<span class="na">standalone</span><span class="p">:</span> <span class="kc">true</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">TabsComponent</span> <span class="p">{</span>
<span class="nx">tabs</span> <span class="o">=</span> <span class="nx">contentChildren</span><span class="p">(</span><span class="nx">TabDirective</span><span class="p">);</span>
<span class="c1">// ^? Signal<ReadonlyArray<TabDirective>></span>
<span class="p">}</span>
</code></pre></div></div>
<p>As for the <code class="language-plaintext highlighter-rouge">@ViewChild</code>/<code class="language-plaintext highlighter-rouge">@ViewChildren</code> decorators, we can specify the descendants option to query the tab directives that are not direct children of <code class="language-plaintext highlighter-rouge">TabsComponent</code>:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">tabs</span> <span class="o">=</span> <span class="nx">contentChildren</span><span class="p">(</span><span class="nx">TabDirective</span><span class="p">,</span> <span class="p">{</span> <span class="na">descendants</span><span class="p">:</span> <span class="kc">true</span> <span class="p">});</span>
<span class="c1">// ^? Signal<ReadonlyArray<TabDirective>></span>
</code></pre></div></div>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><ns-tabs></span>
<span class="nt"><div></span>
<span class="nt"><ns-tab</span> <span class="na">title=</span><span class="s">"Races"</span> <span class="nt">/></span>
<span class="nt"></div></span>
<span class="nt"><ns-tabgroup></span>
<span class="nt"><ns-tab</span> <span class="na">title=</span><span class="s">"About"</span> <span class="nt">/></span>
<span class="nt"></ns-tabgroup></span>
<span class="nt"></ns-tabs></span>
</code></pre></div></div>
<p>As <code class="language-plaintext highlighter-rouge">viewChild</code>, <code class="language-plaintext highlighter-rouge">contentChild</code> can be required.</p>
<h2 id="model-signal"><code class="language-plaintext highlighter-rouge">model</code> signal</h2>
<p>Signals also allow a fresh take on existing patterns. As you probably know, Angular allows a “banana in a box” syntax for two-way binding. This is mostly used with <code class="language-plaintext highlighter-rouge">ngModel</code> to bind a form control to a component property:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><input</span> <span class="na">name=</span><span class="s">"login"</span> <span class="na">[(ngModel)]=</span><span class="s">"user.login"</span> <span class="nt">/></span>
</code></pre></div></div>
<p>Under the hood, this is because the <code class="language-plaintext highlighter-rouge">ngModel</code> directive has a <code class="language-plaintext highlighter-rouge">ngModel</code> input and a <code class="language-plaintext highlighter-rouge">ngModelChange</code> output.</p>
<p>So the banana in a box syntax is just syntactic sugar for the following:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><input</span> <span class="na">name=</span><span class="s">"login"</span> <span class="na">[ngModel]=</span><span class="s">"user.login"</span> <span class="na">(ngModelChange)=</span><span class="s">"user.login = $event"</span> <span class="nt">/></span>
</code></pre></div></div>
<p>The syntax is, in fact, general and can be used with any component or directive that has an input named <code class="language-plaintext highlighter-rouge">something</code> and an output named <code class="language-plaintext highlighter-rouge">somethingChange</code>.</p>
<p>You can leverage this in your own components and directives, for example, to build a pagination component:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@</span><span class="nd">Input</span><span class="p">({</span> <span class="na">required</span><span class="p">:</span> <span class="kc">true</span> <span class="p">})</span> <span class="nx">collectionSize</span><span class="o">!</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Input</span><span class="p">({</span> <span class="na">required</span><span class="p">:</span> <span class="kc">true</span> <span class="p">})</span> <span class="nx">pageSize</span><span class="o">!</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Input</span><span class="p">({</span> <span class="na">required</span><span class="p">:</span> <span class="kc">true</span> <span class="p">})</span> <span class="nx">page</span><span class="o">!</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Output</span><span class="p">()</span> <span class="nx">pageChange</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">EventEmitter</span><span class="o"><</span><span class="kr">number</span><span class="o">></span><span class="p">();</span>
<span class="nl">pages</span><span class="p">:</span> <span class="nb">Array</span><span class="o"><</span><span class="kr">number</span><span class="o">></span> <span class="o">=</span> <span class="p">[];</span>
<span class="nx">ngOnChanges</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">pages</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">computePages</span><span class="p">();</span>
<span class="p">}</span>
<span class="nx">goToPage</span><span class="p">(</span><span class="nx">page</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">pageChange</span><span class="p">.</span><span class="nx">emit</span><span class="p">(</span><span class="nx">page</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">private</span> <span class="nx">computePages</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">({</span> <span class="na">length</span><span class="p">:</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">ceil</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">collectionSize</span> <span class="o">/</span> <span class="k">this</span><span class="p">.</span><span class="nx">pageSize</span><span class="p">)</span> <span class="p">},</span> <span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="nx">i</span><span class="p">)</span> <span class="o">=></span> <span class="nx">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The component receives the collection, the page size, and the current page as inputs, and emits the new page when the user clicks on a button.</p>
<p>Every time an input changes, the component recomputes the buttons to display.
The template uses a for loop to display the buttons:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@for (pageNumber of pages; track pageNumber) {
<span class="nt"><button</span> <span class="na">[class.active]=</span><span class="s">"page === pageNumber"</span> <span class="na">(click)=</span><span class="s">"goToPage(pageNumber)"</span><span class="nt">></span>
{{ pageNumber }}
<span class="nt"></button></span>
}
</code></pre></div></div>
<p>The component can then be used like:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><ns-pagination</span> <span class="na">[(page)]=</span><span class="s">"page"</span> <span class="na">[collectionSize]=</span><span class="s">"collectionSize"</span> <span class="na">[pageSize]=</span><span class="s">"pageSize"</span><span class="nt">></ns-pagination></span>
</code></pre></div></div>
<p>Note that <code class="language-plaintext highlighter-rouge">page</code> can be a number or a signal of a number,
the framework will handle it correctly.</p>
<p>The pagination component can be rewritten using signals,
and the brand new <code class="language-plaintext highlighter-rouge">model()</code> function:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">collectionSize</span> <span class="o">=</span> <span class="nx">input</span><span class="p">.</span><span class="nx">required</span><span class="o"><</span><span class="kr">number</span><span class="o">></span><span class="p">();</span>
<span class="nx">pageSize</span> <span class="o">=</span> <span class="nx">input</span><span class="p">.</span><span class="nx">required</span><span class="o"><</span><span class="kr">number</span><span class="o">></span><span class="p">();</span>
<span class="nx">pages</span> <span class="o">=</span> <span class="nx">computed</span><span class="p">(()</span> <span class="o">=></span> <span class="k">this</span><span class="p">.</span><span class="nx">computePages</span><span class="p">());</span>
<span class="nx">page</span> <span class="o">=</span> <span class="nx">model</span><span class="p">.</span><span class="nx">required</span><span class="o"><</span><span class="kr">number</span><span class="o">></span><span class="p">();</span>
<span class="c1">// ^? ModelSignal<number>;</span>
<span class="nx">goToPage</span><span class="p">(</span><span class="nx">page</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">page</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">page</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">private</span> <span class="nx">computePages</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">({</span> <span class="na">length</span><span class="p">:</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">ceil</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">collectionSize</span><span class="p">()</span> <span class="o">/</span> <span class="k">this</span><span class="p">.</span><span class="nx">pageSize</span><span class="p">())</span> <span class="p">},</span> <span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="nx">i</span><span class="p">)</span> <span class="o">=></span> <span class="nx">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>As you can see, a <code class="language-plaintext highlighter-rouge">model()</code> function is used to define the input/output pair, and the output emission is done using the <code class="language-plaintext highlighter-rouge">set()</code> method of the signal.</p>
<p>A model can be required, or can have a default value, or can be aliased, as it is the case for inputs. It can’t be transformed though. If you use an alias, the output will be aliased as well.</p>
<p>If you try to access the value of the model before it has been set, for example in the constructor of the component, then you’ll have a runtime error:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>'NG0952: Model is required but no value is available yet.
Find more at https://angular.io/errors/NG0952'
</code></pre></div></div>
<h2 id="defer-testing">Defer testing</h2>
<p>The default behavior of the <code class="language-plaintext highlighter-rouge">TestBed</code>
for testing components using <code class="language-plaintext highlighter-rouge">@defer</code> blocks has changed from
<code class="language-plaintext highlighter-rouge">Manual</code> to <code class="language-plaintext highlighter-rouge">Playthrough</code>.</p>
<p>Check out our blog post about <a href="https://blog.ninja-squad.com/2023/11/09/what-is-new-angular-17.0/">defer</a> for more details.</p>
<h2 id="ngoptimizedimage">NgOptimizedImage</h2>
<p>The NgOptimizedImage directive
(check out <a href="/2022/08/26/what-is-new-angular-14.2">our blog post</a> about it)
can now automatically display a placeholder while the image is loading,
if the provider supports automatic image resizing.</p>
<p>This can be enabled by adding a <code class="language-plaintext highlighter-rouge">placeholder</code> attribute to the directive:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><img</span> <span class="na">ngSrc=</span><span class="s">"logo.jpg"</span> <span class="na">placeholder</span> <span class="nt">/></span>
</code></pre></div></div>
<p>The placeholder is 30px by 30px by default, but you can customize it.
It is displayed slightly blurred to give a hint to the user that the image is loading. The blur effect can be disabled with <code class="language-plaintext highlighter-rouge">[placeholderConfig]="{ blur: false }</code>.</p>
<p>Another new feature is the ability to use Netlify as a provider,
joining the existing Cloudflare, Cloudinary, ImageKit, and Imgix providers.</p>
<h2 id="angular-cli">Angular CLI</h2>
<h3 id="define-support">define support</h3>
<p>The CLI now supports a new option named <code class="language-plaintext highlighter-rouge">define</code> in the <code class="language-plaintext highlighter-rouge">build</code> and <code class="language-plaintext highlighter-rouge">serve</code> targets. It is similar to what the <a href="https://esbuild.github.io/api/#define">esbuild plugin of the same name</a> does: you can define constants that will be replaced with the specified value in TS and JS code, including in libraries.</p>
<p>You can for example define a BASE_URL that will be replaced with the value of <code class="language-plaintext highlighter-rouge">https://api.example.com</code>:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"build"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"builder"</span><span class="p">:</span><span class="w"> </span><span class="s2">"@angular-devkit/build-angular:browser"</span><span class="p">,</span><span class="w">
</span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"define"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"BASE_URL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"'https://api.example.com'"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span></code></pre></div></div>
<p>You can then use it in your code:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">http</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">BASE_URL</span><span class="p">}</span><span class="s2">/users`</span><span class="p">);</span>
</code></pre></div></div>
<p>TypeScript needs to know that this constant exists (as you don’t import it),
so you need to declare it in a <code class="language-plaintext highlighter-rouge">d.ts</code> file:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">declare</span> <span class="kd">const</span> <span class="nx">BASE_URL</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
</code></pre></div></div>
<p>This can be an alternative to the environment files, and it can be even more powerful as the constant is also replaced in libraries.</p>
<h3 id="bun-support">Bun support</h3>
<p>You can now use <a href="https://bun.sh/">Bun</a> as a package manager for your Angular CLI projects, in addition to npm, yarn, pnpm and cnpm.
It will be automatically detected, or can be forced with <code class="language-plaintext highlighter-rouge">--package-manager=bun</code>
when generating a new project.</p>
<h3 id="clearscreen-option">clearScreen option</h3>
<p>A new option is now supported in the application builder to clear the screen before rebuilding the application.</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"build"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"builder"</span><span class="p">:</span><span class="w"> </span><span class="s2">"@angular-devkit/build-angular:application"</span><span class="p">,</span><span class="w">
</span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"clearScreen"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="p">},</span><span class="w">
</span></code></pre></div></div>
<p>You then only see the output of the current build,
and not from the previous one.</p>
<h3 id="abbreviated-build-targets">Abbreviated build targets</h3>
<p>The <code class="language-plaintext highlighter-rouge">angular.json</code> file now supports abbreviated build targets.
For example, you currently have something like this in your project:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"serve"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"builder"</span><span class="p">:</span><span class="w"> </span><span class="s2">"@angular-devkit/build-angular:dev-server"</span><span class="p">,</span><span class="w">
</span><span class="nl">"configurations"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"development"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"buildTarget"</span><span class="p">:</span><span class="w"> </span><span class="s2">"app:build:development"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span></code></pre></div></div>
<p>This means that <code class="language-plaintext highlighter-rouge">ng serve</code> uses the <code class="language-plaintext highlighter-rouge">app:build:development</code> target to build the application.</p>
<p>This can now be abbreviated to:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"serve"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"builder"</span><span class="p">:</span><span class="w"> </span><span class="s2">"@angular-devkit/build-angular:dev-server"</span><span class="p">,</span><span class="w">
</span><span class="nl">"configurations"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"development"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"buildTarget"</span><span class="p">:</span><span class="w"> </span><span class="s2">"::development"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span></code></pre></div></div>
<h3 id="postcss-support">PostCSS support</h3>
<p>The application builder now supports PostCSS, a tool for transforming CSS with JavaScript plugins. You just have to add a <code class="language-plaintext highlighter-rouge">postcss.config.json</code> or <code class="language-plaintext highlighter-rouge">.postcssrc.json</code> file to your project and the CLI will pick it up.</p>
<h3 id="json-build-logs">JSON build logs</h3>
<p>The CLI now supports a new option to output the build logs in JSON format. This can be useful to integrate the build logs in other tools.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NG_BUILD_LOGS_JSON=1 ng build
</code></pre></div></div>
<h2 id="summary">Summary</h2>
<p>That’s all for this release, stay tuned!</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Angular 17.1?2024-01-17T00:00:00+00:00https://blog.ninja-squad.com/2024/01/17/what-is-new-angular-17.1<p>Angular 17.1.0 is here!</p>
<p style="text-align: center;">
<a href="https://github.com/angular/angular/releases/tag/17.1.0">
<img class="rounded img-fluid" style="max-width: 60%" src="/assets/images/angular_gradient.png" alt="Angular logo" />
</a>
</p>
<p>This is a minor release with some nice features: let’s dive in!</p>
<h2 id="typescript-53-support">TypeScript 5.3 support</h2>
<p>Angular v17.1 now supports TypeScript 5.3. This means that you can use the latest version of TypeScript in your Angular applications. You can check out the <a href="https://devblogs.microsoft.com/typescript/announcing-typescript-5-3/">TypeScript 5.3 release notes</a> to learn more about the new features.</p>
<h2 id="inputs-as-signals">Inputs as signals</h2>
<p>In Angular v17.1, a new feature was added to allow the use of inputs as signals.
This is a first step towards signal-based components.
The framework team added an <code class="language-plaintext highlighter-rouge">input()</code> function in <code class="language-plaintext highlighter-rouge">@angular/core</code>.</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@</span><span class="nd">Component</span><span class="p">({</span>
<span class="na">standalone</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">selector</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ns-pony</span><span class="dl">'</span><span class="p">,</span>
<span class="na">template</span><span class="p">:</span> <span class="s2">`
@if (ponyModel(); as ponyModel) {
<figure>
<img [src]="imageUrl()" [alt]="ponyModel.name" />
<figcaption></figcaption>
</figure>
}
`</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">PonyComponent</span> <span class="p">{</span>
<span class="nx">ponyModel</span> <span class="o">=</span> <span class="nx">input</span><span class="o"><</span><span class="nx">PonyModel</span><span class="o">></span><span class="p">();</span>
<span class="nx">imageUrl</span> <span class="o">=</span> <span class="nx">computed</span><span class="p">(()</span> <span class="o">=></span> <span class="s2">`assets/pony-</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">ponyModel</span><span class="p">()</span><span class="o">!</span><span class="p">.</span><span class="nx">color</span><span class="p">}</span><span class="s2">.gif`</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>As you can see in the example above, the <code class="language-plaintext highlighter-rouge">input()</code> function returns a signal,
that can be used in the template or in computed values
(which would be the modern equivalent of <code class="language-plaintext highlighter-rouge">ngOnChanges</code>).</p>
<p>It can be undefined though,
hence the <code class="language-plaintext highlighter-rouge">@if</code> in the template and the <code class="language-plaintext highlighter-rouge">!</code> in the computed value.</p>
<p>If an input is mandatory,
you can use the <code class="language-plaintext highlighter-rouge">input.required()</code> version of the function:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@</span><span class="nd">Component</span><span class="p">({</span>
<span class="na">standalone</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">selector</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ns-pony</span><span class="dl">'</span><span class="p">,</span>
<span class="na">template</span><span class="p">:</span> <span class="s2">`
<figure>
<img [src]="imageUrl()" [alt]="ponyModel().name" />
<figcaption>{{ ponyModel().name }}</figcaption>
</figure>
`</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">PonyComponent</span> <span class="p">{</span>
<span class="nx">ponyModel</span> <span class="o">=</span> <span class="nx">input</span><span class="p">.</span><span class="nx">required</span><span class="o"><</span><span class="nx">PonyModel</span><span class="o">></span><span class="p">();</span>
<span class="nx">imageUrl</span> <span class="o">=</span> <span class="nx">computed</span><span class="p">(()</span> <span class="o">=></span> <span class="s2">`assets/pony-</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">ponyModel</span><span class="p">().</span><span class="nx">color</span><span class="p">}</span><span class="s2">.gif`</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>You can also provide a default value, an alias, and a transformer function.
Here the <code class="language-plaintext highlighter-rouge">ponySpeed</code> field is aliased as <code class="language-plaintext highlighter-rouge">speed</code>,
provided with a default value, and transformed to a number (even if the input is a string):</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">ponySpeed</span> <span class="o">=</span> <span class="nx">input</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="p">{</span>
<span class="na">alias</span><span class="p">:</span> <span class="dl">'</span><span class="s1">speed</span><span class="dl">'</span><span class="p">,</span>
<span class="na">transform</span><span class="p">:</span> <span class="nx">numberAttribute</span>
<span class="p">});</span>
</code></pre></div></div>
<p>You can also use the signal as the source of an observable,
to trigger an action when the input changes.
For example, to fetch data from a server:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">PonyComponent</span> <span class="p">{</span>
<span class="nx">ponyService</span> <span class="o">=</span> <span class="nx">inject</span><span class="p">(</span><span class="nx">PonyService</span><span class="p">);</span>
<span class="nx">ponyId</span> <span class="o">=</span> <span class="nx">input</span><span class="p">.</span><span class="nx">required</span><span class="o"><</span><span class="kr">number</span><span class="o">></span><span class="p">();</span>
<span class="c1">// entity fetched from the server every time the ponyId changes</span>
<span class="nx">ponyModel</span> <span class="o">=</span> <span class="nx">toSignal</span><span class="p">(</span><span class="nx">toObservable</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">ponyId</span><span class="p">)</span>
<span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">switchMap</span><span class="p">(</span><span class="nx">id</span> <span class="o">=></span> <span class="k">this</span><span class="p">.</span><span class="nx">ponyService</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">id</span><span class="p">))));</span>
<span class="nx">imageUrl</span> <span class="o">=</span> <span class="nx">computed</span><span class="p">(()</span> <span class="o">=></span> <span class="s2">`assets/pony-</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">ponyModel</span><span class="p">()</span><span class="o">!</span><span class="p">.</span><span class="nx">color</span><span class="p">}</span><span class="s2">.gif`</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>When coupled with the recent addition to the router called “Component Input Binding”,
where the router binds the route parameters to the inputs of a component,
it can lead to an interesting pattern.
Note that the input <code class="language-plaintext highlighter-rouge">transform</code> is necessary as the router parameters are strings:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">ponyId</span> <span class="o">=</span> <span class="nx">input</span><span class="p">.</span><span class="nx">required</span><span class="o"><</span><span class="kr">number</span><span class="p">,</span> <span class="kr">string</span><span class="o">></span><span class="p">({</span>
<span class="na">transform</span><span class="p">:</span> <span class="nx">numberAttribute</span>
<span class="p">});</span>
</code></pre></div></div>
<p>This behavior is enabled via <code class="language-plaintext highlighter-rouge">withComponentInputBinding</code> in the router configuration:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">provideRouter</span><span class="p">(</span>
<span class="p">[</span>
<span class="p">{</span>
<span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">pony/:ponyId</span><span class="dl">'</span><span class="p">,</span>
<span class="na">component</span><span class="p">:</span> <span class="nx">PonyComponent</span>
<span class="p">}</span>
<span class="p">],</span>
<span class="nx">withComponentInputBinding</span><span class="p">()</span>
<span class="p">),</span>
</code></pre></div></div>
<h2 id="zoneless-change-detection">Zoneless change detection</h2>
<p>The framework is making progress towards zoneless change detection.
A new private API called <code class="language-plaintext highlighter-rouge">ɵprovideZonelessChangeDetection</code> was added to <code class="language-plaintext highlighter-rouge">@angular/core</code>.
When you add this provider to your application,
the framework no longer relies on Zone.js for change detection (and you can remove it from the application).</p>
<p>So how does it work?
Every time an event is fired, an input is set, an output emits a value, an <code class="language-plaintext highlighter-rouge">async</code> pipe receives a value, a signal is set, <code class="language-plaintext highlighter-rouge">markForCheck</code> is called, etc.,
the framework notifies an internal scheduler that something happened.
It then runs the change detection on the component marked as dirty.
But this doesn’t catch what Zone.js usually does:
a <code class="language-plaintext highlighter-rouge">setTimeout</code>, a <code class="language-plaintext highlighter-rouge">setInterval</code>, a <code class="language-plaintext highlighter-rouge">Promise</code>, an <code class="language-plaintext highlighter-rouge">XMLHttpRequest</code>, etc.</p>
<p>But that shouldn’t be a problem because the idea is that when a <code class="language-plaintext highlighter-rouge">setTimeout</code>, <code class="language-plaintext highlighter-rouge">setInterval</code> or <code class="language-plaintext highlighter-rouge">XMLHttpRequest</code> callback is triggered, and you want it to update the state of the application, you should do it by modifying a signal, which will in turn trigger change detection.</p>
<p>This is far from being complete, as the “private API” part suggests.
However, it indicates that the framework is making progress towards zoneless change detection.</p>
<h2 id="router">Router</h2>
<p>The router now has an <code class="language-plaintext highlighter-rouge">info</code> option in the <code class="language-plaintext highlighter-rouge">NavigationExtras</code>
that can be used to store information about the navigation.
Unlike the <code class="language-plaintext highlighter-rouge">state</code> option,
this information is not persisted in the session history.
The <code class="language-plaintext highlighter-rouge">RouterLink</code> directive now supports this option as well:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><a</span> <span class="na">[routerLink]=</span><span class="s">"['/pony', pony.id]"</span> <span class="na">[info]=</span><span class="s">"{ ponyName: pony.name }"</span><span class="nt">></a></span>
</code></pre></div></div>
<h2 id="control-flow-migration">Control flow migration</h2>
<p>The control flow migration is still experimental but has been improved
with a ton of bug fixes and new features.
It now removes the useless imports from your component imports after the migration.
It also now has a new option <code class="language-plaintext highlighter-rouge">format</code> to reformat your templates after the migration.
The option is <code class="language-plaintext highlighter-rouge">true</code> by default, but can be turned off:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng g @angular/core:control-flow <span class="nt">--path</span> src/app/app.component.html <span class="nt">--format</span><span class="o">=</span><span class="nb">false</span>
</code></pre></div></div>
<h2 id="infinite_change_detection">INFINITE_CHANGE_DETECTION</h2>
<p>This is not a new feature, but this bug fix is worth mentioning.
Angular v17.1 fixes a bug for transplanted views, but this will also be useful for signals.</p>
<p>The framework now runs change detection while there are still dirty
views to be refreshed in the tree.
If too many loops are detected, the framework will throw an error: <code class="language-plaintext highlighter-rouge">INFINITE_CHANGE_DETECTION</code>.</p>
<p>This will remind the oldest Angular developers of the good old days of AngularJS,
when we had to be careful with infinite digest loops 👴.</p>
<p>Angular v17.1 will throw this error if you have 100 loops in a row at the moment.</p>
<h2 id="angular-cli">Angular CLI</h2>
<h3 id="vite-v5">Vite v5</h3>
<p>The Angular CLI v17.1 now uses Vite v5 under the hood.
Vite v5 was recently released,
you can read more about it in the <a href="https://vitejs.dev/blog/announcing-vite5">official blog post</a>.</p>
<h3 id="application-builder-migration">Application builder migration</h3>
<p>If you haven’t migrated to the new application builder yet,
there is now a migration schematic to help you with that:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng update @angular/cli <span class="nt">--name</span><span class="o">=</span>use-application-builder
</code></pre></div></div>
<h3 id="keyboard-shortcuts-in-dev-server">Keyboard shortcuts in dev server</h3>
<p>After running <code class="language-plaintext highlighter-rouge">ng serve</code>, you can now see in the terminal the following line:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Watch mode enabled. Watching <span class="k">for </span>file changes...
➜ Local: http://localhost:4200/
➜ press h + enter to show <span class="nb">help</span>
</code></pre></div></div>
<p>If you press ‘h + enter’, you will see the list of available keyboard shortcuts:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Shortcuts
press r + enter to force reload browser
press u + enter to show server url
press o + enter to open <span class="k">in </span>browser
press c + enter to clear console
press q + enter to quit
</code></pre></div></div>
<p>Quite cool!</p>
<h3 id="running-tests-with-web-test-runner">Running tests with Web Test Runner</h3>
<p>An experimental builder is now available to run tests with
<a href="https://modern-web.dev/docs/test-runner/overview/">Web Test Runner</a>.
It is <em>very</em> early stage, but you can try it out by replacing the <code class="language-plaintext highlighter-rouge">karma</code> builder with <code class="language-plaintext highlighter-rouge">web-test-runner</code> in the <code class="language-plaintext highlighter-rouge">angular.json</code> file:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"test"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"builder"</span><span class="p">:</span><span class="w"> </span><span class="s2">"@angular-devkit/build-angular:web-test-runner"</span><span class="p">,</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>You then need to install the <code class="language-plaintext highlighter-rouge">@web/test-runner</code> package,
and here you go!
Running <code class="language-plaintext highlighter-rouge">ng test</code> will now use Web Test Runner instead of Karma
(and bundle the files with the <code class="language-plaintext highlighter-rouge">application</code> builder, which uses esbuild,
and not Webpack as the current <code class="language-plaintext highlighter-rouge">karma</code> builder does).</p>
<p>A lot of options aren’t available yet,
so you can’t change the browser for example (it only runs in Chrome for now),
or define reporters, or use any kind of configuration.</p>
<p>In the future, we will be able to define a configuration file for Web Test Runner,
use other browsers (WTR supports using Playwright to download and run tests in other browsers), etc.</p>
<p>This builder will probably be the default in the future,
as Karma is now deprecated.</p>
<h3 id="loader-option">loader option</h3>
<p>The <code class="language-plaintext highlighter-rouge">application</code> builder gained a new <code class="language-plaintext highlighter-rouge">loader</code> option.
It allows defining the type of loader to use for a specified file extension.
The file matching the extension can then be used
within the application code via an import.</p>
<p>The available loaders that can be used are:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">text</code> which treats the content as a string</li>
<li><code class="language-plaintext highlighter-rouge">binary</code> which treats the content as a Uint8Array</li>
<li><code class="language-plaintext highlighter-rouge">file</code> which emits the file and provides the runtime location of the file</li>
<li><code class="language-plaintext highlighter-rouge">empty</code> which considers the content to be empty and will not include it in bundles</li>
</ul>
<p>For example, to inline the content of SVG files into the bundled application,
you can use the following configuration in the <code class="language-plaintext highlighter-rouge">angular.json</code> file:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>loader: {
".svg": "text"
}
</code></pre></div></div>
<p>Then an SVG file can be imported in your code with:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import content from './logo.svg';
</code></pre></div></div>
<p>TypeScript needs to be aware of the module type for the import to prevent type-checking
errors during the build, so you’ll need to add a type definition for the SVG file:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>declare module "*.svg" {
const content: string;
export default content;
}
</code></pre></div></div>
<h3 id="output-location">Output location</h3>
<p>It is now possible to customize the output location of the build artifacts:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"outputPath"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"base"</span><span class="p">:</span><span class="w"> </span><span class="s2">"dist/my-app"</span><span class="p">,</span><span class="w">
</span><span class="nl">"browser"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"server"</span><span class="p">:</span><span class="w"> </span><span class="s2">"node-server"</span><span class="p">,</span><span class="w">
</span><span class="nl">"media"</span><span class="p">:</span><span class="w"> </span><span class="s2">"resources"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<h3 id="retain-special-css-comments">Retain special CSS comments</h3>
<p>By default, the CLI removes comments from CSS files during the build.
If you want to retain them because you use some tools that rely on them,
you can now set the <code class="language-plaintext highlighter-rouge">removeSpecialComments</code> option to <code class="language-plaintext highlighter-rouge">false</code> in the <code class="language-plaintext highlighter-rouge">optimization</code> section of your <code class="language-plaintext highlighter-rouge">angular.json</code> file:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"optimization"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"styles"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"removeSpecialComments"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<h3 id="allowed-commonjs-dependencies">Allowed CommonJS dependencies</h3>
<p>You can now specify <code class="language-plaintext highlighter-rouge">*</code> as a package name in the <code class="language-plaintext highlighter-rouge">allowedCommonJsDependencies</code> option to allow all packages in your build:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"allowedCommonJsDependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"*"</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>
<h3 id="no-browsers-in-tests">–no-browsers in tests</h3>
<p>You can now use the <code class="language-plaintext highlighter-rouge">--no-browsers</code> option when running tests with the CLI.
This will prevent the browser from opening when running tests,
which can be useful if you are inside a container for example.
This was already possible by setting the <code class="language-plaintext highlighter-rouge">browsers</code> option to <code class="language-plaintext highlighter-rouge">[]</code> in the <code class="language-plaintext highlighter-rouge">karma.conf.js</code> file, but not from the CLI command.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng <span class="nb">test</span> <span class="nt">--no-browsers</span>
</code></pre></div></div>
<h2 id="summary">Summary</h2>
<p>That’s all for this release, stay tuned!</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Vue 3.4?2023-12-29T00:00:00+00:00https://blog.ninja-squad.com/2023/12/29/what-is-new-vue-3.4<p><a href="https://blog.vuejs.org/posts/vue-3-4">Vue 3.4.0</a> is here!</p>
<p style="text-align: center;">
<a href="https://github.com/vuejs/core/blob/main/CHANGELOG.md#TODO">
<img class="rounded img-fluid" style="max-width: 100%" src="/assets/images/vue.png" alt="Vue logo" />
</a>
</p>
<p>The last minor release was v3.3.0 in May.
Since then, we have seen a few patch releases,
some coming with new features.</p>
<p>Let’s see what we have in this release!</p>
<h2 id="v-bind-shorthand-syntax">v-bind shorthand syntax</h2>
<p>It is now possible to use a shorthand syntax
for <code class="language-plaintext highlighter-rouge">v-bind</code> when the key and value have the same name!</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- before --></span>
<span class="nt"><div</span> <span class="na">v-bind:id=</span><span class="s">"id"</span><span class="nt">></div></span>
<span class="nt"><--</span> <span class="na">or</span> <span class="na">--</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">:id=</span><span class="s">"id"</span><span class="nt">></div></span>
<span class="c"><!-- after --></span>
<span class="nt"><div</span> <span class="na">v-bind:id</span><span class="nt">></div></span>
<span class="nt"><--</span> <span class="na">or</span> <span class="na">--</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">:id</span><span class="nt">></div></span>
</code></pre></div></div>
<h2 id="performances-improvements-for-the-reactivity-system">Performances improvements for the reactivity system</h2>
<p>Johnson Chu, the author of <a href="https://github.com/vuejs/language-tools">Volar</a>,
has done massive work to improve the performance of the reactivity system.</p>
<p>Let’s consider a scenario where you have a <code class="language-plaintext highlighter-rouge">computed</code> A that depends on a <code class="language-plaintext highlighter-rouge">computed</code> B.
In Vue v3.3, if B is re-evaluated, A is also re-evaluated, even if B has the same value as before.
In Vue v3.4, A is not re-evaluated if B has the same value as before.
This is also true for <code class="language-plaintext highlighter-rouge">watch</code> functions.</p>
<p>Other improvements have been made for Arrays mutations,
for watchers that depend on multiple computed values,
and more (as you can see in the <a href="https://github.com/vuejs/core/pull/5912">PR description</a>).</p>
<p>This should avoid a whole lot of unnecessary re-renders! 🚀
(and hopefully, don’t introduce any regression 🤞).</p>
<h2 id="computed-previous-value"><code class="language-plaintext highlighter-rouge">computed</code> previous value</h2>
<p>You can now get the previous value in a <code class="language-plaintext highlighter-rouge">computed</code>,
as the first parameter of the getter function.</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">count</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">double</span> <span class="o">=</span> <span class="nx">computed</span><span class="p">((</span><span class="nx">prev</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">prev</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">count</span><span class="p">.</span><span class="nx">value</span> <span class="o">*</span> <span class="mi">2</span>
<span class="p">});</span>
<span class="nx">count</span><span class="p">.</span><span class="nx">value</span><span class="o">++</span><span class="p">;</span>
<span class="c1">// logs 0</span>
</code></pre></div></div>
<p>This can be useful if you want to manually compare object values.
(<code class="language-plaintext highlighter-rouge">computed</code> internally uses <code class="language-plaintext highlighter-rouge">Object.is</code> to compare the previous and current values,
which is not always what you want, see the <a href="https://github.com/vuejs/core/pull/9497">PR description</a>).
This is especially useful with the new reactivity system improvements.
In v3.4, a computed property will only trigger effects when its computed value has changed from the previous one.
But in the case of a computed that return new objects, Vue thinks that the previous and current values are different.
If you want to avoid triggering effects in that case, you can compare the previous and current values manually.</p>
<h2 id="performances-improvements-for-the-compiler">Performances improvements for the compiler</h2>
<p>The Vue compiler has been improved to be faster.
Evan rewrote the parser entirely, to avoid using regexes.
The code generation has also been improved.
They are now nearly 2 times faster!</p>
<p>This should not have a huge impact on your build times,
as the Vue compiler is not the only step in the build process
(you usually have the TypeScript compiler, the CSS preprocessor, etc.).</p>
<h2 id="support-for-import-attributes">Support for import attributes</h2>
<p>It is now possible to use import attributes in SFC (both in JS and TS):</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">json</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./foo.json</span><span class="dl">"</span> <span class="kd">with</span> <span class="p">{</span> <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">json</span><span class="dl">"</span> <span class="p">}</span>
</code></pre></div></div>
<p>The support for <code class="language-plaintext highlighter-rouge">using</code> has also been added (new feature for explicit resource management in JS, see the proposal <a href="https://github.com/tc39/proposal-explicit-resource-management">here</a>).</p>
<h2 id="watch-once">watch <code class="language-plaintext highlighter-rouge">once</code></h2>
<p>The <code class="language-plaintext highlighter-rouge">watch</code> function gained a new option called <code class="language-plaintext highlighter-rouge">once</code>.
When set to <code class="language-plaintext highlighter-rouge">true</code>, the watcher is removed after the first call.</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">watch</span><span class="p">(</span><span class="dl">'</span><span class="s1">foo</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">foo changed</span><span class="dl">'</span><span class="p">);</span>
<span class="p">},</span> <span class="p">{</span> <span class="na">once</span><span class="p">:</span> <span class="kc">true</span> <span class="p">});</span>
</code></pre></div></div>
<p>It was previously possible to achieve the same result by using the returned <code class="language-plaintext highlighter-rouge">stop</code> function:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">stop</span> <span class="o">=</span> <span class="nx">watch</span><span class="p">(</span><span class="dl">'</span><span class="s1">foo</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">foo changed</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">stop</span><span class="p">();</span>
<span class="p">});</span>
</code></pre></div></div>
<h2 id="props-validation">Props validation</h2>
<p>As you probably know, Vue provides a mechanism to validate props.</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">defineProps</span><span class="p">({</span>
<span class="na">min</span><span class="p">:</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="nb">Number</span><span class="p">,</span>
<span class="na">required</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">validator</span><span class="p">:</span> <span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="o">=></span> <span class="nx">value</span> <span class="o">>=</span> <span class="mi">0</span><span class="p">,</span>
<span class="p">},</span>
<span class="na">max</span><span class="p">:</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="nb">Number</span><span class="p">,</span>
<span class="na">required</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">validator</span><span class="p">:</span> <span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="o">=></span> <span class="nx">value</span> <span class="o">>=</span> <span class="mi">0</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>
<p>In the above example, the <code class="language-plaintext highlighter-rouge">min</code> and <code class="language-plaintext highlighter-rouge">max</code> props must be positive numbers.
In Vue v3.4, the <code class="language-plaintext highlighter-rouge">validator</code> function is now called with a second argument
containing all the props, allowing to validate the value against other props.</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">defineProps</span><span class="p">({</span>
<span class="na">min</span><span class="p">:</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="nb">Number</span><span class="p">,</span>
<span class="na">required</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">validator</span><span class="p">:</span> <span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="o">=></span> <span class="nx">value</span> <span class="o">>=</span> <span class="mi">0</span><span class="p">,</span>
<span class="p">},</span>
<span class="na">max</span><span class="p">:</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="nb">Number</span><span class="p">,</span>
<span class="na">required</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">validator</span><span class="p">:</span> <span class="p">(</span><span class="nx">value</span><span class="p">,</span> <span class="nx">props</span><span class="p">)</span> <span class="o">=></span> <span class="nx">value</span> <span class="o">>=</span> <span class="nx">props</span><span class="p">.</span><span class="nx">min</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>
<p>Then if you try to use the component with <code class="language-plaintext highlighter-rouge">max</code> being lower than <code class="language-plaintext highlighter-rouge">min</code>,
you will get a warning.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Invalid prop: custom validator check failed for prop "max".
</code></pre></div></div>
<h2 id="ssr-hydration-mismatch-warnings">SSR hydration mismatch warnings</h2>
<p>When using SSR, the client-side hydration will now warn you with a message
that includes the mismatching element.
It was sometimes hard to find the mismatching element in the DOM,
as the warning was in v3.3:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Vue warn]: Hydration completed but contains mismatches.
</code></pre></div></div>
<p>In v3.4, the warning now contains the actual element
(not only its tag name but the actual DOM element, so you can click on it),
allowing us to see where it is in the DOM and why it failed.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Vue warn]: Hydration text content mismatch in <h2>:
- Server rendered: Hello server
- Client rendered: Hello client
</code></pre></div></div>
<p>Vue now also warns you if you have a mismatch in classes, styles, or attributes!</p>
<p>You can enable this feature in production as well by using a feature flag
in your Vite config called <code class="language-plaintext highlighter-rouge">__VUE_PROD_HYDRATION_MISMATCH_DETAILS__</code>:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="k">default</span> <span class="nx">defineConfig</span><span class="p">({</span>
<span class="na">plugins</span><span class="p">:</span> <span class="p">[</span><span class="nx">vue</span><span class="p">()],</span>
<span class="na">define</span><span class="p">:</span> <span class="p">{</span>
<span class="na">__VUE_PROD_HYDRATION_MISMATCH_DETAILS__</span><span class="p">:</span> <span class="kc">true</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>
<h2 id="mathml-support">MathML support</h2>
<p>It is now possible to write templates using MathML
(in addition to HTML and SVG)!</p>
<p>The template below displays a beautiful <code class="language-plaintext highlighter-rouge">x²</code>:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><template></span>
<span class="nt"><math></span>
<span class="nt"><mrow><msup><mi></span>x<span class="nt"></mi><mn></span>2<span class="nt"></mn></msup></mrow></span>
<span class="nt"></math></span>
<span class="nt"></template></span>
</code></pre></div></div>
<h2 id="definemodel">defineModel</h2>
<p>The <code class="language-plaintext highlighter-rouge">defineModel</code> function was introduced as an experimental API in v3.3,
is now a stable API.
It is now the recommended way to define custom v-models.</p>
<p>Compared to what we explained in our <a href="/2023/05/15/what-is-new-vue-3.3">previous blog post</a>, the <code class="language-plaintext highlighter-rouge">local</code> option has been removed (see <a href="https://github.com/vuejs/rfcs/discussions/503#discussioncomment-7566278">this discussion</a> if you want to know why).
The model now automatically adapts based on whether the parent provides a <code class="language-plaintext highlighter-rouge">v-model</code> or not.</p>
<p>Another change it that it is now possible to handle modifiers.
For example, if you want to handle the <code class="language-plaintext highlighter-rouge">.number</code> modifier, you can do:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="p">[</span><span class="nx">count</span><span class="p">,</span> <span class="nx">countModifiers</span><span class="p">]</span> <span class="o">=</span> <span class="nx">defineModel</span><span class="p">({</span>
<span class="kd">set</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">countModifiers</span><span class="p">?.</span><span class="kr">number</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">Number</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">value</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">countModifiers</span><span class="p">?.</span><span class="kr">number</span><span class="p">);</span> <span class="c1">// true if the .number modifier is used</span>
</code></pre></div></div>
<p>You can type the modifiers by using <code class="language-plaintext highlighter-rouge">defineModel<number, 'number'>({ ... })</code>.
The first type parameter is the type of the model value,
and the second one is the type of the modifiers (which can be a union type if you want to handle several ones).</p>
<p>You can play with this <a href="https://play.vuejs.org/#__DEV__eNqNU02P0zAQ/StWLu1Kjc2qnLrZsoD2ABILAm6EQ5pMWhfHtvyRrRTlvzN20pJ2KeLmmXmeefPxuuSt1rT1kKySzJaGa0csOK/XueSNVsaRjhioSU9qoxoyQ+jsFHqvGj36KQtGyDS7y2UuSyWtI2qzJ/fh/7wjpfLSrcgt6W9ymbGhGJZBw0GjReEALUKy3e266+LXvs8YWtEba7VpoyoQVPpmA+Y+TxBFY+I8IQyBGZvkShbJQDRtCk33VknssgvZ8jFg82RFoif4kHyw82TnnLYrxspK4jcsyFtDJTgmdcMeEMYMluQNpJVqHpb0NX2VmpIuWcWtm8Yo2CbdGPVswWCmPFlMajF0tmBSA7ICA+Z/a198e1H/Iv6CQ6DQ57LH8TiLe6r59mI4Jc6aCzCfteO4x7MhFUKo54/R54yHU0PlDspff/Hv7WFo7IuByGwyBFeYLeDqQvjx2xMc8H0K4qa9GBdyJfgVrBI+cBxg77yskPYEF9l+iLvmcvvdPh4cSHtsKhCN04j4uJRwZtda/0N3SZeTKR4vf6IhUcgtnmc4sJOeBkn8iOe6GOTwSVW85mDsT5RJBTWXgC4Q2XDgCzIbHrP1PLLAVPO2EB5ujqx4Tebnqd6M6jhBCArQeSPJU/SPCVClIYQtTBAxFCOhtYAJpJUAKtT2Wp07wlgcZSDjdkDGAMElRSjhlngL1b9lX/E2PvDJpfbuKHYc46jwAYdHHoDnSu9/AxtCnvc=">demo</a> to see how it works.</p>
<h2 id="typescript-improvements">TypeScript improvements</h2>
<p>An effort has been made to sanitize the types (which will be helpful for all libraries in the ecosystem).</p>
<p>A notable improvement for developers is that <code class="language-plaintext highlighter-rouge">app.directive</code>,
used to register global directives, can now be properly typed:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">app</span><span class="p">.</span><span class="nx">directive</span><span class="o"><</span><span class="nx">HTMLElement</span><span class="p">,</span> <span class="kr">string</span><span class="o">></span><span class="p">(</span><span class="dl">'</span><span class="s1">custom</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="nx">mounted</span><span class="p">(</span><span class="nx">el</span><span class="p">,</span> <span class="nx">binding</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// el is correctly typed as HTMLElement</span>
<span class="c1">// binding is correctly typed as string</span>
<span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>
<h2 id="deprecated-features-removed">Deprecated features removed</h2>
<p>The reactivity transform experiment has been removed.
It had been deprecated in v3.3.0 (see our <a href="/2023/05/15/what-is-new-vue-3.3">previous blog post</a>).</p>
<p>Vnode hook events written like <code class="language-plaintext highlighter-rouge">vnodeMounted</code> have been deprecated in v3.3
(see our <a href="/2023/05/15/what-is-new-vue-3.3">previous blog post</a>)
and they are now no longer supported.
You should use the <code class="language-plaintext highlighter-rouge">@vue:</code> syntax instead, like <code class="language-plaintext highlighter-rouge">@vue:mounted</code>.</p>
<p>The <code class="language-plaintext highlighter-rouge">v-is</code> directive has also been removed.</p>
<h2 id="news-from-the-ecosystem">News from the ecosystem</h2>
<h3 id="vue-2-end-of-life">Vue 2 End of Life</h3>
<p>Vue 2 has reached its end of life, and Evan wrote a blog post about it:</p>
<p>👉 https://blog.vuejs.org/posts/vue-2-eol</p>
<h3 id="vapor-mode">Vapor mode</h3>
<p>Vapor (<code class="language-plaintext highlighter-rouge">@vue/vapor</code>) is making progress with a <a href="https://github.com/vuejs/core-vapor">new repository</a>.</p>
<p>For now, it introduces two work-in-progress packages: a new compiler and a new runtime.
They only support the most basic features at the moment,
and aren’t easily usable.
There is a playground in the repository if you want to try it out.</p>
<p>The biggest difference with Vue 3 is that Vapor generates a rendering function that does not rely on virtual DOM.</p>
<p>For example, the following component:</p>
<div class="language-vue highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><</span><span class="k">script</span> <span class="na">setup</span> <span class="na">lang=</span><span class="s">"ts"</span><span class="nt">></span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ref</span><span class="p">,</span> <span class="nx">computed</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">vue/vapor</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">count</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">double</span> <span class="o">=</span> <span class="nx">computed</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">count</span><span class="p">.</span><span class="nx">value</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">inc</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="nx">count</span><span class="p">.</span><span class="nx">value</span><span class="o">++</span>
<span class="nt"></</span><span class="k">script</span><span class="nt">></span>
<span class="nt"><</span><span class="k">template</span><span class="nt">></span>
<span class="nt"><div></span>
<span class="nt"><h1</span> <span class="na">class=</span><span class="s">"red"</span><span class="nt">></span>Counter<span class="nt"></h1></span>
<span class="nt"><div></span>8 * 2 = <span class="nt"></div></span>
<span class="nt"><button</span> <span class="err">@</span><span class="na">click=</span><span class="s">"inc"</span><span class="nt">></span>inc<span class="nt"></button></span>
<span class="nt"></div></span>
<span class="nt"></</span><span class="k">template</span><span class="nt">></span>
</code></pre></div></div>
<p>generates the following render function:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">_sfc_render</span><span class="p">(</span><span class="nx">_ctx</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">t0</span> <span class="o">=</span> <span class="nx">_template</span><span class="p">(</span><span class="dl">'</span><span class="s1"><div><h1 class="red">Counter</h1><div> * 2 = </div><button>inc</button></div></span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">n0</span> <span class="o">=</span> <span class="nx">t0</span><span class="p">();</span>
<span class="kd">const</span> <span class="p">{</span> <span class="mi">0</span><span class="p">:</span> <span class="p">[,</span> <span class="p">{</span> <span class="mi">1</span><span class="p">:</span> <span class="p">[</span><span class="nx">n3</span><span class="p">],</span> <span class="mi">2</span><span class="p">:</span> <span class="p">[</span><span class="nx">n4</span><span class="p">]</span> <span class="p">}]</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">_children</span><span class="p">(</span><span class="nx">n0</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">n1</span> <span class="o">=</span> <span class="nx">_createTextNode</span><span class="p">(</span><span class="nx">_ctx</span><span class="p">.</span><span class="nx">count</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">n2</span> <span class="o">=</span> <span class="nx">_createTextNode</span><span class="p">(</span><span class="nx">_ctx</span><span class="p">.</span><span class="nx">double</span><span class="p">);</span>
<span class="nx">_prepend</span><span class="p">(</span><span class="nx">n3</span><span class="p">,</span> <span class="nx">n1</span><span class="p">);</span>
<span class="nx">_append</span><span class="p">(</span><span class="nx">n3</span><span class="p">,</span> <span class="nx">n2</span><span class="p">);</span>
<span class="nx">_on</span><span class="p">(</span><span class="nx">n4</span><span class="p">,</span> <span class="dl">"</span><span class="s2">click</span><span class="dl">"</span><span class="p">,</span> <span class="p">(...</span><span class="nx">args</span><span class="p">)</span> <span class="o">=></span> <span class="nx">_ctx</span><span class="p">.</span><span class="nx">inc</span> <span class="o">&&</span> <span class="nx">_ctx</span><span class="p">.</span><span class="nx">inc</span><span class="p">(...</span><span class="nx">args</span><span class="p">));</span>
<span class="nx">_watchEffect</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">_setText</span><span class="p">(</span><span class="nx">n1</span><span class="p">,</span> <span class="k">void</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">_ctx</span><span class="p">.</span><span class="nx">count</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">_watchEffect</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">_setText</span><span class="p">(</span><span class="nx">n2</span><span class="p">,</span> <span class="k">void</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">_ctx</span><span class="p">.</span><span class="nx">double</span><span class="p">);</span>
<span class="p">});</span>
<span class="k">return</span> <span class="nx">n0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>As you can see the render function is using a different strategy than Vue 3:
it creates the static elements, then it creates the dynamic elements,
and finally it updates the dynamic elements when needed using <code class="language-plaintext highlighter-rouge">watchEffect</code>.</p>
<p>You can check in the project’s README the features that are supported and the ones that are not.</p>
<h3 id="vue-test-utils">Vue Test Utils</h3>
<p>VTU should now have better type-checking support for TypeScript users.
For example <code class="language-plaintext highlighter-rouge">wrapper.setProps({ foo: 'bar' })</code> will now correctly error
if the component has no <code class="language-plaintext highlighter-rouge">foo</code> prop.</p>
<h3 id="create-vue">create-vue</h3>
<p><code class="language-plaintext highlighter-rouge">create-vue</code> now generates projects using Vite v5,
which was <a href="https://vitejs.dev/blog/announcing-vite5">recently released</a>.</p>
<h3 id="nuxt">Nuxt</h3>
<p>Nuxt v3.9 is out as well, with the support of Vue 3.4.
It brings a lot of new features and experiments:
you can read more in the <a href="https://nuxt.com/blog/v3-9">official blog post</a>.</p>
<p>That’s all for this release. Stay tuned for the next one!</p>
<p>Our <a href="https://books.ninja-squad.com/vue">ebook</a>, <a href="https://vue-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/vue">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Angular 17?2023-11-09T00:00:00+00:00https://blog.ninja-squad.com/2023/11/09/what-is-new-angular-17.0<p>Angular v17 is here!</p>
<p style="text-align: center;">
<a href="https://github.com/angular/angular/releases/tag/17.0.0">
<img class="rounded img-fluid" style="max-width: 60%" src="/assets/images/angular_gradient.png" alt="Angular logo" />
</a>
</p>
<p>For French-speaking people, I talked about the release on the <a href="https://www.youtube.com/live/YBty95aSP6c?si=E_n8L59HC5P1NUxe">Angular Devs France YouTube channel</a>.</p>
<p>This is a major release packed with features: let’s dive in!</p>
<h2 id="angulardev">angular.dev</h2>
<p>The Angular team has been cranking it communication-wise lately,
with a <a href="https://www.youtube.com/watch?v=Wq6GpTZ7AX0">live event</a>
to unveil the new features of Angular v17,
and a new website called <a href="https://angular.dev">angular.dev</a>,
which will be the future official website.
It features the same documentation but with a new interactive tutorial,
and a playground to try Angular without installing anything
(as Vue or Svelte do as well).</p>
<p>Angular also has a new logo that you can see at the top of this post!</p>
<h2 id="control-flow-syntax">Control flow syntax</h2>
<p>Even if it is only a “developer preview” feature, this is a big one!
Angular templates are evolving to use a new syntax for control flow structures.</p>
<p>We wrote a dedicated blog post about this feature:</p>
<p>👉 <a href="/2023/10/11/angular-control-flow-syntax/">Angular Control Flow Syntax</a></p>
<p>An experimental migration allows you to give it a try in your project.
The syntax should become stable in v18, and be the recommended way to write templates at that point.</p>
<h2 id="deferrable-views">Deferrable views</h2>
<p>Another big feature is the introduction of deferrable views using <code class="language-plaintext highlighter-rouge">@defer</code> in templates.</p>
<p>We wrote a dedicated blog post about this feature:</p>
<p>👉 <a href="/2023/11/02/angular-defer/">Angular Deferrable Views</a></p>
<p>This is a “developer preview” feature as well and should become stable in v18.
It’s probably going to be less impactful than the control flow syntax,
but it’s still interesting to have a way to easily lazy-load parts of a template.</p>
<h2 id="signals-are-now-stable">Signals are now stable!</h2>
<p>The Signals API is now marked as stable 🎉.
Except <code class="language-plaintext highlighter-rouge">effect()</code>, and the RxJS interoperability functions <code class="language-plaintext highlighter-rouge">toSignal</code> and <code class="language-plaintext highlighter-rouge">toObservable</code>
which might change and are still marked as “developer preview”.</p>
<p>The API has not changed much since our <a href="/2023/04/26/angular-signals/">blog post about Signals</a>,
but some notable things happened.</p>
<h3 id="mutate-has-been-dropped">mutate has been dropped</h3>
<p><code class="language-plaintext highlighter-rouge">mutate()</code> has been dropped from the API.
You were previously able to write something like:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>users.mutate(usersArray => usersArray.push(newUser));
</code></pre></div></div>
<p>And you’ll now have to write:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>users.update(usersArray => [...usersArray, newUser]);
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">mutate()</code> method was introducing some issues with other libraries,
and was not worth the trouble as it can be replaced by <code class="language-plaintext highlighter-rouge">update()</code> quite easily.</p>
<h2 id="template-diagnostic">template diagnostic</h2>
<p>A new compiler diagnostic is available to help you spot missing signal invocations in your templates.</p>
<p>Let’s say you have a <code class="language-plaintext highlighter-rouge">count</code> signal used in a template, but forgot the <code class="language-plaintext highlighter-rouge">()</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div>{{ count }}</div>
</code></pre></div></div>
<p>throws with:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NG8109: count is a function and should be invoked: count()
</code></pre></div></div>
<h3 id="flusheffects">flushEffects</h3>
<p>A new method is available (as a developer preview)
on the <code class="language-plaintext highlighter-rouge">TestBed</code> class to trigger pending effects: <code class="language-plaintext highlighter-rouge">flushEffects</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>TestBed.flushEffects();
</code></pre></div></div>
<p>This is because effect timing has changed a bit:
they are no longer triggered by change detection but scheduled via the microtask queue
(like <code class="language-plaintext highlighter-rouge">setTimeout()</code> or <code class="language-plaintext highlighter-rouge">Promise.resolve()</code>).
So while you could previously trigger them by calling <code class="language-plaintext highlighter-rouge">detectChanges()</code> on the fixture,
you now have to call <code class="language-plaintext highlighter-rouge">TestBed.flushEffects()</code>.</p>
<h3 id="afterrender-and-afternextrender-phases">afterRender and afterNextRender phases</h3>
<p>The <code class="language-plaintext highlighter-rouge">afterRender</code> and <code class="language-plaintext highlighter-rouge">afterNextRender</code> functions introduced in Angular v16.2
can now specify a <code class="language-plaintext highlighter-rouge">phase</code> option.
Angular uses this phase to schedule callbacks to improve performance.
There are 4 possible values, and they run in the following order:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">EarlyRead</code> (when you need to read the DOM before writing to the DOM)</li>
<li><code class="language-plaintext highlighter-rouge">Write</code> (needed if you want to write to the DOM, for example, to initialize a chart using a third-party library)</li>
<li><code class="language-plaintext highlighter-rouge">MixedReadWrite</code> (default, but should be avoided if possible to use a more specific phase)</li>
<li><code class="language-plaintext highlighter-rouge">Read</code> (recommended if you only need to read the DOM)</li>
</ul>
<p>I <em>think</em> we should be able to use <code class="language-plaintext highlighter-rouge">Read</code> and <code class="language-plaintext highlighter-rouge">Write</code> in most cases.
<code class="language-plaintext highlighter-rouge">EarlyRead</code> and <code class="language-plaintext highlighter-rouge">MixedReadWrite</code> degrade performances, so they should be avoided if possible.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export class ChartComponent {
@ViewChild('canvas') canvas!: ElementRef<HTMLCanvasElement>;
constructor() {
afterNextRender(() => {
const ctx = this.canvas.nativeElement;
new Chart(ctx, { type: 'line', data: { ... } });
}, { phase: AfterRenderPhase.Write });
}
}
</code></pre></div></div>
<h3 id="performances">Performances</h3>
<p>The internal algorithm changed to use a ref-counting mechanism instead of a mechanism based on bi-directional weak references. It should be more performant than it was in many cases.</p>
<p>It’s also worth noting that the change detection algorithm has been improved to be more efficient when using Signals.
Previously, when reading a signal in a template, Angular was marking the component
and all its ancestors as dirty when the signal was updated
(as it currently does with when <code class="language-plaintext highlighter-rouge">OnPush</code> components are marked for check).
It’s now a bit smarter and only marks the component as dirty when the signal is updated and not all its ancestors.
It will still check the whole application tree,
but the algorithm will be faster because some components will be skipped.</p>
<p>We don’t have a way to write pure signal-based components yet, with no need for ZoneJS,
but it should be coming eventually!</p>
<h2 id="styleurls-as-a-string">styleUrls as a string</h2>
<p>The <code class="language-plaintext highlighter-rouge">styleUrls</code> and <code class="language-plaintext highlighter-rouge">styles</code> properties of the <code class="language-plaintext highlighter-rouge">@Component</code> decorator can now be a string instead of an array of strings.
A new property called <code class="language-plaintext highlighter-rouge">styleUrl</code> has also been introduced.</p>
<p>You can now write:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrl: './app.component.css',
})
export class AppComponent {}
</code></pre></div></div>
<h2 id="view-transitions-router-support">View Transitions router support</h2>
<p>The View Transitions API is a fairly new browser API
that allows you to animate the transition between two views.
It is only supported in recent versions of Chrome, Edge, and Opera (see
<a href="https://caniuse.com/mdn-api_document_startviewtransition">caniuse.com stats</a>)
but not in Firefox yet.
It works by taking a screenshot of the current view and animating it to the new view.</p>
<p>I’m not very familiar with this API,
but there is a great article about it on
<a href="https://developer.chrome.com/docs/web-platform/view-transitions/">developer.chrome.com</a>
and cool demos on <a href="https://http203-playlist.netlify.app/">this site</a> (open it with a browser that supports this API of course).</p>
<p>Angular v17 adds support for this API in the router.
This is an experimental feature, and you’ll have to enable it by using <code class="language-plaintext highlighter-rouge">withTransitionViews()</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bootstrapApplication(AppComponent, {
providers: [{ provideRouter(routes, withTransitionViews()) }]
});
</code></pre></div></div>
<p>By default, you get a nice fade-in/fade-out transition between views when navigating from one route to another.
You can customize the animation using CSS, animate the whole view or skip part of it,
or indicate which DOM elements are in fact the same entities in the old and new views:
the browser will then do its best to animate between the states.</p>
<p>It is possible to skip the initial transition by using the <code class="language-plaintext highlighter-rouge">skipInitialTransition</code> option:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bootstrapApplication(AppComponent, {
providers: [{ provideRouter(routes, withTransitionViews({ skipInitialTransition: true })) }]
});
</code></pre></div></div>
<p>More advanced scenarios require to add/remove CSS classes to the views,
so the router also lets you run an arbitrary function when the transition is done
if you use the <code class="language-plaintext highlighter-rouge">onViewTransitionCreated</code> option to define a callback.</p>
<h2 id="http">Http</h2>
<p>The fetch backend (introduced in <a href="/2023/06/14/what-is-new-angular-16.1">Angular v16.1</a>)
has been promoted to stable.</p>
<p>When using SSR, it is now possible to customize the transfer cache, using <code class="language-plaintext highlighter-rouge">withHttpTransferCacheOptions(options)</code>.
The options can be:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">filter</code>: a function to filter the requests that should be cached</li>
<li><code class="language-plaintext highlighter-rouge">includeHeaders</code>: the list of headers to include (none by default)</li>
<li><code class="language-plaintext highlighter-rouge">includePostRequests</code>: whether or not to cache POST requests (by default only GET and HEAD requests are cached)</li>
</ul>
<p>For example:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bootstrapApplication(AppComponent, {
providers: [provideHttpClient({
withHttpTransferCacheOptions({ includePostRequests: true })
})
});
</code></pre></div></div>
<h2 id="devtools">Devtools</h2>
<p>The devtools received some love as well,
and they now allow you to inspect the dependency injection tree.</p>
<h2 id="animations">Animations</h2>
<p>No new feature for this part of Angular,
but it is now possible to lazy-load the animations package.
In a standalone application, you can use <code class="language-plaintext highlighter-rouge">provideAnimationsAsync()</code> instead of
using <code class="language-plaintext highlighter-rouge">provideAnimations()</code> and the necessary code for animations will be loaded asynchronously.</p>
<p>The application should work the same,
but you should see an extra chunk appear when building the application.
That’s a few kilobytes of JavaScript that you don’t have to load upfront 🚀.</p>
<p>You can disable animations by providing <code class="language-plaintext highlighter-rouge">'noop'</code> as the value of <code class="language-plaintext highlighter-rouge">provideAnimationsAsync()</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bootstrapApplication(AppComponent, {
providers: [provideAnimationsAsync('noop')]
});
</code></pre></div></div>
<h2 id="performances-1">Performances</h2>
<p>In dev mode, you’ll now get a warning if you load an oversized image
or if an image is the “Largest Contentful Paint element” in the page and is lazy-loaded
(which is a bad idea, see <a href="https://angular.io/errors/NG0913#lazy-loaded-lcp-element">the explanations here</a>).</p>
<p>For example:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>An image with src image.png has intrinsic file dimensions much larger than its
rendered size. This can negatively impact application loading performance.
For more information about addressing or disabling this warning, see
https://angular.io/errors/NG0913
</code></pre></div></div>
<p>You can configure this behavior via dependency injection,
for example, if you want to turn off these warnings:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
provide: IMAGE_CONFIG, useValue:
{
disableImageSizeWarning: false,
disableImageLazyLoadWarning: false
}
}
</code></pre></div></div>
<h2 id="typescript-52-and-nodejs-v18">TypeScript 5.2 and Node.js v18</h2>
<p>It’s worth noting that Angular now requires TypeScript 5.2 and Node.js v18.
Support for older versions has been dropped.</p>
<h2 id="angular-cli">Angular CLI</h2>
<p>A lot happened in the CLI!</p>
<p>👉 Check out our <a href="/2023/11/09/angular-cli-17.0/">dedicated blog post about the CLI v17</a> for more details.</p>
<h2 id="summary">Summary</h2>
<p>That’s all for this release, stay tuned!</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Angular CLI 17.0?2023-11-09T00:00:00+00:00https://blog.ninja-squad.com/2023/11/09/angular-cli-17.0<p><a href="https://github.com/angular/angular-cli/releases/tag/17.0.0">Angular CLI 17.0.0</a> is out!✨</p>
<p>If you want to upgrade to 17.0.0 without pain (or to any other version, by the way), I have created a Github project to help: <a href="https://github.com/cexbrayat/angular-cli-diff">angular-cli-diff</a>. Choose the version you’re currently using (16.2.0 for example), and the target version (17.0.0 for example), and it gives you a diff of all files created by the CLI: <a href="https://github.com/cexbrayat/angular-cli-diff/compare/16.2.0...17.0.0">angular-cli-diff/compare/16.2.0…17.0.0</a>.
It can be a great help along with the official <code class="language-plaintext highlighter-rouge">ng update @angular/core @angular/cli</code> command.
You have no excuse for staying behind anymore!</p>
<p>Let’s see what we’ve got in this release.</p>
<h2 id="standalone-applications-with-vite-by-default">Standalone applications with Vite by default!</h2>
<p>The <code class="language-plaintext highlighter-rouge">--standalone</code> flag is now the default behavior of the CLI.
This means generating a new project with <code class="language-plaintext highlighter-rouge">ng new</code> now uses standalone components by default,
and that the <code class="language-plaintext highlighter-rouge">ng generate component/pipe/directive</code> command now generates standalone components/pipes/directives.</p>
<p>Another notable change to <code class="language-plaintext highlighter-rouge">ng new</code> is that the routing is now enabled by default.</p>
<p>But the most important change is that the CLI now uses <a href="https://vitejs.dev/">Vite</a> out-of-the-box!
A new builder called <code class="language-plaintext highlighter-rouge">application</code> has been introduced, and is used when generating a new project.
This builder has a very similar configuration to the <code class="language-plaintext highlighter-rouge">browser</code> builder,
so the migration is quite easy if you want to use Vite in an existing project
You have to change the builder from <code class="language-plaintext highlighter-rouge">browser</code> to <code class="language-plaintext highlighter-rouge">application</code> in the <code class="language-plaintext highlighter-rouge">angular.json</code> file,
rename the <code class="language-plaintext highlighter-rouge">main</code> property to <code class="language-plaintext highlighter-rouge">browser</code>,
and remove a few options from the development configuration (<code class="language-plaintext highlighter-rouge">buildOptimizer</code>, <code class="language-plaintext highlighter-rouge">vendorChunk</code>).</p>
<p>Once migrated, the <code class="language-plaintext highlighter-rouge">ng serve</code> command will use Vite instead of Webpack.
Build time should be faster, especially for cold starts (I saw 2-3x times improvement on my machine).
There is no HMR by default yet, but the global style changes are detected and applied automatically without reloading the page.</p>
<p>Note that the output of the <code class="language-plaintext highlighter-rouge">ng build</code> Vite-based command is now in <code class="language-plaintext highlighter-rouge">dist/my-project/browser</code> instead of <code class="language-plaintext highlighter-rouge">dist/my-project</code>.</p>
<p>The <code class="language-plaintext highlighter-rouge">browser-esbuilder</code> builder still exists, but will be removed in the future.
You should use the <code class="language-plaintext highlighter-rouge">application</code> builder instead.</p>
<h2 id="ng-new-ssr">ng new –ssr</h2>
<p>A new flag <code class="language-plaintext highlighter-rouge">--ssr</code> has been added to the <code class="language-plaintext highlighter-rouge">ng new</code> command to generate a new project
with SSR enabled out of the box.</p>
<p>It generates a project similar to what you usually get and then runs the <code class="language-plaintext highlighter-rouge">@angular/ssr</code> schematics
(you can also use the schematics directly on an existing project with <code class="language-plaintext highlighter-rouge">ng add @angular/ssr</code>).
<code class="language-plaintext highlighter-rouge">@angular/ssr</code> is a new package and replaces the Angular Universal package.
If you were using the Angular Universal package, <code class="language-plaintext highlighter-rouge">ng update</code> migrates your configuration to use <code class="language-plaintext highlighter-rouge">@angular/ssr</code> automatically.</p>
<p>This schematic does the following:</p>
<ul>
<li>adds the <code class="language-plaintext highlighter-rouge">@angular/ssr</code> package</li>
<li>adds the <code class="language-plaintext highlighter-rouge">@angular/platform-server</code> package</li>
<li>adds the <code class="language-plaintext highlighter-rouge">express</code> and <code class="language-plaintext highlighter-rouge">@types/express</code> packages</li>
<li>adds the <code class="language-plaintext highlighter-rouge">main.server.ts</code> file (entry point for the application when running on the server)</li>
<li>adds the <code class="language-plaintext highlighter-rouge">app.config.server.ts</code> file (providers for the application when running on the server)</li>
<li>adds the <code class="language-plaintext highlighter-rouge">tsconfig.server.json</code> file</li>
<li>adds the <code class="language-plaintext highlighter-rouge">server.ts</code> file (the Express server, responsible for serving the application)</li>
</ul>
<p>It updates the <code class="language-plaintext highlighter-rouge">angular.json</code> configuration to add the following options to the <code class="language-plaintext highlighter-rouge">build</code> target:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"server"</span><span class="p">:</span><span class="w"> </span><span class="s2">"src/main.server.ts"</span><span class="err">,</span><span class="w">
</span><span class="nl">"prerender"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span><span class="nl">"ssr"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"entry"</span><span class="p">:</span><span class="w"> </span><span class="s2">"server.ts"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>and adds the <code class="language-plaintext highlighter-rouge">provideClientHydration()</code> to the (browser) application providers,
to have a smooth transition between the server and the client.
This is a new feature of Angular v16, and we talked about it in our article about the <a href="/2023/05/03/what-is-new-angular-16.0">v16 release</a>.</p>
<p>When running <code class="language-plaintext highlighter-rouge">ng build</code>, the CLI will now build the server bundle (in <code class="language-plaintext highlighter-rouge">dist/my-project/server</code>) and the client bundle (in <code class="language-plaintext highlighter-rouge">dist/my-project/browser</code>).
You can then run the generated server with:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>node dist/my-project/server/main.server.mjs
</code></pre></div></div>
<p>This starts an Express server on port 4000 by default, which serves the rendered pages.</p>
<p>The rendered pages are in the <code class="language-plaintext highlighter-rouge">browser</code> folder, and are named <code class="language-plaintext highlighter-rouge">${page}/index.html</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dist/my-project/browser/index.html
dist/my-project/browser/login/index.html
dist/my-project/browser/register/index.html
</code></pre></div></div>
<p>If you use <code class="language-plaintext highlighter-rouge">localize</code> in your application, the CLI will also build the localized bundles (in <code class="language-plaintext highlighter-rouge">dist/my-project/server/${lang}</code>).</p>
<p>The prerendering mechanism should be quite accurate now,
as it uses the Angular router under the hood to navigate to each route and render it
(routes with parameters or redirections are skipped).
When prerendering is enabled, the CLI generates a <code class="language-plaintext highlighter-rouge">prerendered-routes.json</code> file
that contains all the prerendered routes.
This is useful if you deploy on the cloud as this file is usually recognized by providers
to serve these files as static.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
"routes": [
"/",
"/login",
"/register"
...
]
}
</code></pre></div></div>
<p>You can disable the auto-discovery of routes by setting the <code class="language-plaintext highlighter-rouge">discoverRoutes</code> option to <code class="language-plaintext highlighter-rouge">false</code> in the <code class="language-plaintext highlighter-rouge">angular.json</code> file. You can also provide your own list of routes in this file by defining <code class="language-plaintext highlighter-rouge">routeFiles</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"ssr": {
"discoverRoutes": false,
"routeFiles": "ssg-routes.txt"
}
</code></pre></div></div>
<p>This file must contain a list of routes that you want to render (and can contain parameterized routes).</p>
<p>When running <code class="language-plaintext highlighter-rouge">ng serve</code>, the CLI serves the application via Vite,
and only pre-renders the requested page (the one you’re currently on).</p>
<p>You can also use a new option to <code class="language-plaintext highlighter-rouge">CommonEngine</code> called <code class="language-plaintext highlighter-rouge">enablePerformanceProfiler</code> to trace the performance of each step of the rendering:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const commonEngine = new CommonEngine({
enablePerformanceProfiler: true
});
</code></pre></div></div>
<p>When using SSR, it is recommended to use the Fetch version of the HTTP client,
by using <code class="language-plaintext highlighter-rouge">provideHttpClient(withFetch())</code>
(as introduced in <a href="/2023/06/14/what-is-new-angular-16.1">Angular v16.1</a>).
This is for performance and compatibility reasons.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NG02801: Angular detected that `HttpClient` is not configured to use `fetch` APIs. It's strongly recommended to enable `fetch` for applications that use Server-Side Rendering for better performance and compatibility. To enable `fetch`, add the `withFetch()` to the `provideHttpClient()` call at the root of the application.
</code></pre></div></div>
<h2 id="functional-http-interceptors-by-default">Functional HTTP interceptors by default</h2>
<p>The CLI now generates functional interceptors by default,
without the need to specify <code class="language-plaintext highlighter-rouge">--functional</code> anymore.
Class-based interceptors are still available with the <code class="language-plaintext highlighter-rouge">--no-functional</code> option,
but you’re now encouraged to use the functional ones.</p>
<h2 id="summary">Summary</h2>
<p>That’s all for the CLI v17.0 release!
You’ll find more interesting features in our article about the
<a href="/2023/11/09/what-is-new-angular-17.0">framework v17.0.0 release</a>.</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
A guide to Angular Deferrable Views with @defer2023-11-02T00:00:00+00:00https://blog.ninja-squad.com/2023/11/02/angular-defer<p>With the introduction of the <a href="/2023/10/11/angular-control-flow-syntax">Control flow syntax</a>,
the Angular team has also introduced a new way to load components lazily
(as a developer preview for now).
We already have lazy-loading in Angular, but it is mainly based on the router.</p>
<p>Angular v17 adds a new way to load components lazily, using the <code class="language-plaintext highlighter-rouge">@defer</code> syntax in your templates.</p>
<p><code class="language-plaintext highlighter-rouge">@defer</code> lets you define a block of template that will be loaded lazily when a condition is met
(with all the components, pipes, directives, and libraries used in this block lazily loaded as well).
Several conditions can be used.
For example, it can be “as soon as possible (no condition)”,
“when the user scrolls to that section”,
“when the user clicks on that button” or “after 2 seconds”.</p>
<p>Let’s say your home page displays a “heavy” <code class="language-plaintext highlighter-rouge">ChartComponent</code> that uses a charting library
and some other dependencies, like a <code class="language-plaintext highlighter-rouge">FromNow</code> pipe:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component({
selector: 'ns-chart',
template: '...',
standalone: true,
imports: [FromNowPipe],
})
export class ChartComponent {
// uses chart.js
}
</code></pre></div></div>
<p>This component is used in the home page:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import { ChartComponent } from './chart.component';
@Component({
selector: 'ns-home',
template: `
<!-- some content -->
<ns-chart />
`,
standalone: true,
imports: [ChartComponent]
})
export class HomeComponent {
// ...
}
</code></pre></div></div>
<p>When the application is packaged, the <code class="language-plaintext highlighter-rouge">ChartComponent</code> will be included in the main bundle:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+------------------------+
| main-xxxx.js - 300KB |
+------------------------+
| home.component.ts |
| chart.component.ts |
| from-now.pipe.ts |
| chart.js |
+------------------------+
</code></pre></div></div>
<p>Let’s say that the component is not visible at first on the home page,
maybe because it is at the bottom of the page, or because it is in a tab that is not active.
It makes sense to avoid loading this component eagerly
because it would slow down the initial loading of the page.</p>
<p>With <code class="language-plaintext highlighter-rouge">@defer</code>, you can load this component only when the user really needs it.
Just wrapping the <code class="language-plaintext highlighter-rouge">ChartComponent</code> in a <code class="language-plaintext highlighter-rouge">@defer</code> block will do the trick:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import { ChartComponent } from './chart.component';
@Component({
selector: 'ns-home',
template: `
<!-- some content -->
@defer (when isVisible) {
<ns-chart />
}
`,
standalone: true,
imports: [ChartComponent]
})
</code></pre></div></div>
<p>The Angular compiler will rewrite the static import of the <code class="language-plaintext highlighter-rouge">ChartComponent</code>
to a dynamic import (<code class="language-plaintext highlighter-rouge">() => import('./chart.component')</code>),
and the component will be loaded only when the condition is met.
As the component is now imported dynamically,
it will not be included in the main bundle.
The bundler will create a new chunk for it:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+------------------------+
| main-xxxx.js - 100KB |
+------------------------+ +-------------------------+
| home.component.ts |------>| chunk-xxxx.js - 200KB |
+------------------------+ +-------------------------+
| chart.component.ts |
| from-now.pipe.ts |
| chart.js |
+-------------------------+
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">chunk-xxxx.js</code> file will only be loaded when the condition is met,
and the <code class="language-plaintext highlighter-rouge">ChartComponent</code> will be displayed.</p>
<p>Before talking about the various kinds of conditions that can be used with <code class="language-plaintext highlighter-rouge">@defer</code>,
let’s see how to use another interesting feature:
displaying a placeholder until the deferred block is loaded.</p>
<h2 id="placeholder-loading-and-error"><code class="language-plaintext highlighter-rouge">@placeholder</code>, <code class="language-plaintext highlighter-rouge">@loading</code>, and <code class="language-plaintext highlighter-rouge">@error</code></h2>
<p>You can define a placeholder template with <code class="language-plaintext highlighter-rouge">@placeholder</code>
that will be displayed until the loading condition is met.
Then, while the block is loading, you can display a loading template with <code class="language-plaintext highlighter-rouge">@loading</code>.
If no <code class="language-plaintext highlighter-rouge">@loading</code> block is defined, the placeholder stays there until the block is loaded.
You can also define an error template with <code class="language-plaintext highlighter-rouge">@error</code> that will be displayed if the block fails to load.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@defer (when show) {
<ns-chart />
}
@placeholder {
<div>Something until the loading starts</div>
}
@loading {
<div>Loading...</div>
}
@error {
<div>Something went wrong</div>
}
</code></pre></div></div>
<p>When using server-side rendering,
only the placeholder will be rendered on the server (the defer conditions will never trigger).</p>
<h2 id="after-and-minimum"><code class="language-plaintext highlighter-rouge">after</code> and <code class="language-plaintext highlighter-rouge">minimum</code></h2>
<p>As the <code class="language-plaintext highlighter-rouge">@defer</code> block loading can be quite fast,
there is a risk that the loading block is displayed and hidden too quickly,
causing a “flickering” effect.</p>
<p>To avoid this, you can use the <code class="language-plaintext highlighter-rouge">after</code> option to specify after how many milliseconds
the loading should be displayed.</p>
<p>If the block takes less than this delay to load, then the <code class="language-plaintext highlighter-rouge">@loading</code> block is never displayed.</p>
<p>You can also use the <code class="language-plaintext highlighter-rouge">minimum</code> option to specify a minimum duration for the loading.
If the loading is faster than the minimum duration,
then the loading will be displayed for the minimum duration (this only applies if the loading is ever displayed).</p>
<p>You can of course combine all these options:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@defer (when show) {
<ns-chart />
}
@placeholder {
<div>Something until the loading starts</div>
}
@loading (after 500ms; minimum 500ms) {
<div>Loading...</div>
}
</code></pre></div></div>
<p>You can also specify a <code class="language-plaintext highlighter-rouge">minimum</code> duration for the placeholder.
It can be useful when the loading condition is immediate (for example, when no condition is specified).
In that case, the placeholder will be displayed for the minimum duration,
even if the block is loaded immediately,
to avoid a “flickering” effect.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@defer (when show) {
<ns-chart />
}
@placeholder (minimum 500ms) {
<div>Something until the loading starts</div>
}
@loading (after 500ms; minimum 500ms) {
<div>Loading...</div>
}
</code></pre></div></div>
<h2 id="conditions">Conditions</h2>
<p>Several conditions can be used with <code class="language-plaintext highlighter-rouge">@defer</code>,
let’s see them one by one.</p>
<h3 id="no-condition-or-on-idle">No condition or <code class="language-plaintext highlighter-rouge">on idle</code></h3>
<p>The simplest condition is to not specify any condition at all:
in this case, the block will be loaded when the browser is idle
(the loading is scheduled using <code class="language-plaintext highlighter-rouge">requestIdleCallback</code>).</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@defer {
<ns-chart />
}
</code></pre></div></div>
<p>This is equivalent to using the <code class="language-plaintext highlighter-rouge">on idle</code> condition:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@defer (on idle) {
<ns-chart />
}
</code></pre></div></div>
<h3 id="simple-boolean-condition-with-when">Simple boolean condition with <code class="language-plaintext highlighter-rouge">when</code></h3>
<p>You can also use a boolean condition to load a block of the template with <code class="language-plaintext highlighter-rouge">when</code>.
Here, we display the defer block only when the <code class="language-plaintext highlighter-rouge">show</code> property of the component is true:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@defer (when show) {
<ns-chart />
}
</code></pre></div></div>
<p>Note that this is not the same as using <code class="language-plaintext highlighter-rouge">*ngIf</code> on the block,
as the block will not be removed even if the condition becomes false later.</p>
<h3 id="on-immediate">on <code class="language-plaintext highlighter-rouge">immediate</code></h3>
<p>The <code class="language-plaintext highlighter-rouge">on immediate</code> condition triggers the loading of the block immediately.
It does not display a placeholder, even if one is defined.</p>
<h3 id="on-timer">on <code class="language-plaintext highlighter-rouge">timer</code></h3>
<p>The <code class="language-plaintext highlighter-rouge">on timer</code> condition triggers the loading of the block after a given duration,
using <code class="language-plaintext highlighter-rouge">setTimeout</code> under the hood.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@defer (on timer(2s)) {
<ns-chart />
}
</code></pre></div></div>
<h3 id="on-hover">on <code class="language-plaintext highlighter-rouge">hover</code></h3>
<p>Other conditions are based on user interactions.
These conditions can specify the element of the interaction using a template reference variable,
or none to use the placeholder element.
In the latter case, the placeholder element must exist
and have a single child element that will be used as the element of the interaction.</p>
<p>The <code class="language-plaintext highlighter-rouge">on hover</code> condition triggers the loading of the block when the user hovers the element.
Under the hood, it listens to the <code class="language-plaintext highlighter-rouge">mouseenter</code> and <code class="language-plaintext highlighter-rouge">focusin</code> events.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span #trigger>Hover me</span>
@defer (on hover(trigger)) {
<ns-chart />
}
</code></pre></div></div>
<p>or using the placeholder element:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@defer (on hover) {
<ns-chart />
}
@placeholder {
<span>Hover me</span>
}
</code></pre></div></div>
<h3 id="on-interaction">on <code class="language-plaintext highlighter-rouge">interaction</code></h3>
<p>The <code class="language-plaintext highlighter-rouge">on interaction</code> condition triggers the loading of the block when the user interacts with the element.
Under the hood, it listens to the <code class="language-plaintext highlighter-rouge">click</code> and <code class="language-plaintext highlighter-rouge">keydown</code> events.</p>
<h3 id="on-viewport">on <code class="language-plaintext highlighter-rouge">viewport</code></h3>
<p>The <code class="language-plaintext highlighter-rouge">on viewport</code> condition triggers the loading of the block when the element becomes visible in the viewport.
Under the hood, it uses an <a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API">intersection observer</a>.</p>
<h3 id="multiple-conditions">Multiple conditions</h3>
<p>You can also combine multiple conditions using a comma-separated list:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!-- Loads if the user hovers the placeholder, or after 1 minute -->
@defer (on hover, timer(60s)) {
<ns-chart />
}
@placeholder {
<span>Something until the loading starts</span>
}
</code></pre></div></div>
<h2 id="prefetching">Prefetching</h2>
<p><code class="language-plaintext highlighter-rouge">@defer</code> allows you
to separate the loading of a component from its display.
You can use the same conditions we previously saw to load a component using <code class="language-plaintext highlighter-rouge">prefetch</code>,
and then display it with another condition.</p>
<p>For example, you can prefetch the lazy-loaded content <code class="language-plaintext highlighter-rouge">on idle</code>
and then display it <code class="language-plaintext highlighter-rouge">on interaction</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@defer (on interaction; prefetch on idle) {
<ns-chart />
}
@placeholder {
<button>Show me</button>
}
</code></pre></div></div>
<p>Note that the <code class="language-plaintext highlighter-rouge">@loading</code> block will not be displayed if the deferred block is already prefetched
when the loading condition is met.</p>
<h2 id="how-to-test-deferred-loading">How to test deferred loading?</h2>
<p>When a component uses defer blocks in its template,
you’ll have to do some extra work to test it.</p>
<p>The <code class="language-plaintext highlighter-rouge">TestBed</code> API has been extended to help you with that.
The <code class="language-plaintext highlighter-rouge">configureTestingModule</code> method now accepts a <code class="language-plaintext highlighter-rouge">deferBlockBehavior</code> option.
By default, this option is set to <code class="language-plaintext highlighter-rouge">DeferBlockBehavior.Manual</code>,
which means that you’ll have to manually trigger the display of the defer blocks.
But let’s start with the other option instead.</p>
<p>You can change this behavior by using <code class="language-plaintext highlighter-rouge">DeferBlockBehavior.Playthrough</code>.
Playthrough means that the defer blocks will be displayed automatically
when a condition is met, as they would when the application runs in the browser.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>beforeEach(() => {
TestBed.configureTestingModule({
deferBlockBehavior: DeferBlockBehavior.Playthrough
});
});
</code></pre></div></div>
<p>In that case, the defer blocks will be displayed automatically when a condition is met,
after calling <code class="language-plaintext highlighter-rouge">await fixture.whenStable()</code>.</p>
<p>So if we test a component with a deferred block that is visible after clicking on a button,
we can use:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// Click the button to trigger the deferred block
fixture.nativeElement.querySelector('button').click();
fixture.detectChanges();
// Wait for the deferred block to render
await fixture.whenStable();
// Check its content
const loadedBlock = fixture.nativeElement.querySelector('div');
expect(loadedBlock.textContent).toContain('Some lazy-loaded content');
</code></pre></div></div>
<p>If you want to use the <code class="language-plaintext highlighter-rouge">DeferBlockBehavior.Manual</code> behavior,
you’ll have to manually trigger the display of the defer blocks.
To do so, the fixture returned by <code class="language-plaintext highlighter-rouge">TestBed.createComponent</code> now has an async <code class="language-plaintext highlighter-rouge">getDeferBlocks</code> method
that returns an array of <code class="language-plaintext highlighter-rouge">DeferBlockFixture</code> objects.
Each of these fixtures has a <code class="language-plaintext highlighter-rouge">render</code> method that you can call to display the block
in a specific state, by providing a <code class="language-plaintext highlighter-rouge">DeferBlockState</code> parameter.</p>
<p><code class="language-plaintext highlighter-rouge">DeferBlockState</code> is an enum with the following values:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">DeferBlockState.Placeholder</code>: display the placeholder state of the block</li>
<li><code class="language-plaintext highlighter-rouge">DeferBlockState.Loading</code>: display the loading state of the block</li>
<li><code class="language-plaintext highlighter-rouge">DeferBlockState.Error</code>: display the error state of the block</li>
<li><code class="language-plaintext highlighter-rouge">DeferBlockState.Complete</code>: display the defer block as if the loading was complete</li>
</ul>
<p>This allows a fine-grained control of the state of the defer blocks.
If we want to test the same component as before, we can do:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const deferBlocks = await fixture.getDeferBlocks();
// only one defer block should be found
expect(deferBlocks.length).toBe(1);
// Render the defer block
await deferBlocks[0].render(DeferBlockState.Complete);
// Check its content
const loadedBlock = fixture.nativeElement.querySelector('div');
expect(loadedBlock.textContent).toContain('Some lazy-loaded content');
</code></pre></div></div>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
Angular templates got better - the Control Flow syntax2023-10-11T00:00:00+00:00https://blog.ninja-squad.com/2023/10/11/angular-control-flow-syntax<p>Angular v17 introduces a new “developer preview” feature called “control flow syntax”.
This feature allows you to use a new template syntax to write control flow statements, like if/else, for, and switch,
instead of using the built-in structural directives (<code class="language-plaintext highlighter-rouge">*ngIf</code>, <code class="language-plaintext highlighter-rouge">*ngFor</code>, and <code class="language-plaintext highlighter-rouge">*ngSwitch</code>).</p>
<p>To understand why this was introduced, let’s see how structural directives work in Angular.</p>
<h2 id="structural-directives-under-the-hood">Structural directives under the hood</h2>
<p>Structural directives are directives that change the structure of the DOM by adding, removing, or manipulating elements.
They are easy to recognize in Angular because they begin with an asterisk <code class="language-plaintext highlighter-rouge">*</code>.</p>
<p>But how do they <em>really</em> work?</p>
<p>Let’s take a simple template with <code class="language-plaintext highlighter-rouge">ngIf</code> and <code class="language-plaintext highlighter-rouge">ngFor</code> directives as an example:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><h1>Ninja Squad</h1>
<ul *ngIf="condition">
<li *ngFor="let user of users">{{ user.name }}</li>
</ul>
</code></pre></div></div>
<p>If you read the chapter of our ebook about the Angular compiler,
you know that the framework generates JavaScript code from this template.
And maybe you imagine that <code class="language-plaintext highlighter-rouge">*ngIf</code> gets converted to a JavaScript <code class="language-plaintext highlighter-rouge">if</code>
and <code class="language-plaintext highlighter-rouge">*ngFor</code> to a <code class="language-plaintext highlighter-rouge">for</code> loop like:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>createElement('h1');
if (condition) {
createElement('ul');
for (user of users) {
createElement('li');
}
}
</code></pre></div></div>
<p>But Angular does not work exactly like that:
the framework decomposes the component’s template into “views”.
A view is a fragment of the template that has static HTML content.
It can have dynamic attributes and texts, but the HTML elements are stable.</p>
<p>So our example generates in fact three views,
corresponding to three parts of the template:</p>
<p>Main view:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><h1>Ninja Squad</h1>
<!-- special comment -->
</code></pre></div></div>
<p>NgIf view:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ul>
<!-- special comment -->
</ul>
</code></pre></div></div>
<p>NgFor view:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><li>{{ user.name }}</li>
</code></pre></div></div>
<p>This is because the <code class="language-plaintext highlighter-rouge">*</code> syntax is in fact syntactic sugar
to apply an attribute directive on an <code class="language-plaintext highlighter-rouge">ng-template</code> element.
So our example is the same as:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><h1>Ninja Squad</h1>
<ng-template [ngIf]="condition">
<ul>
<ng-template ngFor [ngForOf]="users" let-user>
<li>{{ user.name }}</li>
</ng-template>
</ul>
</ng-template>
</code></pre></div></div>
<p>Here <code class="language-plaintext highlighter-rouge">ngIf</code> and <code class="language-plaintext highlighter-rouge">ngFor</code> are plain directives.
Each <code class="language-plaintext highlighter-rouge">ng-template</code> then generates a “view”.
Each view has a static structure that never changes.
But these views need to be dynamically inserted at some point.
And that’s where the <code class="language-plaintext highlighter-rouge"><!-- special comment --></code> comes into play.</p>
<p>Angular has the concept of <code class="language-plaintext highlighter-rouge">ViewContainer</code>.
A <code class="language-plaintext highlighter-rouge">ViewContainer</code> is like a box where you can insert/remove child views.
To mark the location of these containers,
Angular uses a special HTML comment in the created DOM.</p>
<p>That’s what <code class="language-plaintext highlighter-rouge">ngIf</code> actually does under the hood:
it creates a <code class="language-plaintext highlighter-rouge">ViewContainer</code>, and then, when the condition given as input changes,
it inserts or removes the child view at the location of the special comment.</p>
<p>This view concept is quite interesting as it will allow Angular
to only update views that consume a signal in the future,
and not the whole template of a component!
Check out out our <a href="/2023/04/26/angular-signals/">blog post about the Signal API</a> for more details.</p>
<h2 id="custom-structural-directives">Custom structural directives</h2>
<p>You can create your own structural directives if you want to.
Let’s say you want to write a <code class="language-plaintext highlighter-rouge">*customNgIf</code> directive.
You can create a directive that takes a condition as an input and
injects a <code class="language-plaintext highlighter-rouge">ViewContainerRef</code> (the service that allows to create the view)
and a <code class="language-plaintext highlighter-rouge">TemplateRef</code> (the <code class="language-plaintext highlighter-rouge">ng-template</code> on which the directive is applied).</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import { Directive, DoCheck, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[customNgIf]',
standalone: true
})
export class CustomNgIfDirective implements DoCheck {
/**
* The condition to check
*/
@Input({ required: true, alias: 'customNgIf' }) condition!: boolean;
/**
* The view created by the directive
*/
conditionalView: EmbeddedViewRef<any> | null = null;
constructor(
/**
* The container where the view will be inserted
*/
private vcr: ViewContainerRef,
/**
* The template to render
*/
private tpl: TemplateRef<any>
) {}
/**
* This method is called every time the change detection runs
*/
ngDoCheck() {
// if the condition is true and the view is not created yet
if (this.condition && !this.conditionalView) {
// create the view and insert it in the container
this.conditionalView = this.vcr.createEmbeddedView(this.tpl);
} else if (!this.condition && this.conditionalView) {
// if the condition is false and the view is created
// destroy the view
this.conditionalView.destroy();
this.conditionalView = null;
}
} }
</code></pre></div></div>
<p>This works great!
And as you can see, it lets developers like us create powerful structural directives if we want to:
the built-in directives offered by Angular are not special in any way.</p>
<p>But this approach has some drawbacks:
for example, it is a bit clunky to have an <code class="language-plaintext highlighter-rouge">else</code> alternative with <code class="language-plaintext highlighter-rouge">*ngIf</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div *ngIf="condition; else elseBlock">If</div>
<ng-template #elseBlock><div>Else</div></ng-template>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">elseBlock</code> is another input of the <code class="language-plaintext highlighter-rouge">NgIf</code> directive,
of type <code class="language-plaintext highlighter-rouge">TemplateRef</code>, that the directive will display if the condition is falsy.
But this is not very intuitive to use, so we often see this instead:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div *ngIf="condition">If</div>
<div *ngIf="!condition">Else</div>
</code></pre></div></div>
<p>The structural directives are also not perfect type-checking-wise.
Even if Angular does some magic (with some special fields called <code class="language-plaintext highlighter-rouge">ngTemplateGuard</code> in the directives to help the type-checker),
some cases are too tricky to handle.
For example, the “else” alternative of <code class="language-plaintext highlighter-rouge">*ngIf</code> is not type-checked:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div *ngIf="!user; else userNotNullBlock">No user</div>
<ng-template #userNotNullBlock>
<div>
<!-- should compile as user is not null here -->
<!-- but it doesn't -->
{{ user.name }}
</div>
</ng-template>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">NgSwitch</code> is even worse, as it consists of 3 separate directives
<code class="language-plaintext highlighter-rouge">NgSwitch</code>, <code class="language-plaintext highlighter-rouge">NgSwitchCase</code>, and <code class="language-plaintext highlighter-rouge">NgSwitchDefault</code>.
The compiler has no idea if the <code class="language-plaintext highlighter-rouge">NgSwitchCase</code> is used in the right context.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!-- user.type can be `'user' | 'anonymous'` -->
<ng-container [ngSwitch]="user.type">
<div *ngSwitchCase="'user'">User</div>
<!-- compiles even if user.type can't be 'admin' -->
<div *ngSwitchCase="'admin'">Admin</div>
<div *ngSwitchDefault>Unknown</div>
</ng-container>
</code></pre></div></div>
<p>It’s also worth noting that the <code class="language-plaintext highlighter-rouge">*</code> syntax is not very intuitive for beginners.
And structural directives depend on the <code class="language-plaintext highlighter-rouge">ngDoCheck</code> lifecycle hook,
which is tied to <code class="language-plaintext highlighter-rouge">zone.js</code>.
In a future world where our components use the new Signal API
and don’t need <code class="language-plaintext highlighter-rouge">zone.js</code> anymore,
structural directives would still force us to drag <code class="language-plaintext highlighter-rouge">zone.js</code> in our bundle.</p>
<p>So, to sum up, structural directives are powerful but have some drawbacks.
Fixing these drawbacks would require a lot of work in the compiler and the framework.</p>
<p>That’s why the Angular team decided to introduce a new syntax to write control flow statements in templates!</p>
<h2 id="control-flow-syntax">Control flow syntax</h2>
<p>The control flow syntax is a new syntax introduced in Angular v17
to write control flow statements in templates.</p>
<p>The syntax is very similar to some other templating syntaxes you may have met in the past,
and even to JavaScript itself.
There have been some debates and polling in the community about the various alternatives,
and the <code class="language-plaintext highlighter-rouge">@-syntax</code> proposal won.</p>
<p>With the control flow syntax, our previous template with <code class="language-plaintext highlighter-rouge">*ngIf</code> and <code class="language-plaintext highlighter-rouge">*ngFor</code> can be rewritten as:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><h1>Ninja Squad</h1>
@if (condition) {
<ul>
@for (user of users; track user.id) {
<li>{{ user.name }}</li>
}
</ul>
}
</code></pre></div></div>
<p>This syntax is interpreted by the Angular compiler and creates the same views as the previous template,
but without the overhead of creating the structural directives,
so it is also a tiny bit more performant
(as it uses brand new compiled instructions under the hood in the generated code).
As this is not directives, the type-checking is also much better.</p>
<p>And, cherry on the cake, the syntax is more powerful than the structural directives!</p>
<p>The drawback is that this syntax uses <code class="language-plaintext highlighter-rouge">@</code>, <code class="language-plaintext highlighter-rouge">{</code> and <code class="language-plaintext highlighter-rouge">}</code> characters with a special meaning,
so you can’t use these characters in your templates anymore,
and have to use equivalent HTML entities instead (<code class="language-plaintext highlighter-rouge">\&#64;</code> for <code class="language-plaintext highlighter-rouge">@</code>, <code class="language-plaintext highlighter-rouge">\&#123;</code> for <code class="language-plaintext highlighter-rouge">{</code>, and <code class="language-plaintext highlighter-rouge">\&#125;</code> for <code class="language-plaintext highlighter-rouge">}</code>).</p>
<h2 id="if-statement">If statement</h2>
<p>As we saw above, a limitation of <code class="language-plaintext highlighter-rouge">NgIf</code> is that it is a bit clunky to have an <code class="language-plaintext highlighter-rouge">else</code> alternative.
And we can’t have an <code class="language-plaintext highlighter-rouge">else if</code> alternative at all.</p>
<p>That’s no longer a problem with the control flow syntax:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@if (condition) {
<div>condition is true</div>
} @else if (otherCondition) {
<div>otherCondition is true</div>
} @else {
<div>condition and otherCondition are false</div>
}
</code></pre></div></div>
<p>You can still store the result of the condition in a variable if you want to,
which is really handy when used with an <code class="language-plaintext highlighter-rouge">async</code> pipe for example:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@if (user$ | async; as user) {
<div>User is {{ user.name }}</div>
} @else if (isAdmin$ | async) {
<div>User is admin</div>
} @else {
<div>No user</div>
}
</code></pre></div></div>
<h2 id="for-statement">For statement</h2>
<p>With the control flow syntax, a for loop needs to specify a <code class="language-plaintext highlighter-rouge">track</code> property,
which is the equivalent of the <code class="language-plaintext highlighter-rouge">trackBy</code> function of <code class="language-plaintext highlighter-rouge">*ngFor</code>.
Note that this is now mandatory, whereas it was optional with <code class="language-plaintext highlighter-rouge">*ngFor</code>.
This is for performance reasons, as the Angular team found that very often,
developers were not using <code class="language-plaintext highlighter-rouge">trackBy</code> when they should have.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ul>
@for (user of users; track user.id) {
<li>{{ user.name }}</li>
}
</ul>
</code></pre></div></div>
<p>As you can see, this is a bit easier to use than the <code class="language-plaintext highlighter-rouge">trackBy</code> function of <code class="language-plaintext highlighter-rouge">*ngFor</code>
which requires to write a function.
Here we can directly specify the property of the item that is unique,
and the compiler will generate the function for us.
If you don’t have a unique property, you can still use a function or just use the loop variable itself
(which is equivalent to what <code class="language-plaintext highlighter-rouge">*ngFor</code> currently does when no <code class="language-plaintext highlighter-rouge">trackBy</code> is specified).</p>
<p>One of the very useful additions to the control flow syntax is the handling of empty collections.
Previously you had to use an <code class="language-plaintext highlighter-rouge">*ngIf</code> to display a message
if the collection was <code class="language-plaintext highlighter-rouge">null</code> or empty and then use <code class="language-plaintext highlighter-rouge">*ngFor</code> to iterate over the collection.</p>
<p>With the control flow syntax, you can do it with an <code class="language-plaintext highlighter-rouge">@empty</code> clause:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ul>
@for (user of users; track user.id) {
<li>{{ user.name }}</li>
} @empty {
<li>No users</li>
}
</ul>
</code></pre></div></div>
<p>We can still access the variables we used to have with <code class="language-plaintext highlighter-rouge">*ngFor</code>:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">$index</code> to get the index of the current item</li>
<li><code class="language-plaintext highlighter-rouge">$first</code> to know if the current item is the first one</li>
<li><code class="language-plaintext highlighter-rouge">$last</code> to know if the current item is the last one</li>
<li><code class="language-plaintext highlighter-rouge">$even</code> to know if the current item is at an even index</li>
<li><code class="language-plaintext highlighter-rouge">$odd</code> to know if the current item is at an odd index</li>
<li><code class="language-plaintext highlighter-rouge">$count</code> to get the length of the collection</li>
</ul>
<p>Unlike with <code class="language-plaintext highlighter-rouge">*ngFor</code>, you don’t have to alias these variables to use them,
but you still can if you need to, for example when using nested loops.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ul>
@for (user of users; track user.id; let isOdd = $odd) {
<li [class.grey]="isOdd">{{ $index }} - {{ user.name }}</li>
}
</ul>
</code></pre></div></div>
<p>It is also worth noting that the control flow <code class="language-plaintext highlighter-rouge">@for</code> uses
a new algorithm under the hood to update the DOM when the collection changes.
It should be quite a bit faster than the algorithm used by <code class="language-plaintext highlighter-rouge">*ngFor</code>,
as it does not allocate intermediate maps in most cases.
Combined with the required <code class="language-plaintext highlighter-rouge">track</code> property,
<code class="language-plaintext highlighter-rouge">for</code> loops should be way faster in Angular applications by default.</p>
<h2 id="switch-statement">Switch statement</h2>
<p>This is probably where the new type-checking shines the most,
as using an impossible value in a case will now throw a compilation error!</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@switch (user.type) {
@case ('user') {
<div>User</div>
} @case ('anonymous') {
<div>Anonymous</div>
} @default {
<div>Other</div>
}
}
</code></pre></div></div>
<p>Note that the switch statement does not support fall-through,
so you can’t have several cases grouped together.
It also does not check if all cases are covered,
so you won’t get a compilation error if you forget a case.
(but I hope it will, add a 👍 on <a href="https://github.com/angular/angular/issues/52107">this issue</a> if you want this as well!).</p>
<p>It’s also noteworthy that the <code class="language-plaintext highlighter-rouge">@switch</code> statement uses strict equality (<code class="language-plaintext highlighter-rouge">===</code>) to compare values,
whereas <code class="language-plaintext highlighter-rouge">*ngSwitch</code> used to use loose equality (<code class="language-plaintext highlighter-rouge">==</code>).
Angular v17 introduced a breaking change, and <code class="language-plaintext highlighter-rouge">*ngSwitch</code> now uses strict equality too,
with a warning in the console during development if you use loose equality:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NG02001: As of Angular v17 the NgSwitch directive
uses strict equality comparison === instead of == to match different cases.
Previously the case value "1" matched switch expression value "'1'",
but this is no longer the case with the stricter equality check.
Your comparison results return different results using === vs. ==
and you should adjust your ngSwitch expression and / or values
to conform with the strict equality requirements.
</code></pre></div></div>
<h2 id="the-future-of-templating-">The future of templating 🚀</h2>
<p>The control flow syntax is a new “developer preview” feature introduced in Angular v17,
and will probably be the recommended way to write templates in the future
(the plan is to make it stable in v18 once it has been battle-tested).</p>
<p>It doesn’t mean that structural directives will be deprecated,
but the Angular team will likely focus on the control flow syntax in the future
and push them forward as the recommended solution.</p>
<p>We will even have an automated migration to convert structural directives
to control flow statements in existing applications.
The migration is available in Angular v17 as a developer preview.
If you want to give it a try, run:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng g @angular/core:control-flow
</code></pre></div></div>
<p>This automatically migrates all your templates to the new syntax!
You can also run the migration against a single file with the <code class="language-plaintext highlighter-rouge">--path</code> option:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng g @angular/core:control-flow --path src/app/app.component.html
</code></pre></div></div>
<p>Even though the new control flow is experimental,
v17 comes with a mandatory migration needed to support this new control flow syntax,
which consists in converting the <code class="language-plaintext highlighter-rouge">@</code>, <code class="language-plaintext highlighter-rouge">{</code> and <code class="language-plaintext highlighter-rouge">}</code> characters used in your templates to their HTML entities.
This migration is run automatically when you update the app with <code class="language-plaintext highlighter-rouge">ng update</code>.</p>
<p>The future of Angular is exciting!</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Angular 16.2?2023-08-09T00:00:00+00:00https://blog.ninja-squad.com/2023/08/09/what-is-new-angular-16.2<p>Angular 16.2.0 is here!</p>
<p style="text-align: center;">
<a href="https://github.com/angular/angular/releases/tag/16.2.0">
<img class="rounded img-fluid" style="max-width: 100%" src="/assets/images/angular.png" alt="Angular logo" />
</a>
</p>
<p>This is a minor release with some nice features: let’s dive in!</p>
<h2 id="binding-inputs-of-ngcomponentoutlet">Binding inputs of NgComponentOutlet</h2>
<p>It used to be cumbersome to pass input data to a dynamic component
(you could do it, but you needed to use a provider and inject it).
It’s now way easier:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component({
selector: 'app-user',
standalone: true,
template: '{{ name }}'
})
export class UserComponent {
@Input({ required: true }) name!: string;
}
</code></pre></div></div>
<p>Then to dynamically insert the component with inputs:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component({
selector: 'app-root',
standalone: true,
imports: [NgComponentOutlet],
template: '<div *ngComponentOutlet="userComponent; inputs: userData"></div>'
})
class AppComponent {
userComponent = UserComponent;
userData = { name: 'Cédric' }
}
</code></pre></div></div>
<h2 id="afterrender-and-afternextrender">afterRender and afterNextRender</h2>
<p>The <code class="language-plaintext highlighter-rouge">afterRender</code> and <code class="language-plaintext highlighter-rouge">afterNextRender</code> lifecycle hooks have been added to the framework as developer preview APIs.
They are parts of the Signal API, see <a href="https://github.com/angular/angular/discussions/49682">the RFC discussion</a>.</p>
<p>They allow to run code after the component has been rendered the first time (<code class="language-plaintext highlighter-rouge">afterNextRender</code>), or after every render (<code class="language-plaintext highlighter-rouge">afterRender</code>).</p>
<p>The first one is useful to run code that needs to access the DOM, like calling a third-party library like we currently do in <code class="language-plaintext highlighter-rouge">ngAfterViewInit</code>.
But, unlike <code class="language-plaintext highlighter-rouge">ngAfterViewInit</code> and other lifecycle methods,
these hooks do not run during server-side rendering,
which makes them easier to use for SSR applications.</p>
<p>You can now write:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import { Component, ElementRef, ViewChild, afterNextRender } from '@angular/core';
@Component({
selector: 'app-chart',
standalone: true,
template: '<canvas #canvas></canvas>'
})
export class ChartComponent {
@ViewChild('canvas') canvas!: ElementRef<HTMLCanvasElement>;
constructor() {
afterNextRender(() => {
const ctx = this.canvas.nativeElement;
new Chart(ctx, { type: 'line', data: { ... } });
});
}
}
</code></pre></div></div>
<h2 id="routertestingharness">RouterTestingHarness</h2>
<p>The <code class="language-plaintext highlighter-rouge">RouterTestingHarness</code>, introduced in v15.2 (check out our <a href="/2023/02/23/what-is-new-angular-15.2">blog post</a>),
now exposes the underlying fixture, allowing to use its methods and properties and making it compatible with testing libraries that expect a fixture (like <a href="https://github.com/Ninja-Squad/ngx-speculoos">ngx-speculoos</a>).</p>
<h2 id="devtools">Devtools</h2>
<p>Some preliminary work has been done in the framework to trace what is injected in an application in dev mode.
This will be used in the future to improve the devtools experience,
by providing a way to see what is injected in a component, and where it comes from.</p>
<h2 id="angular-cli">Angular CLI</h2>
<p>The CLI has been updated to v16.2.0 as well, with a few new features:</p>
<ul>
<li>the esbuild builder now adds preload hints based on its analysis of the application initial files</li>
<li>the esbuild builder can now build the server bundle</li>
<li>the esbuild builder now has experimental support for serving the application in SSR mode with the Vite-based dev-server</li>
</ul>
<h2 id="summary">Summary</h2>
<p>That’s all for this release, stay tuned!</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Angular 16.1?2023-06-14T00:00:00+00:00https://blog.ninja-squad.com/2023/06/14/what-is-new-angular-16.1<p>Angular 16.1.0 is here!</p>
<p style="text-align: center;">
<a href="https://github.com/angular/angular/releases/tag/16.1.0">
<img class="rounded img-fluid" style="max-width: 100%" src="/assets/images/angular.png" alt="Angular logo" />
</a>
</p>
<p>This is a minor release with some nice features: let’s dive in!</p>
<h2 id="typescript-51-support">TypeScript 5.1 support</h2>
<p>Angular v16.1 now supports TypeScript 5.1. This means that you can use the latest version of TypeScript in your Angular applications. You can check out the <a href="https://devblogs.microsoft.com/typescript/announcing-typescript-5-1/">TypeScript 5.1 release notes</a> to learn more about the new features.</p>
<h2 id="transform-input-values">Transform input values</h2>
<p>Angular v16.1 introduces a new <code class="language-plaintext highlighter-rouge">transform</code> option in the <code class="language-plaintext highlighter-rouge">@Input</code> decorator.
It allows transforming the value passed to the input before it is assigned to the property.
The <code class="language-plaintext highlighter-rouge">transform</code> option takes a function that takes the value as input and returns the transformed value.
As the most common use cases are to transform a string to a number or a boolean, Angular provides two built-in functions to do that: <code class="language-plaintext highlighter-rouge">numberAttribute</code> and <code class="language-plaintext highlighter-rouge">booleanAttribute</code> in <code class="language-plaintext highlighter-rouge">@angular/core</code>.</p>
<p>Here is an example of using <code class="language-plaintext highlighter-rouge">booleanAttribute</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Input({ transform: booleanAttribute }) disabled = false;
</code></pre></div></div>
<p>This will transform the value passed to the input to a boolean so that the following code will work:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><my-component disabled></my-component>
<my-component disabled="true"></my-component>
<!-- Before, only the following was properly working -->
<my-component [disabled]="true"></my-component>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">numberAttribute</code> function works the same way but transforms the value to a number.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Input({ transform: numberAttribute }) value = 0;
</code></pre></div></div>
<p>It also allows to define a fallback value, in case the input is not a proper number (default is NaN):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Input({ transform: (value: unknown) => numberAttribute(value, 42) }) value = 0;
</code></pre></div></div>
<p>This can then be used like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><my-component value="42"></my-component>
<my-component value="not a number"></my-component>
<!-- Before, only the following was properly working -->
<my-component [value]="42"></my-component>
</code></pre></div></div>
<h2 id="fetch-backend-for-the-angular-http-client">Fetch backend for the Angular HTTP client</h2>
<p>The HTTP client has a new backend implementation based on the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">Fetch API</a>.</p>
<p>This is an experimental and opt-in feature, that you can enable with:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>provideHttpClient(withFetch());
</code></pre></div></div>
<p>It does not support the progress reports on uploads,
and of course, requires a browser that supports the Fetch API.
The fetch API is also experimental on Node but available without flags from Node 18 onwards.</p>
<p>This is mainly interesting for server-side rendering, as the XHR implementation is not supported natively in Node and requires a polyfill (which has some issues).</p>
<h2 id="angular-cli">Angular CLI</h2>
<p>The CLI now has a <code class="language-plaintext highlighter-rouge">--force-esbuild</code> option that allows forcing the usage of esbuild for <code class="language-plaintext highlighter-rouge">ng serve</code>.
It allows trying the esbuild implementation without switching the builder in <code class="language-plaintext highlighter-rouge">angular.json</code> (and keeping the Webpack implementation for the <code class="language-plaintext highlighter-rouge">ng build</code> command).</p>
<p>The esbuild builder has been improved. It now pre-bundles the dependencies using the underlying <a href="https://vitejs.dev/guide/dep-pre-bundling.html">Vite mechanism</a>, uses some persistent cache for the TypeScript compilation and Vite pre-bundling, and shows the estimated transfer sizes of the built assets as the Webpack builder does.</p>
<h2 id="summary">Summary</h2>
<p>That’s all for this release, stay tuned!</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Vue 3.3?2023-05-15T00:00:00+00:00https://blog.ninja-squad.com/2023/05/15/what-is-new-vue-3.3<p>Vue 3.3.0 is here!</p>
<p style="text-align: center;">
<a href="https://github.com/vuejs/core/blob/main/CHANGELOG.md#330-rurouni-kenshin-2023-05-11">
<img class="rounded img-fluid" style="max-width: 100%" src="/assets/images/vue.png" alt="Vue logo" />
</a>
</p>
<p>The last minor release was v3.2.0 in August 2021!
Since then, we have seen a lot of patch releases,
some coming with new features.</p>
<p>Originally, the v3.3 release was supposed to bring
Suspense and the Reactivity Transform APIs out of
their experimental state.</p>
<p>Is that the case?
Let’s see what we have in this release (and some interesting bits from the 47 patches since v3.2.0)!</p>
<h2 id="hello-reactivity-transform-and-goodbye">Hello Reactivity Transform, and goodbye!</h2>
<p>During the last year and a half, the Vue team pursued its experiments with ref sugar
(see our <a href="/2021/08/10/what-is-new-vue-3.2/">previous blog post</a> to catch up).</p>
<p>Currently, without ref sugar, you write code like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import { ref, computed, watchEffect } from 'vue';
const quantity = ref(0);
const total = computed(() => quantity.value * 10);
watchEffect(() => console.log(`New total ${total.value}`));
</code></pre></div></div>
<p>Note the <code class="language-plaintext highlighter-rouge">.value</code> that you need to access the value of the <code class="language-plaintext highlighter-rouge">quantity</code> or <code class="language-plaintext highlighter-rouge">total</code> ref.
If you use the Composition API, you’re used to it.</p>
<p>The reactivity transform experiment introduced new compiler macros like <code class="language-plaintext highlighter-rouge">$ref()</code> and <code class="language-plaintext highlighter-rouge">$computed()</code>.
When using these, the variable becomes reactive:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import { watchEffect } from 'vue';
const quantity = $ref(0);
const total = $computed(() => quantity * 10);
watchEffect(() => console.log(`New total ${total}`));
</code></pre></div></div>
<p>And <code class="language-plaintext highlighter-rouge">.value</code> was no longer necessary with this syntax!</p>
<p>But it turns out that this experiment is not quite as perfect as hoped initially.
It introduced another way to do the same thing, with quite a bit of “magic”,
additional pitfalls, and complexity.</p>
<p>So in the end, this experiment is now officially… dropped!</p>
<p>As some teams already started to use it, it will not be removed right away.
The plan is to phase these APIs out in a different package, add deprecation warnings in core,
and eventually remove them in v3.4.</p>
<p>It doesn’t mean that the team is not thinking about Vue how can be improved.
Some new ideas will probably be shared publicly soon.</p>
<p>And a part of the reactivity transform experiment is going to stay: the <code class="language-plaintext highlighter-rouge">defineProps</code> destructuration.
It’s <em>the</em> part I really liked, so I’m quite happy about it 🤓.</p>
<h2 id="defineprops-destructuration">defineProps destructuration</h2>
<p><code class="language-plaintext highlighter-rouge">defineProps</code> is the way to declare your props in the script setup syntax
(see <a href="/2021/09/30/script-setup-syntax-in-vue-3/">our article about script setup</a>).</p>
<p>The syntax plays well with TypeScript, but the declaration of default values was a bit painful:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const props = withDefaults(defineProps<{ name?: string }>(), { name: 'Hello '})
console.log(props.name);
</code></pre></div></div>
<p>You also can’t destructure the props directly, as it loses the reactivity.</p>
<p>With this new release, you can now give default values while destructuring the props and keeping the reactivity!</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const { name = 'Hello' } = defineProps<{ name?: string }>()
console.log(name);
</code></pre></div></div>
<p>If you try to use a destructured prop directly inside a watcher (or to <code class="language-plaintext highlighter-rouge">toRef</code>),
Vue will issue a warning and indicate to use a getter function instead:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>watch(name, () => {});
// "name" is a destructured prop and should not be passed directly to watch().
// Pass a getter () => name instead.
</code></pre></div></div>
<p>To help with this pattern, a new <code class="language-plaintext highlighter-rouge">toValue</code> helper function has been added
to convert refs and getters to values:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const v1 = toValue(ref('hello')); // 'hello'
const v2 = toValue(() => 'hello'); // 'hello'
</code></pre></div></div>
<p>If you want to give it a try, you’ll need to enable the <code class="language-plaintext highlighter-rouge">propsDestructure</code> option
in your bundler config. For example, in Vite:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>plugins: [
vue({
script: {
propsDestructure: true
}
})
</code></pre></div></div>
<h2 id="typescript-improvements">TypeScript improvements</h2>
<p>The TypeScript support of <code class="language-plaintext highlighter-rouge">defineProps</code> and other macros has been massively improved,
as pretty much all built-in types are now supported (<code class="language-plaintext highlighter-rouge">Extract</code>, <code class="language-plaintext highlighter-rouge">Exclude</code>, <code class="language-plaintext highlighter-rouge">Uppercase</code>, <code class="language-plaintext highlighter-rouge">Parameters</code>, etc.).
It also can now refer to types and interfaces imported from other files
(whereas it was only resolving local types previously).</p>
<p><code class="language-plaintext highlighter-rouge">defineEmits</code> has also been improved,
as it now supports a shorter TS declaration.
In Vue v3.2, we used to write the type like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const emit = defineEmits<{
(e: 'selected', value: number): void;
}>();
// emit('selected', 14)
</code></pre></div></div>
<p>There is now a simplified syntax in Vue v3.3.
You can use an interface with the events as keys,
and the arguments as tuples:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const emit = defineEmits<{
selected: [value: number]
}>();
</code></pre></div></div>
<p>Vue 3.3 also allows writing TypeScript directly in templates.
It can be handy to hint to <a href="https://github.com/johnsoncodehk/volar">Volar</a> that a
variable is not null, or of a particular type:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div>
<h2>Welcome {{ (user!.name as string).toLowerCase() }}</h2>
</div>
</code></pre></div></div>
<h2 id="generic-components">Generic components</h2>
<p><code class="language-plaintext highlighter-rouge">script setup</code> components can now have a generic parameter,
which works like a generic <code class="language-plaintext highlighter-rouge"><T></code> in TypeScript:</p>
<script setup="" lang="ts" generic="T">
defineProps<{ value: T, items: Array<T> }>()
</script>
<p>Volar is then capable to throw an error if
<code class="language-plaintext highlighter-rouge">value</code> is a <code class="language-plaintext highlighter-rouge">string</code> and <code class="language-plaintext highlighter-rouge">items</code> an array of numbers for example.</p>
<h2 id="component-name-inference">Component name inference</h2>
<p>When using the script setup syntax, the SFC compiler now infers the component name
based on the file name.</p>
<p>So a component declared in a file named <code class="language-plaintext highlighter-rouge">Home.vue</code> will automatically have the name <code class="language-plaintext highlighter-rouge">Home</code> since v3.2.34.</p>
<h2 id="defineoptions-macro">defineOptions macro</h2>
<p>A new macro (a compile-time helper like <code class="language-plaintext highlighter-rouge">defineProps</code> and <code class="language-plaintext highlighter-rouge">defineEmits</code>) has been introduced
to help declare the options of a component.
This is available only in <code class="language-plaintext highlighter-rouge">script setup</code> component,
and can be handy to declare a few things like the name of a component,
if the inferred name is not good enough
or to set the <code class="language-plaintext highlighter-rouge">inheritAttrs</code> option:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>defineOptions({ name: 'Home', inheritAttrs: true });
</code></pre></div></div>
<h2 id="defineslots-macro">defineSlots macro</h2>
<p>Another macro called <code class="language-plaintext highlighter-rouge">defineSlots</code> (and a <code class="language-plaintext highlighter-rouge">slots</code> option if you’re using <code class="language-plaintext highlighter-rouge">defineComponent</code>)
has been added to the framework to help declare typed slots.
When doing so, Volar will be able to check the slot props of a component.
Let’s say an <code class="language-plaintext highlighter-rouge">Alert</code> component has a default slot that exposes a <code class="language-plaintext highlighter-rouge">close</code> function:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>defineSlots<{
default: (props: { close: () => void }) => void;
}>();
</code></pre></div></div>
<p>If the <code class="language-plaintext highlighter-rouge">Alert</code> component is not used properly, then Volar throws an error:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><Alert><template #default="{ closeAlert }">...</template></Alert>
// error TS2339: Property 'closeAlert' does not exist on type '{ close: () => void; }'.
</code></pre></div></div>
<p>The returning value of <code class="language-plaintext highlighter-rouge">defineProps</code> can be used
and is the same object as returned by <code class="language-plaintext highlighter-rouge">useSlots</code>.</p>
<h2 id="experimental-definemodel-macro">experimental defineModel macro</h2>
<p>When you have a custom form component that just wants to bind the <code class="language-plaintext highlighter-rouge">v-model</code> value to a classic input, the prop/event mechanic we saw can be a bit cumbersome:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><template>
<input :value="modelValue" @input="setValue($event.target.value)" />
</template>
<script setup lang="ts">
defineProps<{ modelValue: string }>();
const emit = defineEmits<{ 'update:modelValue': [value: string] }>();
function setValue(pickedValue) {
emit('update:modelValue', pickedValue);
}
</script>
</code></pre></div></div>
<p>It is now possible to simplify this component,
by using the <code class="language-plaintext highlighter-rouge">defineModel</code> (experimental) macro:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><template>
<input v-model="modelValue" />
</template>
<script setup lang="ts">
const modelValue = defineModel<string>();
</script>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">defineModel</code> also accepts a few options:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">required: true</code> indicates that the prop is required</li>
<li><code class="language-plaintext highlighter-rouge">default: value</code> lets specify a default value</li>
<li><code class="language-plaintext highlighter-rouge">local: true</code> indicates that the prop is available and mutable even if the parent component did not pass the matching v-model</li>
</ul>
<p>A <code class="language-plaintext highlighter-rouge">useModel</code> helper is also available if you don’t use <code class="language-plaintext highlighter-rouge">script setup</code>.</p>
<p>Note that this feature is experimental and opt-in.
For example, in Vite:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>plugins: [
vue({
script: {
defineModel: true
}
})
</code></pre></div></div>
<h2 id="default-value-for-toref">default value for toRef</h2>
<p>It is now possible to define a default value when using <code class="language-plaintext highlighter-rouge">toRef()</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const order = { quantity: undefined }
const quantity = toRef(order, 'quantity', 1); // quantity is 1
</code></pre></div></div>
<p>Note that this works only if the value is <code class="language-plaintext highlighter-rouge">undefined</code>.</p>
<h2 id="isshallow">isShallow</h2>
<p>A new utility function called <code class="language-plaintext highlighter-rouge">isShallow</code> is now available.
It allows checking if a variable is deeply reactive (created with <code class="language-plaintext highlighter-rouge">ref</code> or <code class="language-plaintext highlighter-rouge">reactive</code>)
or “shallow” (created with <code class="language-plaintext highlighter-rouge">shallowRef</code> or <code class="language-plaintext highlighter-rouge">shallowReactive</code>).</p>
<h2 id="v-for-and-ref">v-for and ref</h2>
<p>Vue 3 now behaves like Vue 2 used to behave when using <code class="language-plaintext highlighter-rouge">ref</code> inside <code class="language-plaintext highlighter-rouge">v-for</code>:
it populates an array of refs.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script setup>
import { ref } from 'vue'
const divs = ref([])
</script>
<template>
<div v-for="i of 3" ref="divs">{{ i }}</div>
<!-- divs is populated with an array of 3 refs -->
<!-- one for each HTMLDivElement created -->
<div>{{ divs }}</div>
</template>
</code></pre></div></div>
<h2 id="aliases-for-vnode-hook-events">aliases for vnode hook events</h2>
<p>Vue allows you to listen for lifecycle events in templates, both for elements and components.
The syntax in Vue 3 is <code class="language-plaintext highlighter-rouge">@vnodeMounted</code> for example.
In Vue v3.3, it is now possible to use <code class="language-plaintext highlighter-rouge">@vue:mounted</code> instead,
which is a bit more understandable.
<code class="language-plaintext highlighter-rouge">@vnode</code> hooks are now deprecated.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script setup>
import { ref } from 'vue'
const isMounted = ref(false)
const onDivMounted = () => isMounted.value = true
const condition = ref(false)
setTimeout(() => condition.value = true, 3000)
</script>
<template>
<div>isMounted: {{ isMounted }}</div>
<div @vue:mounted="onDivMounted()" v-if="condition">Hello</div>
</template>
</code></pre></div></div>
<p>You can try this example in this <a href="https://play.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCB7IHJlZiB9IGZyb20gJ3Z1ZSdcblxuY29uc3QgaXNNb3VudGVkID0gcmVmKGZhbHNlKVxuY29uc3Qgb25EaXZNb3VudGVkID0gKCkgPT4gaXNNb3VudGVkLnZhbHVlID0gdHJ1ZSBcblxuY29uc3QgY29uZGl0aW9uID0gcmVmKGZhbHNlKVxuc2V0VGltZW91dCgoKSA9PiBjb25kaXRpb24udmFsdWUgPSB0cnVlLCAzMDAwKVxuPC9zY3JpcHQ+XG5cbjx0ZW1wbGF0ZT5cbiAgPGRpdj5pc01vdW50ZWQ6IHt7IGlzTW91bnRlZCB9fTwvZGl2PlxuICA8ZGl2IEB2dWU6bW91bnRlZD1cIm9uRGl2TW91bnRlZCgpXCIgdi1pZj1cImNvbmRpdGlvblwiPkhlbGxvPC9kaXY+XG48L3RlbXBsYXRlPiIsImltcG9ydC1tYXAuanNvbiI6IntcbiAgXCJpbXBvcnRzXCI6IHtcbiAgICBcInZ1ZVwiOiBcImh0dHBzOi8vdW5wa2cuY29tL0B2dWUvcnVudGltZS1kb21AMy4yLjI2L2Rpc3QvcnVudGltZS1kb20uZXNtLWJyb3dzZXIuanNcIlxuICB9XG59In0=">online demo</a>.</p>
<h2 id="suspensible-suspense">suspensible Suspense</h2>
<p><code class="language-plaintext highlighter-rouge">Suspense</code> is still experimental but gained a new prop called <code class="language-plaintext highlighter-rouge">suspensible</code>.</p>
<p>The prop allows the suspense to be captured by the parent suspense.
That can be useful if you have nested <code class="language-plaintext highlighter-rouge">Suspense</code> components,
as you can see in the <a href="https://github.com/vuejs/core/pull/6736">PR explanation</a>.</p>
<h2 id="console-available-in-templates">console available in templates</h2>
<p>A small (but useful when debugging) improvement in templates is the possibility
to directly use <code class="language-plaintext highlighter-rouge">console</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><input @input="console.log($event.target.value)">
</code></pre></div></div>
<p>To conclude, let’s see what happened in the ecosystem recently.</p>
<h2 id="create-vue">create-vue</h2>
<p>Since Vue v3.2, the Vue team started a new project called <a href="https://github.com/vuejs/create-vue">create-vue</a>,
which is now the recommended way to start a Vue project.
You can use it with</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm init vue@next
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">create-vue</code> is based on Vite v4,
and officially replaces Vue CLI.</p>
<p>If you missed it, <code class="language-plaintext highlighter-rouge">create-vue</code> recently added the support of Playwright in addition to Cypress for e2e tests!
It now also supports TypeScript v5 out of the box.</p>
<h2 id="router">Router</h2>
<p>Vue v3.3 introduced a new function on the object returned by <code class="language-plaintext highlighter-rouge">createApp</code>:
<code class="language-plaintext highlighter-rouge">runWithContext</code>.
The function allows using <code class="language-plaintext highlighter-rouge">inject</code> with the app as the active instance,
and get the value provided by the app providers.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const app = createApp(/* ... */);
app.provide('token', 1);
app.runWithContext(() => inject('token'));
</code></pre></div></div>
<p>If I mention this in the router section,
it’s because it unlocks the possibility to use <code class="language-plaintext highlighter-rouge">inject</code> in global navigation guards
if you use Vue v3.3 and the router v4.2!</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>router.beforeEach((to, from) => {
console.log(inject('token'));
});
</code></pre></div></div>
<h2 id="pinia">Pinia</h2>
<p><a href="https://pinia.vuejs.org/">Pinia</a> is a state-management library
from the author of vue-router <a href="https://twitter.com/posva">Eduardo “@posva”</a>.
It was meant as an experiment for Vuex v5,
but it turns out to be very good,
and it’s now the official recommendation for state-management library in Vue 3 projects.</p>
<p>The project moved into the vuejs organization, and there will be no Vuex version 5.
Pinia is a really cool project, with a great composition API and TS support,
and one of the cutest logos you’ve ever seen.</p>
<p>We added a complete chapter in <a href="https://books.ninja-squad.com/vue">our ebook</a>
to explain how Pinia works if you’re interested 🤓.</p>
<p>Eduardo also released <a href="https://vuefire.vuejs.org/">VueFire</a>,
the official Firebase bindings for Vue 3.
With this library, you can add Firebase to your Vue or Nuxt projects in a few minutes.</p>
<h2 id="nuxt">Nuxt</h2>
<p>After a long development, <a href="https://nuxt.com/">Nuxt</a> v3 is now stable!
It is a really amazing solution and the Nuxt team has been hard at work
to provide a great development experience (with some dark magic under the hood).
Give it a try if you’re looking for a meta-framework on top of Vue
(for example if you need SSR or SSG for your project).</p>
<h2 id="volar">Volar</h2>
<p><a href="https://github.com/johnsoncodehk/volar">Volar</a> reached v1.0 recently
after a very intense period of development these past months.
The TypeScript support is now better than ever,
making it a no-brainer to use in your projects.</p>
<h2 id="vue-test-utils">Vue Test utils</h2>
<p>The testing library has a few typings improvements coming in the v2.4 release,
and now supports SSR testing via <code class="language-plaintext highlighter-rouge">renderToString</code> since v2.3.</p>
<h2 id="vue-3-in-2023">Vue 3 in 2023</h2>
<p>The Vue team plans to release more frequent minor releases than in the past, so we can expect Vue v3.4 soon.
The next releases will be focused on bug fixes and small improvements in the first quarter of the year.
Then there should be some improvements for the SSR support in Q2.
Finally, the second half of the year should see the first alpha of Vapor.
We should hopefully also see Suspense finally getting out of its experimental state.</p>
<p>Vue Vapor is an alternative compilation mode to get better performances.
It’s not public yet, but we already know that it is inspired by what <a href="https://www.solidjs.com/">Solidjs</a> does,
as the reactivity system of Vue and Solid are fairly similar.
The idea is to compile a <code class="language-plaintext highlighter-rouge">script setup</code> component differently when the “Vapor” mode is enabled,
resulting in a lighter rendering code (not using VDOM).</p>
<p>Let’s say we have a classic Counter component:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script setup lang="ts">
let count = ref(0)
</script>
<template>
<div>
<button @click="count++">{{ count }}</button>
</div>
</template>
</code></pre></div></div>
<p>In the current Vue 3 compilation mode,
the template is compiled into a function that produces VDOM
which is then diffed and rendered
(check out the “Under the hood” chapter of our ebook if you want to learn more).
In Vapor mode, the template is compiled into a function that only updates what’s necessary in the DOM.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import { ref, effect } from 'vue';
import { setText, template, on } from 'vue/vapor';
let t0 = template('<div><button>');
export default () => {
const count = ref(0);
let div = t0();
let button = div.firstChild;
let button_text;
effect(() => {
// This is the only part that is executed at runtime when the counter value changes
setText(button, button_text, count.value);
});
on(button, 'click', () => count.value++);
return div;
}
</code></pre></div></div>
<p>This “Vapor” mode will be opt-in at the component level, probably for “leaf” components first.
To switch a component to Vapor, the current idea is to import it with a <code class="language-plaintext highlighter-rouge">.vapor.vue</code> extension:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script setup lang="ts">
// 👇 compiles the User component in Vapor mode
// you get an error if the component is not "Vapor" compatible
import User from './User.vapor.vue'
</script>
<template>
<User />
</template>
</code></pre></div></div>
<p>We’ll be able to enable it for a whole application in the future.
The current idea is to call a different <code class="language-plaintext highlighter-rouge">createApp</code> function from <code class="language-plaintext highlighter-rouge">vue/vapor</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import { createApp } from 'vue/vapor'
import App from './App.vapor.vue'
createApp(App).mount('#app')
</code></pre></div></div>
<p>When enabled for a full application, the VDOM implementation could be completely dropped from the resulting bundle!
We can’t wait to try this!</p>
<p>That’s all for this release. Stay tuned for the next one!</p>
<p>Our <a href="https://books.ninja-squad.com/vue">ebook</a>, <a href="https://vue-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/vue">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Angular 16?2023-05-03T00:00:00+00:00https://blog.ninja-squad.com/2023/05/03/what-is-new-angular-16.0<p>Angular 16.0.0 is here!</p>
<p style="text-align: center;">
<a href="https://github.com/angular/angular/releases/tag/16.0.0">
<img class="rounded img-fluid" style="max-width: 100%" src="/assets/images/angular.png" alt="Angular logo" />
</a>
</p>
<p>This is a major release packed with features: let’s dive in!</p>
<h2 id="angular-signals">Angular Signals</h2>
<p>As you may have heard, all the hype around Angular is about the addition of Signals to the framework. As this is a big change that will shape how we build Angular applications in the future, we wrote an introduction to Signals, to cover what you can do with them in v16 and what to expect in the future:</p>
<p>👉 <a href="/2023/04/26/angular-signals/">Angular Signals</a></p>
<p>Note that Signals are released as a developer preview in v16 and the API may change in the future.</p>
<p>As Signals are progressing, Angular now allows configuring ZoneJS explicitly with <code class="language-plaintext highlighter-rouge">provideZoneChangeDetection</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bootstrapApplication(AppComponent, {
providers: [provideZoneChangeDetection({eventCoalescing: true})],
});
</code></pre></div></div>
<p>This opens the door to zoneless applications in the near future,
where developers could choose to not include ZoneJS in their application.</p>
<h2 id="required-inputs">Required inputs</h2>
<p>Angular v16 added the possibility to mark an input as required,
with <code class="language-plaintext highlighter-rouge">@Input({ required: true})</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Input({ required: true }) user!: UserModel;
</code></pre></div></div>
<p>In that case, if the parent component does not pass the input,
then the compiler will throw an error.</p>
<p>This has been a long-awaited feature, we’re happy to see this land in Angular!</p>
<h2 id="server-side-rendering-and-progressive-hydration">Server-Side Rendering and progressive hydration</h2>
<p>Angular has been supporting Server-Side Rendering (SSR) for a while now,
but it was a bit limited as it was only possible to render the whole application on the server, and then re-render it on the client when the JavaScript bundle was loaded.
This was resulting in a flickering when the application loaded,
as the DOM was completely wiped out before being re-rendered.</p>
<p>Angular v16 introduces “progressive hydration”,
which allows rendering the application on the server,
and then progressively hydrate it on the client.</p>
<p>This means that the server-rendered DOM is not wiped out anymore,
and the client-side rendering is done progressively,
which results in a much smoother experience for the user.</p>
<p>To enable these new behaviors, you simply add <code class="language-plaintext highlighter-rouge">provideClientHydration()</code> to your providers:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bootstrapApplication(AppComponent, {
providers: [provideClientHydration()]
});
</code></pre></div></div>
<p>The HttpClient has also been updated to be able
to store the result of a request done on the server,
and then reuse it on the client during the hydration process!
The behavior is enabled by default if you use <code class="language-plaintext highlighter-rouge">provideClientHydration()</code>,
but can be disabled with <code class="language-plaintext highlighter-rouge">provideClientHydration(withNoHttpTransferCache())</code>.
You can also disable the DOM reuse with <code class="language-plaintext highlighter-rouge">withNoDomReuse()</code>.</p>
<p>Note that this is a developer preview, and the API may change in the future.
There are also a few pitfalls to be aware of.
For example, the HTML must be valid when generated on the server (whereas the browser is more forgiving).
The DOM must also be the same on the server and the client,
so you can’t manipulate the server-rendered DOM before sending it to the client.
If some parts of your templates don’t produce the same result on the server and the client, you can skip them by adding <code class="language-plaintext highlighter-rouge">ngSkipHydration</code> to the element or component.
i18n is also not supported yet, but that should come soon.</p>
<p>When running in development mode, the application will output some stats to the console to help you debug the hydration process:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Angular hydrated 19 component(s) and 68 node(s), 1 component(s) were skipped
</code></pre></div></div>
<p>You can easily give this a try by using Angular Universal.
In the long term, this will probably be part of the CLI directly.</p>
<h2 id="destroyref">DestroyRef</h2>
<p>Angular v16 introduces a new <code class="language-plaintext highlighter-rouge">DestroyRef</code> class,
which has only one method called <code class="language-plaintext highlighter-rouge">onDestroy</code>.</p>
<p><code class="language-plaintext highlighter-rouge">DestroyRef</code> can be injected,
and then used to register code that should run
on the destruction of the surrounding context.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const destroyRef = inject(DestroyRef);
// register a destroy callback
destroyRef.onDestroy(() => doSomethingOnDestroy());
</code></pre></div></div>
<p>For example, it can be used to execute code on the destruction
of a component or directive (as we do now with <code class="language-plaintext highlighter-rouge">ngOnDestroy</code>).
But this is more useful for cases where you want to execute code
when a component is destroyed, but you don’t have access to the component itself,
for example when defining a utility function.</p>
<p>This is exactly what Angular uses internally to implement <code class="language-plaintext highlighter-rouge">takeUntilDestroyed</code>,
the new RXJS operator introduced in our <a href="/2023/04/26/angular-signals/">Signals blog post</a>.</p>
<h2 id="provideserviceworker">provideServiceWorker</h2>
<p>One of the last modules that needed to be transitioned to a standalone provider function was <code class="language-plaintext highlighter-rouge">ServiceWorkerModule</code>. It is now done with <code class="language-plaintext highlighter-rouge">provideServiceWorker</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bootstrapApplication(AppComponent, {
providers: [provideServiceWorker('ngsw-worker.js', { enabled: !isDevMode() })]
});
</code></pre></div></div>
<p>It, of course, accepts the same options as the <code class="language-plaintext highlighter-rouge">ServiceWorkerModule</code>.
Running <code class="language-plaintext highlighter-rouge">ng add @angular/pwa</code> will now add <code class="language-plaintext highlighter-rouge">provideServiceWorker</code> to your providers
if your application is a standalone one.</p>
<h2 id="typescript-50-support">TypeScript 5.0 support</h2>
<p>Angular v16 now supports TypeScript 5.0. This means that you can use the latest version of TypeScript in your Angular applications. You can check out the <a href="https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/">TypeScript 5.0 release notes</a> to learn more about the new features.</p>
<p>One important point is that TypeScript now supports the “official” decorator specification.
Their “experimental decorators” (based on a much older specification) are still supported but are now considered legacy. One of the differences between these two specifications is that the legacy one supports decorators on parameters, which is used by Angular for dependency injection (with <code class="language-plaintext highlighter-rouge">@Optional</code>, <code class="language-plaintext highlighter-rouge">@Inject</code>, etc.), and the new one doesn’t.</p>
<p>It is now possible to use the new decorator specification in Angular, but it requires a few changes in your code, as you can’t use decorators on parameters anymore.
This can usually be worked around by using the <code class="language-plaintext highlighter-rouge">inject()</code> function from <code class="language-plaintext highlighter-rouge">@angular/core</code>.
There is no rush to use the new decorators instead of the “legacy” ones, but it’s something to keep in mind as I wouldn’t be surprised if we have to migrate away from <code class="language-plaintext highlighter-rouge">experimentalDecorators</code> in the future</p>
<h2 id="styles-removal-opt-in">Styles removal opt-in</h2>
<p>Angular v16 introduces a new opt-in feature to remove the styles of a component
when its last instance is destroyed.</p>
<p>This will be the default behavior in the future, but you can already opt in with:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{ provide: REMOVE_STYLES_ON_COMPONENT_DESTROY, useValue: true }
</code></pre></div></div>
<h2 id="router">Router</h2>
<p>Angular v15.2 deprecated the usage of class-based guards and resolvers
(check out our <a href="/2023/02/23/what-is-new-angular-15.2/">blog post for more details</a>).
In Angular v16, a migration will run to remove the guard and resolver interfaces from your code (<code class="language-plaintext highlighter-rouge">CanActivate</code>, <code class="language-plaintext highlighter-rouge">Resolve</code>, etc.).</p>
<p>To help with the conversion, the router now offers helper functions to convert class-based entities to their function-based equivalent:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">mapToCanActivate</code></li>
<li><code class="language-plaintext highlighter-rouge">mapToCanActivateChild</code></li>
<li><code class="language-plaintext highlighter-rouge">mapToCanDeactivate</code></li>
<li><code class="language-plaintext highlighter-rouge">mapToCanMatch</code></li>
<li><code class="language-plaintext highlighter-rouge">mapToResolve</code></li>
</ul>
<p>For example, you can now write:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{ path: 'admin', canActivate: mapToCanActivate([AdminGuard]) };
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">RouterTestingModule</code> is also getting phased out, and will probably be deprecated and removed in the future. It is not needed anymore, because Angular v16 now provides <code class="language-plaintext highlighter-rouge">MockPlatformLocation</code> in <code class="language-plaintext highlighter-rouge">BrowserTestingModule</code> by default, which was the main reason to use <code class="language-plaintext highlighter-rouge">RouterTestingModule</code> in the first place.</p>
<p>You can now directly use <code class="language-plaintext highlighter-rouge">RouterModule.forRoot([])</code> or <code class="language-plaintext highlighter-rouge">providerRouter([])</code> in your tests.</p>
<p>Last but not least, the router now offers the possibility to bind parameters as inputs.</p>
<p>To do so, you need to configure the router with <code class="language-plaintext highlighter-rouge">withComponentInputBinding</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>provideRouter(routes, withComponentInputBinding())
</code></pre></div></div>
<p>With this option, a component can declare an input with the same name as a route parameter,
query parameter or data, and Angular will automatically bind the value of the parameter or data to this input.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export class RaceComponent implements OnChanges {
@Input({ required: true }) raceId!: string;
</code></pre></div></div>
<p>We can then use this input as a regular input, and react to its change with ngOnChanges or by using a setter for this input:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>constructor(private raceService: RaceService) {}
ngOnChanges() {
this.raceModel$ = this.raceService.get(this.raceId);
}
</code></pre></div></div>
<h2 id="angular-cli">Angular CLI</h2>
<p>Check out our <a href="/2023/05/03/angular-cli-16.0/">dedicated blog post about the CLI</a> for more details.</p>
<h2 id="summary">Summary</h2>
<p>That’s all for this release, stay tuned!</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Angular CLI 16.0?2023-05-03T00:00:00+00:00https://blog.ninja-squad.com/2023/05/03/angular-cli-16.0<p><a href="https://github.com/angular/angular-cli/releases/tag/16.0.0">Angular CLI 16.0.0</a> is out!✨</p>
<p>If you want to upgrade to 16.0.0 without pain (or to any other version, by the way), I have created a Github project to help: <a href="https://github.com/cexbrayat/angular-cli-diff">angular-cli-diff</a>. Choose the version you’re currently using (14.2.0 for example), and the target version (16.0.0 for example), and it gives you a diff of all files created by the CLI: <a href="https://github.com/cexbrayat/angular-cli-diff/compare/14.2.0...16.0.0">angular-cli-diff/compare/14.2.0…16.0.0</a>.
It can be a great help along with the official <code class="language-plaintext highlighter-rouge">ng update @angular/core @angular/cli</code> command.
You have no excuse for staying behind anymore!</p>
<p>Let’s see what we’ve got in this release.</p>
<h2 id="standalone-applications">Standalone applications!</h2>
<p>It is now possible to generate standalone applications with the CLI,
thanks to the <code class="language-plaintext highlighter-rouge">--standalone</code> flag!</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng new my-app --standalone
</code></pre></div></div>
<p>This is great to start new projects 😍</p>
<p>now that the option is available, we switched all the exercises of our
<a href="https://angular-exercises.ninja-squad.com/">Angular training</a> to use a standalone application
with standalone components.</p>
<p>We also re-wrote our ebook entirely to now introduce standalone concepts from the beginning,
and add the concept of modules only when it makes sense!</p>
<h2 id="esbuild-builder-is-now-in-developer-preview">esbuild builder is now in developer preview!</h2>
<p>The new builder that uses <a href="https://esbuild.github.io/">esbuild</a>
has been introduced in v14 but was experimental.
It is now in developer preview, so you can give it a try.
Even if some options are still unsupported,
most of them are now available (autoprefixer and tailwind support have been added),
and the builder should be usable.</p>
<p>To check it out in your project,
replace <code class="language-plaintext highlighter-rouge">@angular-devkit/build-angular:browser</code>
with <code class="language-plaintext highlighter-rouge">@angular-devkit/build-angular:browser-esbuild</code>,
and run <code class="language-plaintext highlighter-rouge">ng build</code>.
For a small application, on a cold production build (after <code class="language-plaintext highlighter-rouge">ng cache clean</code>), the build time went from 13s to 6s on my machine.
For a larger application, the build time went from 1m12s to 32s 🤯.</p>
<p>Note that the esbuild builder now uses <a href="https://vitejs.dev/">Vite</a> under the hood for the development server. Even if the integration is not 100% complete and optimized,
it is promising to see this Webpack alternative being used in the Angular ecosystem.</p>
<h2 id="functional-guards-and-resolvers-by-default">Functional guards and resolvers by default</h2>
<p>The CLI now generates functional guards and resolvers by default,
without the need to specify <code class="language-plaintext highlighter-rouge">--functional</code> anymore.
Class-based guards and resolvers are still available with the <code class="language-plaintext highlighter-rouge">--no-functional</code> option,
but as they are now deprecated, you’re encouraged to use the functional ones.</p>
<h2 id="jest-experimental-support">Jest experimental support</h2>
<p>The CLI now supports Jest as a test runner, but it is still experimental.</p>
<p>To check it out in your project,
replace <code class="language-plaintext highlighter-rouge">@angular-devkit/build-angular:karma</code>
with <code class="language-plaintext highlighter-rouge">@angular-devkit/build-angular:jest</code>,
and run <code class="language-plaintext highlighter-rouge">ng test</code>.</p>
<p>The support is far from being complete, but it is promising.
It uses the new esbuild builder under the hood,
and the support should improve in the next releases.</p>
<p>Jest does not run in a browser and uses JSDOM instead.
That means that you don’t have the same experience that you can have with Karma,
but as Karma is now deprecated, an alternative is welcome.</p>
<p>The Angular team also announced that they are working on the support of
<a href="https://modern-web.dev/docs/test-runner/overview/">Web Test Runner</a>,
to have an alternative to Karma that runs in a browser.</p>
<h2 id="bye-bye-compilecomponents">Bye-bye compileComponents!</h2>
<p>The <code class="language-plaintext highlighter-rouge">compileComponents</code> call generated in the unit tests of the CLI is now gone,
as it was useless since the very beginning if you were running your tests with the CLI.
My PR to remove it was merged in the framework, and the CLI now generates tests without it 🤓.</p>
<h2 id="optional-migrations">Optional migrations</h2>
<p>The CLI now supports optional migrations and will ask you if you want to run them when you upgrade to a new version.</p>
<h2 id="ssr-support">SSR support</h2>
<p>A ton of work has been done to improve the SSR story with the CLI,
along with what the framework team did with the progressive hydration.</p>
<p>For example, it is now possible to add universal or to generate an app-shell with the CLI for a standalone application.</p>
<h2 id="summary">Summary</h2>
<p>That’s all for the CLI v16.0 release!
You’ll find more interesting features in our article about the
<a href="/2023/05/03/what-is-new-angular-16.0">framework v16.0.0 release</a>.</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
An introduction to Angular Signals2023-04-26T00:00:00+00:00https://blog.ninja-squad.com/2023/04/26/angular-signals<p>The Angular team has been working on a different way to handle reactivity in applications for the past year.
The result of their work is a new concept in Angular called Signals.</p>
<p>Let’s see what they are, how they work, how they interoperate with RxJS, and what they’ll change for Angular developers.</p>
<h2 id="the-reasons-behind-signals">The reasons behind Signals</h2>
<p>Signals are a concept used in many other frameworks, like SolidJS, Vue, Preact, and even the venerable KnockoutJS.
The idea is to offer a few primitives to define reactive state in applications
and to allow the framework to know which components are impacted by a change, rather than having to detect changes on the whole tree of components.</p>
<p>This will be a significant change to how Angular works, as it currently relies on zone.js to detect changes in the whole tree of components by default.
Instead, with signals, the framework will only dirty-check the components that are impacted by a change,
which of course makes the re-rendering process more efficient.</p>
<p>This opens the door to zoneless applications, i.e. applications where Angular applications don’t need to include Zone.js (which makes them lighter), don’t have to patch all the browser APIs (which makes them start faster)
and smarter in their change detection (to only check the components impacted by a change).</p>
<p>Signals are released in Angular v16, as a developer preview API, meaning there might be changes in the naming or behavior in the future.
But this preview is only a small part of the changes that will come with signals.
In the future, signal-based components with inputs and queries based on signals,
and different lifecycle hooks, will be added to Angular.
Other APIs like the router parameters and form control values and status, etc. should also be affected.</p>
<h2 id="signals-api">Signals API</h2>
<p>A signal is a function that holds a value that can change over time.
To create a signal, Angular offers a <code class="language-plaintext highlighter-rouge">signal()</code> function:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import { signal } from '@angular/core';
// define a signal
const count = signal(0);
</code></pre></div></div>
<p>The type of <code class="language-plaintext highlighter-rouge">count</code> is <code class="language-plaintext highlighter-rouge">WritableSignal<number></code>, which is a function that returns a number.</p>
<p>When you want to get the value of a signal, you have to call the created signal:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// get the value of the signal
const value = count();
</code></pre></div></div>
<p>This can be done both in TypeScript code and in HTML templates:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><p>{{ count() }}</p>
</code></pre></div></div>
<p>You can also set the value of a signal:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// set the value of the signal
count.set(1);
</code></pre></div></div>
<p>Or update it:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// update the value of the signal, based on the current value
count.update((value) => value + 1);
</code></pre></div></div>
<p>There is also a <code class="language-plaintext highlighter-rouge">mutate</code> method that can be used to mutate the value of a signal:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// mutate the value of the signal (handy for objects/arrays)
const user = signal({ name: 'JB', favoriteFramework: 'Angular' });
user.mutate((user) => user.name = 'Cédric');
</code></pre></div></div>
<p>You can also create a readonly signal, that can’t be updated, with <code class="language-plaintext highlighter-rouge">asReadonly</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const readonlyCount = count.asReadonly();
</code></pre></div></div>
<p>Once you have defined signals, you can define computed values that derive from them:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const double = computed(() => count() * 2);
</code></pre></div></div>
<p>Computed values are automatically computed when one of the signals they depend on changes.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>count.set(2);
console.log(double()); // logs 4
</code></pre></div></div>
<p>Note that they are lazily computed and only re-computed
when one of the signals they depend on produces a new value.
They are not writable, so you can’t use <code class="language-plaintext highlighter-rouge">.set()</code> or <code class="language-plaintext highlighter-rouge">.update()</code> or <code class="language-plaintext highlighter-rouge">.mutate()</code> on them
(their type is <code class="language-plaintext highlighter-rouge">Signal<T></code>).</p>
<p>Under the hood, a signal has a set of subscribers, and when the value of the signal changes, it notifies all its subscribers.
Angular does that in a smart way, to avoid recomputing everything when a signal changes,
by using internal counters to know which signals have really changed since the last time a computed value was computed.</p>
<p>Finally, you can use the <code class="language-plaintext highlighter-rouge">effect</code> function to react to changes in your signals:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// log the value of the count signal when it changes
effect(() => console.log(count()));
</code></pre></div></div>
<p>An <code class="language-plaintext highlighter-rouge">effect</code> returns an object with a <code class="language-plaintext highlighter-rouge">destroy</code> method that can be used to stop the effect:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const effectRef: EffectRef = effect(() => console.log(count()));
// stop executing the effect
effectRef.destroy();
</code></pre></div></div>
<p>This does look like a <code class="language-plaintext highlighter-rouge">BehaviorSubject</code>, but it has some subtle differences, the most important one being that unsubscribing is unnecessary thanks to the usage of
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakRef">weak references</a>.</p>
<p>An <code class="language-plaintext highlighter-rouge">effect</code> or a <code class="language-plaintext highlighter-rouge">computed</code> value will re-evaluate when one of the signals they depend on changes,
without any action from the developer. This is usually what you want.</p>
<p>Note that while signals and computed values will be common in Angular applications,
effects will be used less often, as they are more advanced and low-level primitive.
If you need to react to a signal change,
for example, to fetch data from a server, you can use the RxJS interoperability layer
that we detail below.</p>
<p>If you want to exclude a signal from the dependencies of an effect or computed value,
you can use the <code class="language-plaintext highlighter-rouge">untracked</code> function:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const multiplier = signal(2);
// `total` will not re-evaluate when `multiplier` changes
// it only re-evaluates when `count` changes
const total = computed(() => count() * untracked(() => multiplier()));
</code></pre></div></div>
<p>You <em>can’t</em> write to a signal inside a computed value, as it would create an infinite loop:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// this will throw an error NG0600
const total = computed(() => { multiplier.set(2); return count() * multiplier() });
</code></pre></div></div>
<p>This is the same in effect, but this can be overridden using <code class="language-plaintext highlighter-rouge">allowSignalWrites</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// this will not throw an error
effect(
() => {
if (this.count() > 10) {
this.count.set(0);
}
},
{ allowSignalWrites: true }
);
</code></pre></div></div>
<p>All these features (and terminology) are fairly common in other frameworks,
so you won’t be surprised if you used SolidJS or Vue 3 before.</p>
<p>One feature that I <em>think</em> is fairly unique to Angular is the possibility to pass a <code class="language-plaintext highlighter-rouge">ValueEqualityFn</code> to the <code class="language-plaintext highlighter-rouge">signal</code> function.
To decide if a signal changed (and know if a computed or an effect need to run),
Angular uses <code class="language-plaintext highlighter-rouge">Object.is()</code> by default, but you can pass a custom function to compare the old and new values:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const user = signal({ id: 1, name: 'Cédric' }, { equal: (previousUser, newUser) => previousUser.id === newUser.id });
// upperCaseName will not re-evaluate when the user changes if the ID stays the same
const uppercaseName = computed(() => user().name.toUpperCase());
</code></pre></div></div>
<h2 id="signals-components-and-change-detection">Signals, components, and change detection</h2>
<p>As mentioned above, you can use signals and computed values in your templates:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><p>{{ count() }}</p>
</code></pre></div></div>
<p>If the counter value changes, Angular detects it and re-renders the component.</p>
<p>But what happens if the component is marked as <code class="language-plaintext highlighter-rouge">OnPush</code>? Until now, <code class="language-plaintext highlighter-rouge">OnPush</code> meant that Angular would only re-render the component if one of its inputs changed, if an <code class="language-plaintext highlighter-rouge">async</code> pipe was used in the template, or if the component used <code class="language-plaintext highlighter-rouge">ChangeDetectorRef#markForCheck()</code>.</p>
<p>The framework now handles another reason to re-render a component: when a signal changes.
Let’s consider the following component (not using a signal, but a simple field that will be updated after 2 seconds):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component({
selector: 'app-user',
standalone: true,
templateUrl: './user.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserComponent {
count = 0;
constructor() {
// set the counter to 1 after 2 seconds
setTimeout(() => this.count = 1, 2000);
}
}
</code></pre></div></div>
<p>As the component is <code class="language-plaintext highlighter-rouge">OnPush</code>, using a <code class="language-plaintext highlighter-rouge">setTimeout</code> will trigger a change detection, but the component will not be re-rendered (as it won’t be marked as “dirty”).</p>
<p>But if we use a signal instead:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component({
selector: 'app-user',
standalone: true,
templateUrl: './user.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserComponent {
count = signal(0);
constructor() {
// set the counter to 1 after 2 seconds
setTimeout(() => this.count.set(1), 2000);
}
}
</code></pre></div></div>
<p>Then the component will be re-rendered after 2 seconds, as Angular detects the signal change and refresh the template.</p>
<p>Under the hood, if a view reads a signal value, then Angular marks the template of the component as a consumer of the signal.
Then, when the signal value changes, it marks the component and all its ancestors as “dirty”.</p>
<p>For us developers, it means that we can use signals in our components as soon as Angular v16 is released,
and we don’t need to worry about change detection, even if the components are using OnPush.</p>
<h2 id="sharing-a-signal-between-components">Sharing a signal between components</h2>
<p>In the previous example, the signal was defined in the component itself.
But what if we want to share a signal between multiple components?</p>
<p>In the Vue ecosystem, you frequently encounter the pattern of “composables”:
functions that return an object containing signals, computed values, and functions to manipulate them.
If a signal needs to be shared, it is defined outside of the function and returned by the function:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const count = signal(0);
export function useCount() {
return {
count,
increment: () => count.set(count() + 1)
};
}
</code></pre></div></div>
<p>In Angular, we can do the same and we can also use services instead of functions (and it’s probably what we’ll do).
We can define a <code class="language-plaintext highlighter-rouge">CountService</code> as the following:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Injectable({ providedIn: 'root' })
export class CountService {
count = signal(0);
}
</code></pre></div></div>
<p>Then, in our components, we can inject the service and use the signal:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component({
selector: 'app-user',
standalone: true,
templateUrl: './user.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserComponent {
count = inject(CountService).count;
}
</code></pre></div></div>
<p>The service could also define computed values, effects, methods to manipulate the signals, etc.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Injectable({ providedIn: 'root' })
export class CountService {
count = signal(0);
double = computed(() => this.count() * 2);
constructor() {
effect(() => console.log(this.count()));
}
increment() {
this.count.update(value => value + 1);
}
}
</code></pre></div></div>
<h2 id="memory-leaks">Memory leaks</h2>
<p>Signal consumers and producers are linked together using weak references,
which means that if all the consumers of a signal cease to exist,
then the signal will be garbage collected as well.
In other terms: no need to worry about “unsubscribing” from a signal to prevent memory leaks,
as we have to do with RxJS observables \o/.</p>
<p>You can also use an <code class="language-plaintext highlighter-rouge">effect</code> in your component (even if that’s probably going to be very rare) to watch a value and react to its changes,
for example, the <code class="language-plaintext highlighter-rouge">count</code> defined in a service like the above.</p>
<p>Note that you don’t have to manually stop an effect when the component is destroyed, as Angular will do it for you to prevent memory leaks.
Under the hood, an effect uses a <code class="language-plaintext highlighter-rouge">DestroyRef</code>, a new feature introduced in Angular v16, to automatically be cleaned up when the component or service is destroyed.</p>
<p>You can change this behavior though, by creating an effect with a specific option:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.logEffect = effect(() => console.log(count()), { manualCleanup: true });
</code></pre></div></div>
<p>In this case, you will have to manually stop the effect when the component is destroyed:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ngOnDestroy() {
this.logEffect.destroy();
}
</code></pre></div></div>
<p>Effects can also receive a cleanup function, that is run when the effect runs again.
This can be handy when you need to stop a previous action before starting a new one.
In the example below, we start an interval that runs every <code class="language-plaintext highlighter-rouge">count</code> milliseconds,
and we want to stop it and start a new one when the count changes:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.intervalEffect = effect(
(onCleanup) => {
const intervalId = setInterval(() => console.log(`count in intervalEffect ${this.count()}`), this.count());
return onCleanup(() => clearInterval(intervalId))
}
)
</code></pre></div></div>
<p>Note that effects run during change detection, so they’re not a good place to set signal values.
That’s why you get an error from Angular if you try to do so:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// ERROR: Cannot set a signal value during change detection
</code></pre></div></div>
<p>Effects will probably be used very rarely, but they can be handy in some cases:</p>
<ul>
<li>logging / tracing;</li>
<li>synchronizing state to the DOM or to a storage, etc.</li>
</ul>
<h2 id="signals-and-rxjs-interoperability">Signals and RxJS interoperability</h2>
<p>RxJS is here to stay, even if its usage might be more limited in the future.
Angular is not going to remove RxJS, and it’s not going to force us to use signals instead of observables.
In fact, RxJS is probably a better way to react to signal changes than using effects.</p>
<p>We can use signals and observables together, and we can convert one into the other. Two functions to do that are available in the brand new <code class="language-plaintext highlighter-rouge">@angular/core/rxjs-interop</code> package.</p>
<p>To convert a signal to an observable, we can use the <code class="language-plaintext highlighter-rouge">toObservable</code> function:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const count$ = toObservable(count);
</code></pre></div></div>
<p>Note that the created observable will not receive all the value changes of the signal,
as this is done using an effect under the hood, and effects are only run during change detection:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const count = signal(0);
const count$ = toObservable(count);
count$.subscribe(value => console.log(value));
count.set(1);
count.set(2);
// logs only 2, not 0 and 1, as this is the value when the under-the-hood effect runs
</code></pre></div></div>
<p>To convert an observable to a signal, we can use the <code class="language-plaintext highlighter-rouge">toSignal</code> method:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const count = toSignal(count$);
</code></pre></div></div>
<p>The signal will contain the last value emitted by the observable,
or will throw an error if the observable emits an error.
Note that the subscription created by <code class="language-plaintext highlighter-rouge">toSignal()</code> is automatically unsubscribed
when the component that declared it is destroyed.
As observables can be asynchronous, you can pass an initial value to the function:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const count = toSignal(count$, { initialValue: 0 });
</code></pre></div></div>
<p>If you do not provide an initial value, the value is <code class="language-plaintext highlighter-rouge">undefined</code> if it is read before the observable emits a value.
You can also use the option <code class="language-plaintext highlighter-rouge">requireSync</code> to make the signal throw an error if it is read before the observable emits a value:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const count = toSignal(count$, { requireSync: true });
</code></pre></div></div>
<h2 id="signal-based-components">Signal-based components</h2>
<p>In the future (v17? v18?),
we’ll be able to build a component entirely based on signals, even for its inputs and queries.
The framework would be notified when an expression has changed thanks to the signals, and would thus only need to dirty-checks the components affected by the change, without having to check for changes on unrelated components, without the need for zone.js. It will even be able to re-render only the part of the template that has changed,
instead of checking the whole template of a component as it currently does.
But there is a long way ahead, as several things need to be rethought in the framework to make this work
(inputs, outputs, queries, lifecycle methods, etc).</p>
<p>An RFC is out with the details of the proposal, and you can follow the progress on the <a href="https://github.com/angular/angular/discussions/49685">Angular repository</a>.</p>
<p>Currently, the RFC proposes to use <code class="language-plaintext highlighter-rouge">signals: true</code> to mark a component as “Signal-based”:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component({
signals: true,
selector: 'temperature-calc',
template: `
<p>C: {{ celsius() }}</p>
<p>F: {{ fahrenheit() }}</p>
`,
})
export class SimpleCounter {
celsius = signal(25);
// The computed only re-evaluates if celsius() changes.
fahrenheit = computed(() => this.celsius() * 1.8 + 32);
}
</code></pre></div></div>
<p>Inputs, outputs, and queries would be defined via functions instead of decorators in these components,
and would return a signal:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>firstName = input<string>(); // Signal<string|undefined>
</code></pre></div></div>
<p>Nothing has been implemented for this part yet, so you can’t try this in v16.
But you can already try to use signals in existing components, as we mentioned above
(but keep in mind they are not production ready)</p>
<p>It’s anyway quite interesting how frameworks inspire each other,
with Angular taking inspiration from Vue and SolidJS for the reactivity part,
whereas other frameworks are increasingly adopting the template compilation approach of Angular,
with no Virtual DOM needed at runtime.</p>
<p>The future of Angular is exciting!</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Angular 15.2?2023-02-23T00:00:00+00:00https://blog.ninja-squad.com/2023/02/23/what-is-new-angular-15.2<p>Angular 15.2.0 is here!</p>
<p style="text-align: center;">
<a href="https://github.com/angular/angular/releases/tag/15.2.0">
<img class="rounded img-fluid" style="max-width: 100%" src="/assets/images/angular.png" alt="Angular logo" />
</a>
</p>
<p>This is a minor release with some interesting features and some big news: let’s dive in!</p>
<h2 id="easily-migrate-to-standalone-components">Easily migrate to standalone components!</h2>
<p>The Angular team is releasing a set of schematics to automatically migrate your application
to standalone components. It does an amazing job at analyzing your code,
migrating your components/pipes/directives to their standalone versions,
and removing the obsolete modules of your application 😍.</p>
<p>Sounds interesting? We wrote a guide about it:</p>
<p>👉 <a href="/2023/02/21/migrate-an-angular-application-to-standalone/">Migrate to standalone components with Angular schematics</a></p>
<h2 id="angular-signals">Angular Signals</h2>
<p>The Angular team has been working on a different way to handle reactivity in your application for the past year.
The first step of the result has been publicly released (even if there is nothing to use yet, as the API will only be available in v16):
the <a href="https://github.com/angular/angular/discussions/49090">discussion about Angular Signals</a>.</p>
<p>Signals are a concept that is used in many other frameworks, like SolidJS, Vue, Preact and even the venerable KnockoutJS.
The idea is to offer a few primitives to define reactive state in your application
and to allow the framework to know which components are impacted by a change, rather than having to detect changes on the whole tree of components.</p>
<p>This would be a significative change to how Angular works, as it currently relies on zone.js to detect changes in the whole tree of components by default.
Instead, with signals, the framework would only re-render the components that are impacted by a change.</p>
<p>This also opens the door to zoneless applications, i.e. applications where Angular applications don’t need to include Zone.js (which makes them lighter), and don’t have to patch all the browser APIs (which makes them start faster).</p>
<p>The first draft of the API is available and looks like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// define a signal
const count = signal(0);
// get the value of the signal
const value = count();
// set the value of the signal
count.set(1);
// update the value of the signal, based on current value
count.update((value) => value + 1);
// mutate the value of the signal (handy for objects/arrays)
const user = signal({ name: 'JB', favoriteFramework: 'Angular' });
user.mutate((user) => user.name = 'Cédric');
</code></pre></div></div>
<p>Once you have defined signals, you can define computed values that derive from them:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const double = computed(() => count() * 2);
</code></pre></div></div>
<p>Computed values are automatically computed when one of the signals they depend on changes.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>count.set(2);
console.log(double()); // logs 4
</code></pre></div></div>
<p>Note that they are lazily computed and only re-computed
when one of the signals they depend on produces a new value.</p>
<p>Finally, you can use the <code class="language-plaintext highlighter-rouge">effect</code> function to react to changes in your signals:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// log the value of the count signal when it changes
effect(() => console.log(count()));
</code></pre></div></div>
<p>This does look like a <code class="language-plaintext highlighter-rouge">BehaviorSubject</code>, but it has some subtle differences, the most important one being that unsubscribing is unnecessary thanks to the usage of
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakRef">weak references</a>.</p>
<p>That’s pretty much it for now!
The next step is to integrate this API with the framework,
and make it interoperate with RxJS.</p>
<p>In an ideal future,
we may be able to build a component with fields that are signals and computed values used in the template.
The framework would be notified when an expression has changed thanks to the signals, and would thus only need to re-render the components affected by the change, without having to check for changes on unrelated components, without the need for zone.js.
But there is a long way ahead, as several things needs to be rethought in the framework to make this work
(what about inputs, outputs, queries, lifecycle methods, etc?).</p>
<p>This is anyway an exciting project, and it’s quite interesting how frameworks inspire each others,
with Angular taking inspiration from Vue and SolidJS for the reactivity part,
whereas other frameworks are increasingly adopting the template compilation approach of Angular,
with no Virtual DOM needed at runtime.</p>
<h2 id="deprecation-of-class-based-guards-and-resolvers">Deprecation of class-based guards and resolvers</h2>
<p>The class-based guards and resolvers are now officially deprecated on a route definition.
As you may know, it is possible to write them as functions since Angular v14.2
(check out our <a href="/2022/08/26/what-is-new-angular-14.2/">blog post about that</a>).</p>
<p>You can migrate your guards and resolvers to functions fairly easily
or you can simply wrap the class with <code class="language-plaintext highlighter-rouge">inject()</code> as a quick way to get rid of the deprecation warning:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{ path: 'users', component: UsersComponent, canActivate: () => inject(LoggedInGuard).canActivate() }
</code></pre></div></div>
<p>Note that the <code class="language-plaintext highlighter-rouge">CanActivate</code>, <code class="language-plaintext highlighter-rouge">CanDeactivate</code>, etc interfaces will be deleted in a future version of Angular.</p>
<h2 id="routertestingharness">RouterTestingHarness</h2>
<p>The <code class="language-plaintext highlighter-rouge">RouterTestingModule</code> now provides a <code class="language-plaintext highlighter-rouge">RouterTestingHarness</code>
that can be used to write tests.
It can be handy to test components that expect an <code class="language-plaintext highlighter-rouge">ActivatedRoute</code> for example,
or when you want to trigger navigations in your tests to test guards or resolvers.</p>
<p><code class="language-plaintext highlighter-rouge">RouterTestingHarness</code> has a static method <code class="language-plaintext highlighter-rouge">create</code> that can be called with an optional initial navigation.
This method returns a promise of the created harness, that can then be used to trigger navigations,
using <code class="language-plaintext highlighter-rouge">navigateByUrl</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// load the routes in the TestBed
TestBed.configureTestingModule({
imports: [RouterTestingModule.withRoutes(routes)],
});
// create the harness
const harness = await RouterTestingHarness.create();
// explicitly cast the component returned with `<UserComponent>`
const component = await harness.navigateByUrl<UserComponent>('/users/1');
// or pass the type as the second argument
// in that case, the test fails if the component is not of the expected type when navigating to /users/1
const component = await harness.navigateByUrl('/users/1', UserComponent);
</code></pre></div></div>
<p>The harness provides a <code class="language-plaintext highlighter-rouge">routeDebugElement</code> property that returns the <code class="language-plaintext highlighter-rouge">DebugElement</code> of the component
you navigated to, and a <code class="language-plaintext highlighter-rouge">routeNativeElement</code> property that returns the native element of the component.
If you want to get the component instance, you can either get it as the return value of <code class="language-plaintext highlighter-rouge">navigateByUrl</code>,
or by accessing <code class="language-plaintext highlighter-rouge">harness.routeDebugElement.componentInstance</code>.</p>
<p>The harness does not have a property to access the <code class="language-plaintext highlighter-rouge">ComponentFixture</code> as we usually have in tests,
but directly provides a <code class="language-plaintext highlighter-rouge">detectChanges</code> method that will trigger change detection on the component.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const harness = await RouterTestingHarness.create();
const component = await harness.navigateByUrl('/users/1', UserComponent);
component.name = 'Cédric';
harness.detectChanges();
expect(harness.routeNativeElement!.querySelector('#name')!.textContent).toBe('Cédric');
</code></pre></div></div>
<h2 id="withnavigationerrorhandler">withNavigationErrorHandler</h2>
<p>A new feature called <code class="language-plaintext highlighter-rouge">withNavigationErrorHandler</code> has been added to the router.
It can be used in <code class="language-plaintext highlighter-rouge">provideRouter</code> to provide a custom error handler for navigation errors.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>provideRouter(routes, withNavigationErrorHandler((error: NavigationError) => {
// do something with the error
}))
</code></pre></div></div>
<p>This is roughly equivalent to the (now deprecated) <code class="language-plaintext highlighter-rouge">errorHandler</code> you could configure on the <code class="language-plaintext highlighter-rouge">RouterModule</code>.</p>
<h2 id="ngoptimizedimage">NgOptimizedImage</h2>
<p><code class="language-plaintext highlighter-rouge">NgOptimizedImage</code> has a new <code class="language-plaintext highlighter-rouge">loaderParams</code> input that accepts an object.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!-- params = { isBlackAndWhite: true } for example -->
<img [ngSrc]="source" [loaderParams]="params"></img>
</code></pre></div></div>
<p>This object will be passed to your custom loader when it is called,
as a property <code class="language-plaintext highlighter-rouge">loaderParams</code> in the <code class="language-plaintext highlighter-rouge">ImageLoaderConfig</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const customLoader = (config: ImageLoaderConfig) => {
const { loaderParams } = config;
// do something with loaderParams
};
</code></pre></div></div>
<h2 id="performances">Performances</h2>
<p>The <code class="language-plaintext highlighter-rouge">NgClass</code> directive has been rewritten to improve performances.
Its algorithm is now a bit smarter and triggers less change detections and DOM updates.
You don’t have to change anything, you’ll get that for free when upgrading 😍.</p>
<h2 id="angular-cli">Angular CLI</h2>
<p>The CLI had few changes in this release, so no dedicated article this time.</p>
<p>The esbuild builder now supports Less stylesheets, CommonJS dependency checks and node modules license extraction. Maybe more importantly, it now uses the new incremental rebuild of esbuild,
introduced in <a href="https://github.com/evanw/esbuild/releases/tag/v0.17.0">esbuild v0.17</a>.
Watch mode should now be even faster.</p>
<p>Another tiny new feature: <code class="language-plaintext highlighter-rouge">ng update</code> now logs the number of files modified by the migrations.</p>
<h2 id="summary">Summary</h2>
<p>That’s all for this release, stay tuned!</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
How to migrate an Angular application to standalone components?2023-02-21T00:00:00+00:00https://blog.ninja-squad.com/2023/02/21/migrate-an-angular-application-to-standalone<p>Angular 14 introduced standalone components and optional modules.
But it can be quite a daunting task to migrate an existing application to this new model!</p>
<p style="text-align: center;">
<a href="https://github.com/angular/angular">
<img class="rounded img-fluid" style="max-width: 100%" src="/assets/images/angular.png" alt="Angular logo" />
</a>
</p>
<p>This guide supposes that you are familiar with the new standalone components and optional modules introduced in Angular 14.
If not, you can read our <a href="/2022/05/12/a-guide-to-standalone-components-in-angular/">dedicated article</a>.</p>
<p>The Angular team has been working on a collection of schematics
to help you migrate your application to standalone components.
These schematics are available in Angular v15.2.0 and above.
So the first step is to update your application to the latest version of Angular.
Then we’re good to go!</p>
<p>Let’s dive in.</p>
<h2 id="schematics-to-the-rescue">Schematics to the rescue</h2>
<p>The schematics are available in the <code class="language-plaintext highlighter-rouge">@angular/core</code> package.</p>
<p>To run them, enter:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng generate @angular/core:standalone
</code></pre></div></div>
<p>The schematics expects two arguments:</p>
<ul>
<li>the path to the application you want to migrate (by default ‘./’)</li>
<li>the mode of the schematic (by default ‘convert-to-standalone’)</li>
</ul>
<p>There are three modes available:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">convert-to-standalone</code>: this is the default mode, and it will convert all your components to standalone components, except the ones declared in your main module.</li>
<li><code class="language-plaintext highlighter-rouge">prune-ng-modules</code>: this mode will remove all the modules that aren’t necessary anymore.</li>
<li><code class="language-plaintext highlighter-rouge">standalone-bootstrap</code>: this mode will bootstrap your application with the <code class="language-plaintext highlighter-rouge">bootstrapApplication</code> function,
and migrate the components referenced in your main module.</li>
</ul>
<p>To fully run a migration, you need to run the schematics in the three modes consecutively.</p>
<h2 id="convert-to-standalone">Convert to standalone</h2>
<p>The first mode will convert all your components to standalone components,
except the ones referenced in the <code class="language-plaintext highlighter-rouge">bootstrap</code> field of your main module.
It also updates the related unit tests.</p>
<p>As this is the default mode, you can run:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng generate @angular/core:standalone --defaults
</code></pre></div></div>
<p>The schematic is quite smart, as it compiles the template of each component to detect what the standalone version of the component needs to import.</p>
<p>For example, if you have a component that uses the <code class="language-plaintext highlighter-rouge">NgIf</code>, <code class="language-plaintext highlighter-rouge">RouterLink</code> and <code class="language-plaintext highlighter-rouge">FormControl</code> directives, the schematic will add <code class="language-plaintext highlighter-rouge">NgIf</code>, <code class="language-plaintext highlighter-rouge">RouterLink</code> (as they are standalone directives) and <code class="language-plaintext highlighter-rouge">ReactiveFormModule</code> (as <code class="language-plaintext highlighter-rouge">FormControl</code> is available via this module) to the list of imports of the standalone component (and add the necessary TypeScript imports).
It also works with your own components, pipes and directives,
and the ones from third-party libraries of course.</p>
<p>Be warned though: the schematic can’t target a specific component or module,
so it generates a ton of changes in your application.
It also generates some “noise”: some files are modified but not really changed,
because the schematic sometimes reformats the code.</p>
<p>To avoid this, I strongly advise you to add a formatter to your project,
for example <a href="https://prettier.io/">Prettier</a>.
If you want to learn how, we have a dedicated article about <a href="/2021/03/31/migrating-from-tslint-to-eslint/">how to add ESLint and Prettier to your Angular project</a>.</p>
<p>This allows you to run <code class="language-plaintext highlighter-rouge">ng lint --fix</code> after the schematic,
to only focus on the real changes.</p>
<p>All your entities are now standalone components, pipes and directives.
The schematic also updates the modules of your application,
by moving the migrated entities from the <code class="language-plaintext highlighter-rouge">declarations</code> array,
to the <code class="language-plaintext highlighter-rouge">imports</code> array.</p>
<p>At the end of this step, you’ll have most of your components migrated to standalone components,
but you’ll still have your existing modules.
The application should still work if you run <code class="language-plaintext highlighter-rouge">ng serve</code>, <code class="language-plaintext highlighter-rouge">ng test</code>, etc.</p>
<h2 id="prune-the-modules">Prune the modules</h2>
<p>The second mode will remove all the modules that aren’t necessary anymore.</p>
<p>To run it, enter:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng generate @angular/core:standalone --defaults --mode=prune-ng-modules
</code></pre></div></div>
<p>The schematic can remove a module only if:</p>
<ul>
<li>it doesn’t have any <code class="language-plaintext highlighter-rouge">declarations</code>, <code class="language-plaintext highlighter-rouge">providers</code> or <code class="language-plaintext highlighter-rouge">bootstrap</code></li>
<li>it doesn’t have any code in its constructor, or other methods</li>
<li>it doesn’t have any <code class="language-plaintext highlighter-rouge">imports</code> that reference a <code class="language-plaintext highlighter-rouge">ModuleWithProviders</code></li>
</ul>
<p>If your module has providers, you can usually move them.</p>
<p>This last one means that modules that import a module with providers
(like <code class="language-plaintext highlighter-rouge">RouterModule.forChild</code>) can’t be removed without a bit of work first.</p>
<p>Typically, if you lazy-loaded modules, you have a module that looks like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@NgModule({
imports: [RouterModule.forChild(adminRoutes)]
})
export class AdminModule { }
</code></pre></div></div>
<p>With the routes of the module declared like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export const adminRoutes: Routes = [
{ path: '', component: AdminComponent }
];
</code></pre></div></div>
<p>And a main route file that lazy-load the module:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const routes: Routes = [
{ path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) }
];
</code></pre></div></div>
<p>You then need to manually migrate this to lad the routes directly:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const routes: Routes = [
{ path: 'admin', loadChildren: () => import('./admin/admin.routes').then(m => m.adminRoutes)
];
</code></pre></div></div>
<p>When this is done, you can remove <code class="language-plaintext highlighter-rouge">RouterModule.forChild</code>from the imports of the admin module,
and manually delete the <code class="language-plaintext highlighter-rouge">AdminModule</code> if it isn’t referenced elsewhere.</p>
<p>Sometimes, your module is referenced somewhere else in your application.
In that case, the call won’t be removed by the schematic.
The schematic adds a comment with a TODO</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>console.log(/* TODO(standalone-migration): clean up removed NgModule reference manually */ AdminModule);
</code></pre></div></div>
<p>You can look into your codebase to see where <code class="language-plaintext highlighter-rouge">AdminModule</code> is referenced and remove it manually.</p>
<h2 id="bootstrap-the-application">Bootstrap the application</h2>
<p>The last mode will update your application to use the <code class="language-plaintext highlighter-rouge">bootstrapApplication</code> function.
It will convert the main module of your application and all its components/pipes/directives to standalone components.
It also converts the imports of other modules to <code class="language-plaintext highlighter-rouge">importProvidersFrom</code> calls.
When it can, it uses the appropriate <code class="language-plaintext highlighter-rouge">provide...()</code> function to import the providers:
for example <code class="language-plaintext highlighter-rouge">provideRouter()</code> for the <code class="language-plaintext highlighter-rouge">RouterModule</code>, or <code class="language-plaintext highlighter-rouge">provideHttpClient()</code> for the <code class="language-plaintext highlighter-rouge">HttpClientModule</code> 😍.</p>
<p>To run it, enter:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng generate @angular/core:standalone --defaults --mode=standalone-bootstrap
</code></pre></div></div>
<p>After this step, your application is fully migrated to standalone components.
You can then do a bit of cleanup in your codebase with <code class="language-plaintext highlighter-rouge">ng lint --fix</code>
and check that everything is still working.</p>
<p>Your tests will need to be updated though:
the schematics can’t analyze them (as tests are not compiled in AoT mode).
The schematics tries to do its best to update them,
and moves the declarations to the imports of the testing module,
but you usually have to do some manual work to make them work again.</p>
<h2 id="a-strategy-for-large-applications">A strategy for large applications</h2>
<p>As this can be a daunting task in a large project,
where thousands of tests are affected,
you can try to approach this migration in small steps.</p>
<p>Even if the migration runs on the whole project,
you can then use your version control system to revert the changes on all modules except one.
I usually start with the “shared” module (that almost all projects have),
which usually contains components/pipes/directives that are used in many places
and fairly easy to migrate as they are “leaves” of the application.</p>
<p>Then, I lint the code, commit the changes, run the tests and fix them.</p>
<p>Once this is done, I can move on to the next module, starting with the small ones
and progressively moving to the bigger ones.
One a module is migrated, I migrate its routes configuration,
in order to delete the module.</p>
<p>Rinse and repeat until you’re done!
Your application now uses standalone components 🎉.</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Angular 15.1?2023-01-11T00:00:00+00:00https://blog.ninja-squad.com/2023/01/11/what-is-new-angular-15.1<p>Angular 15.1.0 is here!</p>
<p style="text-align: center;">
<a href="https://github.com/angular/angular/releases/tag/15.1.0">
<img class="rounded img-fluid" style="max-width: 100%" src="/assets/images/angular.png" alt="Angular logo" />
</a>
</p>
<p>This is a minor release with some interesting features: let’s dive in!</p>
<h2 id="typescript-49">TypeScript 4.9</h2>
<p>TypeScript 4.9 is now supported by Angular.
This new version of TypeScript brings some new features,
as you can see in the <a href="https://devblogs.microsoft.com/typescript/announcing-typescript-4-9/">official blog post</a>, including the new <code class="language-plaintext highlighter-rouge">satisfies</code> operator.</p>
<h2 id="templates">Templates</h2>
<p>It is now possible to use self-closing tags on custom elements.
This is a small but nice improvement, as it allows to write:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><my-component />
</code></pre></div></div>
<p>instead of:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><my-component></my-component>
</code></pre></div></div>
<p>This is also applicable to <code class="language-plaintext highlighter-rouge">ng-content</code> and <code class="language-plaintext highlighter-rouge">ng-container</code> for example.</p>
<p>Fun fact: this might not seem like a big change,
but this is actually the first time that Angular allows
a syntax that is not HTML compliant in templates.
Until now, templates were always valid HTML (yes, even, the binding syntax <code class="language-plaintext highlighter-rouge">[src]</code>!).
But as Angular templates are never parsed by the browser,
the Angular team decided to allow this syntax,
and it will probably be extended in the future to other non-HTML compliant syntaxes
that can improve the developer experience.</p>
<h2 id="router">Router</h2>
<p>The <code class="language-plaintext highlighter-rouge">CanLoad</code> guard is now officially deprecated and replaced by the recently introduced <code class="language-plaintext highlighter-rouge">CanMatch</code> guard. They both achieve the same goal (prevent loading the children of a route), but the <code class="language-plaintext highlighter-rouge">CanMatch</code> guard can also match another route when it rejects.
<code class="language-plaintext highlighter-rouge">CanLoad</code> was also only running once, whereas <code class="language-plaintext highlighter-rouge">CanMatch</code> runs on every navigation
as the <code class="language-plaintext highlighter-rouge">CanActivate</code> guard.</p>
<p>It is now possible to define the <code class="language-plaintext highlighter-rouge">onSameUrlNavigation</code> option for a specific navigation to specify what to do when the user navigates to the same URL as the current one,
with two possible values: <code class="language-plaintext highlighter-rouge">reload</code> and <code class="language-plaintext highlighter-rouge">ignore</code>.
This was previously only possible globally with the <code class="language-plaintext highlighter-rouge">RouterConfigOptions</code> of the router (or <code class="language-plaintext highlighter-rouge">withRouterConfig</code> if you’re using the standalone router providers).</p>
<p>You can now do something like:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.router.navigateByUrl('/user', { onSameUrlNavigation: 'reload' })
</code></pre></div></div>
<p>The router also gained a new event <code class="language-plaintext highlighter-rouge">NavigationSkipped</code> that is emitted when a navigation is skipped because the user navigated to the same URL as the current one or if <code class="language-plaintext highlighter-rouge">UrlHandlingStrategy</code> ignored it.</p>
<p>A new <code class="language-plaintext highlighter-rouge">withHashLocation()</code> function has been added to the router to configure the router to use a hash location strategy. It was previously configured via DI <code class="language-plaintext highlighter-rouge">{ provide: LocationStrategy, useClass: HashLocationStrategy }</code>. You can now write:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>providers: [provideRouter(routes, withHashLocation())]
</code></pre></div></div>
<h2 id="core">Core</h2>
<p>A new function <code class="language-plaintext highlighter-rouge">isStandalone()</code> was added to check if a component, directive or pipe is standalone or not.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const isStandalone = isStandalone(UserComponent);
</code></pre></div></div>
<h2 id="tests">Tests</h2>
<p>The <code class="language-plaintext highlighter-rouge">TestBed</code> now has a new method <code class="language-plaintext highlighter-rouge">runInInjectionContext</code>
to easily run a function that uses <code class="language-plaintext highlighter-rouge">inject()</code>.
This was already possible via the verbose <code class="language-plaintext highlighter-rouge">TestBed.inject(EnvironmentInjector).runInContext()</code>.
This is especially useful when you want to test a functional guard or resolver for example,
and this is what the CLI now generates by default for the tests of these entities.</p>
<h2 id="angular-cli">Angular CLI</h2>
<p>As usual, you can check out our dedicated article about the new CLI version:</p>
<p>👉 <a href="/2023/01/11/angular-cli-15.1">Angular CLI v15.1</a></p>
<h2 id="summary">Summary</h2>
<p>The roadmap includes work on the CLI to be able to generate standalone applications without modules.
It also mentions some efforts on the server-side rendering story,
which is not the strong suit of Angular (compared to other mainstream frameworks)
and the possibility to use Angular without zone.js.</p>
<p>That’s all for this release, stay tuned!</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Angular CLI 15.1?2023-01-11T00:00:00+00:00https://blog.ninja-squad.com/2023/01/11/angular-cli-15.1<p><a href="https://github.com/angular/angular-cli/releases/tag/15.1.0">Angular CLI 15.1.0</a> is out!✨</p>
<p>If you want to upgrade to 15.1.0 without pain (or to any other version, by the way), I have created a Github project to help: <a href="https://github.com/cexbrayat/angular-cli-diff">angular-cli-diff</a>. Choose the version you’re currently using (14.2.0 for example), and the target version (15.1.0 for example), and it gives you a diff of all files created by the CLI: <a href="https://github.com/cexbrayat/angular-cli-diff/compare/14.2.0...15.1.0">angular-cli-diff/compare/14.2.0…15.1.0</a>.
It can be a great help along with the official <code class="language-plaintext highlighter-rouge">ng update @angular/core @angular/cli</code> command.
You have no excuse for staying behind anymore!</p>
<p>Let’s see what we’ve got in this release.</p>
<h2 id="esbuild-builder-improvements">esbuild builder improvements</h2>
<p>The new experimental builder that uses <a href="https://esbuild.github.io/">esbuild</a>
has been introduced in v14, but with some missing features.</p>
<p>Even if it is still not feature complete, the esbuild builder is making progress.
It now supports Sass inline styles. Less is not supported yet.
It also supports the <code class="language-plaintext highlighter-rouge">--stats-json</code> option
which is useful to generate a JSON file with the build stats.</p>
<p>To check it out in your project,
replace <code class="language-plaintext highlighter-rouge">@angular-devkit/build-angular:browser</code>
with <code class="language-plaintext highlighter-rouge">@angular-devkit/build-angular:browser-esbuild</code>,
and run <code class="language-plaintext highlighter-rouge">ng build</code>.
For a small application, on a cold production build (after <code class="language-plaintext highlighter-rouge">ng cache clean</code>), the build time went from 13s to 6s on my machine.
For a larger application, the build time went from 1m12s to 32s 🤯.</p>
<h2 id="ng-generate-environments">ng generate environments</h2>
<p>A new schematic has been added to add environment files to an application.
If you are a returning reader, you know that the environment files have been removed from
a <a href="/2022/11/16/angular-cli-15.0">new application in v15</a>, and you now have to add them yourself.</p>
<p>The new schematic can be used to simplify the task:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng generate environments
</code></pre></div></div>
<p>This will create the <code class="language-plaintext highlighter-rouge">environments</code> folder with the <code class="language-plaintext highlighter-rouge">environment.ts</code> and <code class="language-plaintext highlighter-rouge">environment.development.ts</code> files, and update the <code class="language-plaintext highlighter-rouge">angular.json</code> file to use the <code class="language-plaintext highlighter-rouge">development</code> environment file when using the development configuration:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.development.ts"
}
]
</code></pre></div></div>
<p>Notice that this is different from what the CLI used to generate by default,
where the <code class="language-plaintext highlighter-rouge">environment.ts</code> file was used for the development configuration,
and an <code class="language-plaintext highlighter-rouge">environment.prod.ts</code> file was used for the production configuration.
The CLI team thinks this is more aligned with the current naming choices,
as <code class="language-plaintext highlighter-rouge">prod</code> is not a configuration.</p>
<h2 id="ng-generate-config">ng generate config</h2>
<p>Another new schematic has been added to generate the configuration files
that are now <a href="/2022/11/16/angular-cli-15.0">hidden by default since v15</a>.</p>
<p>You can use:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng generate config karma
</code></pre></div></div>
<p>to generate the <code class="language-plaintext highlighter-rouge">karma.conf.js</code> file, and:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng generate config browserslist
</code></pre></div></div>
<p>to generate the <code class="language-plaintext highlighter-rouge">.browserslistrc</code> file.</p>
<h2 id="ng-generate-interceptor">ng generate interceptor</h2>
<p><code class="language-plaintext highlighter-rouge">ng generate interceptor</code> can now generate an interceptor with the <code class="language-plaintext highlighter-rouge">--functional</code> option.
This will generate a functional interceptor, which is a new feature in Angular v15.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng generate interceptor --functional authentication
</code></pre></div></div>
<h2 id="ng-generate-guard">ng generate guard</h2>
<p><code class="language-plaintext highlighter-rouge">ng generate guard</code> now supports the <code class="language-plaintext highlighter-rouge">--guardType</code> option, which is the same as <code class="language-plaintext highlighter-rouge">--implements</code>,
but makes more sense when you’re generating a functional guard (as they are not implementing an interface).</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng generate guard --functional --guardType CanActivate logged-in
</code></pre></div></div>
<p>The guards as classes are probably going to be deprecated really soon,
so <code class="language-plaintext highlighter-rouge">--functional</code> will be the default and <code class="language-plaintext highlighter-rouge">--implements</code> will be removed.</p>
<h2 id="summary">Summary</h2>
<p>That’s all for the CLI v15.1 release!
You’ll find more interesting features in our article about the
<a href="/2023/01/11/what-is-new-angular-15.1">framework v15.1.0 release</a>.</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Angular 15?2022-11-16T00:00:00+00:00https://blog.ninja-squad.com/2022/11/16/what-is-new-angular-15.0<p>Angular 15.0.0 is here!</p>
<p style="text-align: center;">
<a href="https://github.com/angular/angular/releases/tag/15.0.0">
<img class="rounded img-fluid" style="max-width: 100%" src="/assets/images/angular.png" alt="Angular logo" />
</a>
</p>
<p>This is a major release with a ton of interesting features: let’s dive in!</p>
<h2 id="standalone-components-are-stable-">Standalone components are stable! ✨</h2>
<p>Here we are: standalone components are now stable!
You can officially build Angular applications without modules if you want to.</p>
<p>Check out our guide to standalone components:</p>
<p>👉 <a href="/2022/05/12/a-guide-to-standalone-components-in-angular/">A guide to standalone components in Angular</a></p>
<p>A few improvements landed in v15.</p>
<p>The HTTP support evolved, and we can now use <code class="language-plaintext highlighter-rouge">provideHttpClient</code> to provide <code class="language-plaintext highlighter-rouge">HttpClient</code> without using <code class="language-plaintext highlighter-rouge">HttpClientModule</code> (see below).</p>
<p>We can now use <code class="language-plaintext highlighter-rouge">provideHttpClientTesting()</code> to provide <code class="language-plaintext highlighter-rouge">HttpClient</code> in tests,
and… <code class="language-plaintext highlighter-rouge">provideLocationMocks()</code> to test components using the router:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>TestBed.configureTestingModule({
providers: [
// 👇 ~ similar to RouterTestingModule
// (some providers are missing compared to RouterTestingModule)
provideRouter([]),
provideLocationMocks(),
// 👇 similar to HttpClientTestingModule
provideHttpClient(),
provideHttpClientTesting()
],
});
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">NgForOf</code> directive is now aliased as <code class="language-plaintext highlighter-rouge">NgFor</code> which makes it simpler to import it in your standalone components, as you previously had to know <code class="language-plaintext highlighter-rouge">ngIf</code> was the <code class="language-plaintext highlighter-rouge">NgIf</code> directive, and <code class="language-plaintext highlighter-rouge">ngFor</code> was the <code class="language-plaintext highlighter-rouge">NgForOf</code> directive 😅.</p>
<p>The same kind of thing has been done with the <code class="language-plaintext highlighter-rouge">RouterLink</code> directive in the router:
there was previously a <code class="language-plaintext highlighter-rouge">RouterLink</code> directive and a <code class="language-plaintext highlighter-rouge">RouterLinkWithHref</code> directive.
They are now merged into one, making it a no-brainer to import it.
A schematic will automatically migrate your code if you were using <code class="language-plaintext highlighter-rouge">RouterLinkWithHref</code>.</p>
<p>The language service (used for the autocompletion and type-checking in your IDE) has been improved for standalone components, and it now automatically offers to import a standalone directive/component/pipe
in your component, if you use one in its template ✨.</p>
<h2 id="http-with-providehttpclient">HTTP with provideHttpClient</h2>
<p>The HTTP support evolves and adapts to the new world of Angular 15, where modules are optional.
It’s now possible to provide the <code class="language-plaintext highlighter-rouge">HttpClient</code> using <code class="language-plaintext highlighter-rouge">provideHttpClient()</code>.
HTTP interceptors are also evolving and can now be defined as functions.</p>
<p>We wrote a dedicated article about this:</p>
<p>👉 <a href="/2022/11/09/angular-http-in-standalone-applications">HTTP in a standalone Angular application with provideHttpClient</a></p>
<h2 id="directive-composition-api">Directive composition API</h2>
<p>The other big feature of Angular v15 is the directive composition API.
We also wrote a dedicated article about this:</p>
<p>👉 <a href="/2022/10/19/directive-composition-api-in-angular">Directive Composition API in Angular</a></p>
<h2 id="ngoptimizedimage-is-stable">NgOptimizedImage is stable</h2>
<p>The <code class="language-plaintext highlighter-rouge">NgOptimizedImage</code> directive is now stable and can be used in production.
Introduced in Angular v14.2, it allows you to optimize images.
You can check out our explanation in our <a href="/2022/08/26/what-is-new-angular-14.2/">blog post about v14.2</a>.</p>
<p>Note that there is a change in the API: the <code class="language-plaintext highlighter-rouge">NgOptimizedImage</code> directive now has inputs named <code class="language-plaintext highlighter-rouge">ngSrc</code> and <code class="language-plaintext highlighter-rouge">ngSrcset</code> (whereas they were originally called <code class="language-plaintext highlighter-rouge">rawSrc</code> and <code class="language-plaintext highlighter-rouge">rawSrcset</code>).</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><img [ngSrc]="imageUrl" />
</code></pre></div></div>
<p>Another input called <code class="language-plaintext highlighter-rouge">sizes</code> has also been added.
When you provide it a value, then the directive will automatically generate a responsive <code class="language-plaintext highlighter-rouge">srcset</code> for you.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><img [ngSrc]="imageUrl" sizes="100vw"> />
</code></pre></div></div>
<p>It uses the default breakpoints <code class="language-plaintext highlighter-rouge">[16, 32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048, 3840]</code> (and thus generates a big <code class="language-plaintext highlighter-rouge">srcset</code> with all these values) but they can be configured.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>providers: [
{
provide: IMAGE_CONFIG, useValue: { breakpoints: [1080, 1200] }
},
]
</code></pre></div></div>
<p>This generates the following <code class="language-plaintext highlighter-rouge">srcset</code>: <code class="language-plaintext highlighter-rouge">https://example.com/image.png 1080w, https://example.com/image.png 1200w</code>.</p>
<p>This behavior can be disabled via the <code class="language-plaintext highlighter-rouge">disableOptimizedSrcset</code> input of the directive.</p>
<p>The directive also gained a new <code class="language-plaintext highlighter-rouge">fill</code> boolean input,
which removes the requirements for height and width on the image,
adds inline styles to cause the image to fill its containing element
and adds a default <code class="language-plaintext highlighter-rouge">sizes</code> value of <code class="language-plaintext highlighter-rouge">100vw</code> which will cause the image to have a responsive <code class="language-plaintext highlighter-rouge">srcset</code> automatically generated:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><img [ngSrc]="imageUrl" fill />
</code></pre></div></div>
<p>Last but not least, the directive triggers the generation of a <code class="language-plaintext highlighter-rouge">preload</code> link in the <code class="language-plaintext highlighter-rouge">head</code> of your document for priority images when used in SSR/Angular Universal.</p>
<h2 id="dependency-injection">Dependency injection</h2>
<p>The <code class="language-plaintext highlighter-rouge">providedIn: NgModule</code> syntax of the <code class="language-plaintext highlighter-rouge">@Injectable()</code> decorator is now deprecated.
You generally want to use <code class="language-plaintext highlighter-rouge">providedIn: 'root'</code>.
If providers should truly be scoped to a specific NgModule, use
<code class="language-plaintext highlighter-rouge">NgModule.providers</code> instead.
The <code class="language-plaintext highlighter-rouge">providedIn: 'any'</code> syntax is also deprecated.</p>
<h2 id="router">Router</h2>
<p>The router now auto-unwraps default exports from lazy-loaded modules, routes or components.
You can replace:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
// of for routes
loadChildren: () => import('./admin/admin.routes').then(c => c.adminRoutes)
// or for component
loadComponent: () => import('./admin/admin.component').then(m => m.AdminComponent)
</code></pre></div></div>
<p>with the shorter:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>loadChildren: () => import('./admin/admin.module')
// of for routes
loadChildren: () => import('./admin/admin.routes')
// or for component
loadComponent: () => import('./admin/admin.component')
</code></pre></div></div>
<p>if <code class="language-plaintext highlighter-rouge">AdminModule</code>, <code class="language-plaintext highlighter-rouge">AdminComponent</code> and <code class="language-plaintext highlighter-rouge">adminRoutes</code> are default exports.</p>
<h2 id="forms">Forms</h2>
<p>Some utility functions have been added to the forms package:
<code class="language-plaintext highlighter-rouge">isFormControl</code>, <code class="language-plaintext highlighter-rouge">isFormGroup</code>, <code class="language-plaintext highlighter-rouge">isFormRecord</code>, <code class="language-plaintext highlighter-rouge">isFormArray</code>.</p>
<p>They are particularly useful when you want to write a custom validator,
as custom validators have <code class="language-plaintext highlighter-rouge">AbstractControl</code> in their signature,
but you often <em>know</em> that the validator you write is for a specific <code class="language-plaintext highlighter-rouge">FormControl</code>, <code class="language-plaintext highlighter-rouge">FormGroup</code>, etc.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>positiveValues(control: AbstractControl) {
if (!isFormArray(control)) {
return null;
}
// check that every value is positive
// we can use `control.controls` here \o/
if (control.controls.some(c => c.value < 0)) {
return { positiveValues: true };
}
return null;
}
</code></pre></div></div>
<h2 id="common">Common</h2>
<p>Angular v13 introduced the toke <code class="language-plaintext highlighter-rouge">DATE_PIPE_DEFAULT_TIMEZONE</code> to configure the default timezone of the <code class="language-plaintext highlighter-rouge">DatePipe</code> (see <a href="/2021/11/03/what-is-new-angular-13.0">our blog post about v13</a>).</p>
<p>This token has been deprecated in v15 and replaced with <code class="language-plaintext highlighter-rouge">DATE_PIPE_DEFAULT_OPTIONS</code> which accepts an object with a <code class="language-plaintext highlighter-rouge">timezone</code> property <em>and</em> a <code class="language-plaintext highlighter-rouge">dateFormat</code> property to specify the default date format that the pipe should use.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>providers: [{ provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: { dateFormat: 'shortDate'} }]
</code></pre></div></div>
<h2 id="devtools">Devtools</h2>
<p>The devtools now allow you to inspect the source code of a directive</p>
<h2 id="angular-cli">Angular CLI</h2>
<p>As usual, you can check out our dedicated article about the new CLI version:</p>
<p>👉 <a href="/2022/11/16/angular-cli-15.0">Angular CLI v15</a></p>
<h2 id="summary">Summary</h2>
<p>This release is packed with features as you can see,
and the future is exciting with the standalone APIs.
The roadmap includes work on the CLI to be able to generate standalone applications without modules.
It also mentions some efforts on the server-side rendering story,
which is not the strong suit of Angular (compared to other mainstream frameworks)
and the possibility to use Angular without zone.js.</p>
<p>That’s all for this release, stay tuned!</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Angular CLI 15.0?2022-11-16T00:00:00+00:00https://blog.ninja-squad.com/2022/11/16/angular-cli-15.0<p><a href="https://github.com/angular/angular-cli/releases/tag/15.0.0">Angular CLI 15.0.0</a> is out!✨</p>
<p>If you want to upgrade to 15.0.0 without pain (or to any other version, by the way), I have created a Github project to help: <a href="https://github.com/cexbrayat/angular-cli-diff">angular-cli-diff</a>. Choose the version you’re currently using (14.2.0 for example), and the target version (15.0.0 for example), and it gives you a diff of all files created by the CLI: <a href="https://github.com/cexbrayat/angular-cli-diff/compare/14.2.0...15.0.0">angular-cli-diff/compare/14.2.0…15.0.0</a>.
It can be a great help along with the official <code class="language-plaintext highlighter-rouge">ng update @angular/core @angular/cli</code> command.
You have no excuse for staying behind anymore!</p>
<p>Let’s see what we’ve got in this release.</p>
<h2 id="deprecations-and-breaking-changes">Deprecations and breaking changes</h2>
<p>The CLI now requires at least NodeJS v14.20 (as it relies on the <code class="language-plaintext highlighter-rouge">crypto</code> module).
It now officially supports NodeJS v18 as well.</p>
<p>TypeScript 4.6 and 4.7 are no longer supported: you need to upgrade to TS v4.8.2+.</p>
<p>SCSS imports starting with a tilde, like <code class="language-plaintext highlighter-rouge">@import '~font-awesome/scss/font-awesome';</code>, are now deprecated.
You’ll see a warning message when you build your application:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>'src/styles.scss' imports '~font-awesome/scss/font-awesome' with a tilde. Usage of '~' in imports is deprecated.
</code></pre></div></div>
<p>The solution is to simply remove the tilde: <code class="language-plaintext highlighter-rouge">@import 'font-awesome/scss/font-awesome';</code>.</p>
<p>This was dictated by the migration to the modern Sass API and compiler,
which is faster. You can still use the legacy API with <code class="language-plaintext highlighter-rouge">NG_BUILD_LEGACY_SASS=1 ng build</code>, but this will go away in the next versions.</p>
<p>Speaking of styles, the support of Stylus has been removed.
It was deprecated since <a href="/2021/05/12/angular-cli-12.0">Angular CLI v12</a>.</p>
<h2 id="esbuild-builder-improvements">esbuild builder improvements</h2>
<p>The new experimental builder that uses <a href="https://esbuild.github.io/">esbuild</a>
has been introduced in v14, but with some missing features.</p>
<p>Even if it is still not feature complete, the esbuild builder is making progress.
You can now use the watch mode with <code class="language-plaintext highlighter-rouge">--watch</code> (and polling with <code class="language-plaintext highlighter-rouge">--poll</code>).
The builder is now also able to rebuild incrementally as the webpack-based one!</p>
<p>The code and styles are now processed in parallel,
so the bundling should be a bit faster.</p>
<p>The esbuild builder also supports <code class="language-plaintext highlighter-rouge">fileReplacements</code>,
which was missing to really give it a try in an existing application.</p>
<p>To check it out in your project,
replace <code class="language-plaintext highlighter-rouge">@angular-devkit/build-angular:browser</code>
with <code class="language-plaintext highlighter-rouge">@angular-devkit/build-angular:browser-esbuild</code>,
and run <code class="language-plaintext highlighter-rouge">ng build</code>.
For a small application, on a cold production build (after <code class="language-plaintext highlighter-rouge">ng cache clean</code>), the build time went from 13s to 6s on my machine.
For a larger application, the build time went from 1m12s to 32s 🤯.</p>
<h2 id="new-project-simplifications">New project simplifications</h2>
<p>The CLI now generates fewer files in a new project:</p>
<ul>
<li>the environment files are gone, so you’ll no longer see <code class="language-plaintext highlighter-rouge">environment.ts</code>
and <code class="language-plaintext highlighter-rouge">environment.prod.ts</code> in your project. The <code class="language-plaintext highlighter-rouge">fileReplacements</code> configuration has been removed from the <code class="language-plaintext highlighter-rouge">angular.json</code> file. You can still use these files if you want, but they are not generated by default anymore: you have to manually create them and add the relevant <code class="language-plaintext highlighter-rouge">fileReplacements</code> configuration in the <code class="language-plaintext highlighter-rouge">angular.json</code> file.</li>
<li>the <code class="language-plaintext highlighter-rouge">enableProdMode</code> function call has been removed in <code class="language-plaintext highlighter-rouge">main.ts</code>. It was only used for JiT mode, which is not really used anymore.
In AoT mode, the CLI already sets <code class="language-plaintext highlighter-rouge">ngDevMode</code> when building in production mode,
which has the same purpose.</li>
<li>the <code class="language-plaintext highlighter-rouge">.browserslistrc</code> file has been removed. You can still add one, but the file is no longer generated by default. A migration will remove it from your project if you use the default one. Note that the default or custom <code class="language-plaintext highlighter-rouge">.browserslistrc</code> of your project is now used by the CLI to determine the ECMAScript version of the output generated from your TypeScript code (instead of relying on the <code class="language-plaintext highlighter-rouge">target</code> defined in <code class="language-plaintext highlighter-rouge">tsconfig.json</code>). That’s why the target is set to <code class="language-plaintext highlighter-rouge">es2022</code> in new projects.</li>
<li>the <code class="language-plaintext highlighter-rouge">polyfills.ts</code> file has been removed. The <code class="language-plaintext highlighter-rouge">polyfills</code> option in <code class="language-plaintext highlighter-rouge">angular.json</code> now directly accepts an array of imports. To add <code class="language-plaintext highlighter-rouge">zone.js</code> to your application, the <code class="language-plaintext highlighter-rouge">angular.json</code> file now specifies <code class="language-plaintext highlighter-rouge">polyfills: ["zone.js"]</code>.</li>
<li>the <code class="language-plaintext highlighter-rouge">karma.conf.js</code> file has been removed. Same here, you can add one to customize the unit test behavior, but it is not generated by default anymore.</li>
<li>the <code class="language-plaintext highlighter-rouge">test.ts</code> file has been removed, as it was bringing no special value. You can still add one and specify it in the <code class="language-plaintext highlighter-rouge">angular.json</code> file if you need to. A migration will run and simplify this file in your project (but not remove it, although you can).</li>
</ul>
<p>Note that the default <code class="language-plaintext highlighter-rouge">test.ts</code> now uses:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
errorOnUnknownElements: true,
errorOnUnknownProperties: true
});
</code></pre></div></div>
<p>These options (added here and in the framework by this blog post author 🤓) check that all elements and properties used in your tests are known by Angular.
See <a href="/2022/06/02/what-is-new-angular-14.0/">our blog post about Angular v14</a> for more details.</p>
<h2 id="see-you-ngoninit">See you ngOnInit</h2>
<p>The CLI used to generate a component with an empty <code class="language-plaintext highlighter-rouge">ngOnInit</code> method.
As this was not always useful, this is no longer the case!</p>
<h2 id="generate-library">Generate library</h2>
<p><code class="language-plaintext highlighter-rouge">ng generate library</code> now accepts a <code class="language-plaintext highlighter-rouge">--project-root</code> option to generate the library in a different folder than the default <code class="language-plaintext highlighter-rouge">projects</code> one.</p>
<h2 id="summary">Summary</h2>
<p>That’s all for the CLI v15 release!
You’ll find more interesting features in our article about the
<a href="/2022/11/16/what-is-new-angular-15.0">framework v15.0.0 release</a>.</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
HTTP in a standalone Angular application with provideHttpClient2022-11-09T00:00:00+00:00https://blog.ninja-squad.com/2022/11/09/angular-http-in-standalone-applications<p>Angular v14 introduced the concept of standalone components/directives/pipes and allows writing applications without modules.</p>
<p>How do you use and test HTTP in a standalone application?</p>
<h2 id="providehttpclient">provideHttpClient</h2>
<p>For a long time, the <code class="language-plaintext highlighter-rouge">HttpClient</code> was provided by the <code class="language-plaintext highlighter-rouge">HttpClientModule</code> that you imported into your application module.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@NgModule({
imports: [BrowserModule, HttpClientModule],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {}
</code></pre></div></div>
<p>When the standalone APIs were introduced in Angular v14,
it opened the door to writing applications without modules.</p>
<p>The Angular team introduced an <code class="language-plaintext highlighter-rouge">importProvidersFrom()</code> function, that you could use in the <code class="language-plaintext highlighter-rouge">bootstrapApplication</code> function
to import providers from an existing module, as most of the ecosystem was structured around modules.</p>
<p>So to provide the <code class="language-plaintext highlighter-rouge">HttpClient</code> in a standalone application, you could do:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import { bootstrapApplication, importProvidersFrom } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
bootstrapApplication(AppComponent, {
providers: [importProvidersFrom(HttpClientModule)]
});
</code></pre></div></div>
<p>But since Angular v15, this can be replaced by <code class="language-plaintext highlighter-rouge">provideHttpClient()</code>,
a new function that does the same thing as <code class="language-plaintext highlighter-rouge">importProvidersFrom(HttpClientModule)</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import { bootstrapApplication } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
import { AppComponent } from './app.component';
bootstrapApplication(AppComponent, {
providers: [provideHttpClient()]
});
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">HttpClient</code> is then available for injection in your application.</p>
<p><code class="language-plaintext highlighter-rouge">provideHttpClient()</code> is more “tree-shakable” than importing <code class="language-plaintext highlighter-rouge">HttpClientModule</code>,
as you can enable the features you want by giving it some parameters.</p>
<p>For example, if you want JSONP support, you can write:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import { bootstrapApplication } from '@angular/core';
import { provideHttpClient, withJsonpSupport } from '@angular/common/http';
import { AppComponent } from './app.component';
bootstrapApplication(AppComponent, {
providers: [provideHttpClient(withJsonpSupport())]
});
</code></pre></div></div>
<p>In the same vein, Angular provides XSRF protection (cross-site request forgery) out-of-the-box, by adding a custom header containing a random token provided by the server in a cookie (which is a common technic to mitigate these attacks).</p>
<p>As you probably want to keep this security, it’s enabled by default in <code class="language-plaintext highlighter-rouge">provideHttpClient()</code>, but you can configure it with <code class="language-plaintext highlighter-rouge">withXsrfConfiguration()</code>
to specify a custom header name and cookie name:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import { bootstrapApplication } from '@angular/core';
import { provideHttpClient, withXsrfConfiguration } from '@angular/common/http';
import { AppComponent } from './app.component';
bootstrapApplication(AppComponent, {
providers: [provideHttpClient(withXsrfConfiguration({
cookieName: 'TOKEN', // default is 'XSRF-TOKEN'
headerName: 'X-TOKEN' // default is 'X-XSRF-TOKEN'
}))]
});
</code></pre></div></div>
<p>or you can disable it completely with <code class="language-plaintext highlighter-rouge">withNoXsrfProtection()</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import { bootstrapApplication } from '@angular/core';
import { provideHttpClient, withNoXsrfProtection } from '@angular/common/http';
import { AppComponent } from './app.component';
bootstrapApplication(AppComponent, {
providers: [provideHttpClient(withNoXsrfProtection())]
});
</code></pre></div></div>
<p>There is another feature that you can enable,
but first I need to introduce the concept of functional interceptors.</p>
<h2 id="functional-interceptors">Functional interceptors</h2>
<p>In Angular, interceptors are classes that implement the <code class="language-plaintext highlighter-rouge">HttpInterceptor</code> interface.
They are used to intercept HTTP requests and responses and can be used to add headers, log requests, etc.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Injectable()
export class LoggerInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log(`Request is on its way to ${req.url}`);
return next.handle(req);
}
}
</code></pre></div></div>
<p>Since Angular v15, you can also use functional interceptors.
They are functions that take an <code class="language-plaintext highlighter-rouge">HttpRequest</code> and a <code class="language-plaintext highlighter-rouge">HttpHandlerFn</code> as parameters:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export function loggerInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> {
console.log(`Request is on its way to ${req.url}`);
return next(req);
}
</code></pre></div></div>
<p>As this is a function, you can’t use the usual dependency injection via constructor parameters to inject services in it.
But you can use the <code class="language-plaintext highlighter-rouge">inject()</code> function:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export const loggerInterceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> => {
const logger = inject(Logger);
logger.log(`Request is on its way to ${req.url}`);
return next(req);
}
</code></pre></div></div>
<p>Functional interceptors have to be registered via <code class="language-plaintext highlighter-rouge">withInterceptors()</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import { bootstrapApplication } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { AppComponent } from './app.component';
bootstrapApplication(AppComponent, {
providers: [provideHttpClient(withInterceptors([loggerInterceptor]))]
});
</code></pre></div></div>
<p>Note that you can also register class-based interceptors via <code class="language-plaintext highlighter-rouge">withInterceptorsFromDi()</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import { bootstrapApplication } from '@angular/core';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { AppComponent } from './app.component';
bootstrapApplication(AppComponent, {
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: LoggerInterceptor, multi: true },
provideHttpClient(withInterceptorsFromDi())
]
});
</code></pre></div></div>
<p>But this API may be phased out in the future,
so it’s better to use <code class="language-plaintext highlighter-rouge">withInterceptors()</code> and functional interceptors.</p>
<h2 id="interceptors-and-lazy-loading">Interceptors and lazy-loading</h2>
<p>A long-standing issue with Angular was that interceptors are not inherited in lazy-loaded modules (see <a href="https://github.com/angular/angular/issues/20575">this issue</a> for more context).</p>
<p>For example, let’s say we have a lazy-loaded part of the application for the administration of our website:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
path: 'admin',
loadChildren: () => import('./admin/admin.routes').then(c => c.ADMIN_ROUTES)
},
</code></pre></div></div>
<p>If it provides the <code class="language-plaintext highlighter-rouge">HttpClient</code> as well (which is not a good idea to be honest, but let’s say that’s the case for this example):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export const ADMIN_ROUTES: Routes = [
{
path: '',
pathMatch: 'prefix',
providers: [provideHttpClient()], // <--
children: [{ path: '', component: AdminComponent }]
}
];
</code></pre></div></div>
<p>then all requests made in the <code class="language-plaintext highlighter-rouge">AdminComponent</code> will <em>not</em> be intercepted by the interceptors registered in our application.</p>
<p>If we want them to go through our <code class="language-plaintext highlighter-rouge">loggerInterceptor</code>,
we can use <code class="language-plaintext highlighter-rouge">withRequestsMadeViaParent()</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export const ADMIN_ROUTES: Routes = [
{
path: '',
pathMatch: 'prefix',
providers: [provideHttpClient(withRequestsMadeViaParent(), withInterceptors([adminInterceptor()]))],
children: [{ path: '', component: AdminComponent }]
}
];
</code></pre></div></div>
<p>Then the requests made in the <code class="language-plaintext highlighter-rouge">AdminComponent</code> will then go through the <code class="language-plaintext highlighter-rouge">adminInterceptor</code> and are then handed off to the parent <code class="language-plaintext highlighter-rouge">HttpClient</code>, and will be intercepted by the <code class="language-plaintext highlighter-rouge">loggerInterceptor</code> registered at the application bootstrap.</p>
<h2 id="testing-http">Testing HTTP</h2>
<p>When using <code class="language-plaintext highlighter-rouge">HttpClientModule</code> in your application,
you can import the <code class="language-plaintext highlighter-rouge">HttpClientTestingModule</code> in your tests to mock HTTP requests.</p>
<p>But if you use <code class="language-plaintext highlighter-rouge">provideHttpClient()</code> instead,
you can use <code class="language-plaintext highlighter-rouge">provideHttpClientTesting()</code> to mock HTTP requests in your tests (in addition to <code class="language-plaintext highlighter-rouge">provideHttpClient()</code>):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import { TestBed } from '@angular/core/testing';
import { provideHttpClientTesting } from '@angular/common/http/testing';
beforeEach(() =>
TestBed.configureTestingModule({
providers: [provideHttpClient(), provideHttpClientTesting()]
})
);
</code></pre></div></div>
<p>You can then inject <code class="language-plaintext highlighter-rouge">HttpController</code> to mock HTTP requests as you usually do.</p>
<h2 id="summary">Summary</h2>
<p>The <code class="language-plaintext highlighter-rouge">provideHttpClient()</code> API is the way to go if you work with an Angular application and don’t want to use <code class="language-plaintext highlighter-rouge">NgModule</code>.
When migrating an existing application to the standalone APIs,
you will need to replace the usage of <code class="language-plaintext highlighter-rouge">HttpClientModule</code> with <code class="language-plaintext highlighter-rouge">provideHttpClient()</code> and the usage of <code class="language-plaintext highlighter-rouge">HttpClientTestingModule</code> with <code class="language-plaintext highlighter-rouge">provideHttpClientTesting()</code> in your tests.
You can also gradually migrate your class-based interceptors to functional interceptors.</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
Directive Composition API in Angular2022-10-19T00:00:00+00:00https://blog.ninja-squad.com/2022/10/19/directive-composition-api-in-angular<p>Angular 15.0 introduces a new API to easily compose directives.
This feature has been pushed by
<a href="https://github.com/crisbeto">Kristiyan Kostadinov</a> who shares its time between the Material and Framework teams.
It solves a pain that is particularly felt by the Angular Material team.</p>
<h2 id="the-problem">The problem</h2>
<p>One of the most powerful mechanics of Angular is its directive system: you can apply a directive to an element to give it a special behavior.</p>
<p>For example, Material provides a <code class="language-plaintext highlighter-rouge">MatTooltip</code> directive that you can apply to an element to display a tooltip:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><button matTooltip="Info" [matTooltipHideDelay]="delay">Click me</button>
</code></pre></div></div>
<p>or a <code class="language-plaintext highlighter-rouge">CdkDrag</code> directive to make an element draggable:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div cdkDrag [cdkDragDisabled]="isDisabled">Drag me!</div>
</code></pre></div></div>
<p>Let’s say that you built a nice button directive <code class="language-plaintext highlighter-rouge">appButton</code> (or a component),
that probably does something amazing, and you always want to apply the <code class="language-plaintext highlighter-rouge">MatTooltip</code> and <code class="language-plaintext highlighter-rouge">CdkDrag</code> directives at the same time.</p>
<p>You also want to let the user of your directive decide if the button is draggable or not, and what the text and delay of the tooltip should be. But you don’t want your users to have to write:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><button appButton
matTooltip="Info"
[matTooltipHideDelay]="delay"
cdkDrag
[cdkDragDisabled]="isDisabled">
Click me
</button>
</code></pre></div></div>
<p>Here it is a burden on the developers to remember to add <code class="language-plaintext highlighter-rouge">matTooltip</code> and <code class="language-plaintext highlighter-rouge">cdkDrag</code> every time and to configure them properly.</p>
<p>Ideally, you’d want:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><button appButton
tooltip="Info"
[tooltipHideDelay]="delay"
[dragDisabled]="isDisabled">
Click me
</button>
</code></pre></div></div>
<p>When you want to compose behaviors like this,
you can currently use inheritance (but you can only inherit from one directive) or mixins (with a pattern I’ve only seen in <a href="https://github.com/angular/components/blob/03408fdb83680cbb69f5d547437e520910a905a3/src/material/tree/node.ts#L29">Angular Material</a>).</p>
<p>In v15, the Angular team introduces a new API to compose directives, called the Directive Composition API.
A new property is available in the <code class="language-plaintext highlighter-rouge">@Directive</code> (or <code class="language-plaintext highlighter-rouge">@Component</code>) decorator: <code class="language-plaintext highlighter-rouge">hostDirectives</code>.
It accepts an array of <em>standalone</em> directives, and will apply them on the host component.</p>
<p>Note: my following example is not working yet at the time of writing, as the Angular Material directives aren’t available as standalone directives. But they will probably be soon.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Directive({
selector: 'button[appButton]',
hostDirectives: [
{
directive: MatTooltip,
inputs: ['matTooltip', 'matTooltipHideDelay']
},
{
directive: CdkDrag,
inputs: ['cdkDragDisabled']
}
]
})
export class ButtonComponent {
}
</code></pre></div></div>
<p>You can specify which inputs should be exposed (by default, none are). They are exposed with the same name, but you can rename them:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Directive({
selector: 'button[appButton]',
hostDirectives: [
{
directive: MatTooltip,
inputs: [
'matTooltip: tooltip',
'matTooltipHideDelay: tooltipHideDelay'
]
},
{
directive: CdkDrag,
inputs: ['cdkDragDisabled: dragDisabled']
}
]
})
export class ButtonDirective {
}
</code></pre></div></div>
<p>And then use your directive like this 🎉:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><button appButton
tooltip="Info"
[tooltipHideDelay]="delay"
[dragDisabled]="isDisabled">
</button>
</code></pre></div></div>
<p>You can of course do the same with the outputs.
The type-checking will properly work,
host bindings will be applied,
DI will work (you can even inject the host directives into your directive/component), etc.
You can override lifecycle hooks if you want to.</p>
<p>The host directives are picked by view/content queries as well,
so this works:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// picks our ButtonDirective 🤯
@ViewChild(CdkDrag) drag!: CdkDrag;
</code></pre></div></div>
<p>If you want to dive deeper into this topic,
check out this <a href="https://www.youtube.com/watch?v=oC9Qd9yw3pE">talk from Kristiyan</a>.</p>
<p>Currently, the biggest limitation is that you can only apply standalone directives.</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Angular 14.2?2022-08-26T00:00:00+00:00https://blog.ninja-squad.com/2022/08/26/what-is-new-angular-14.2<p>Angular 14.2.0 is here!</p>
<p style="text-align: center;">
<a href="https://github.com/angular/angular/releases/tag/14.2.0">
<img class="rounded img-fluid" style="max-width: 100%" src="/assets/images/angular.png" alt="Angular logo" />
</a>
</p>
<p>This is a minor release, but it is packed with interesting features: let’s dive in!</p>
<h2 id="typescript-48">Typescript 4.8</h2>
<p>TypeScript v4.8 has just been released, and Angular is already compatible \o/.
Check out the <a href="https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/">Typescript v4.8 blog post</a> to learn more about the new features.</p>
<h2 id="ngoptimizedimage">NgOptimizedImage</h2>
<p>The biggest new feature in Angular 14.2 is the new <code class="language-plaintext highlighter-rouge">NgOptimizedImage</code> directive.
It is a standalone directive available as an experiment in the <code class="language-plaintext highlighter-rouge">@angular/common</code> package.</p>
<p>This directive helps you to optimize your images in your application.
To enable it, add it to the imports of one of your modules or standalone components:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>imports: [NgOptimizedImage]
</code></pre></div></div>
<p>Then to use it, you can simply replace the <code class="language-plaintext highlighter-rouge">src</code> of an image with <code class="language-plaintext highlighter-rouge">rawSrc</code>
(<em>update</em>: this is now <code class="language-plaintext highlighter-rouge">ngSrc</code> since Angular v15):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><img [rawSrc]="imageUrl" />
</code></pre></div></div>
<p>The directive then does its best to enforce best practices for this image.
For example, did you know that it is recommended to set the <code class="language-plaintext highlighter-rouge">width</code> and <code class="language-plaintext highlighter-rouge">height</code> attributes on the <code class="language-plaintext highlighter-rouge">img</code> tag to prevent layout shifts? See <a href="https://web.dev/patterns/web-vitals-patterns/images/img-tag/">this web.dev article</a> for more information.</p>
<p>If you use the <code class="language-plaintext highlighter-rouge">NgOptimizedImage</code> directive, then you get an error if <code class="language-plaintext highlighter-rouge">width</code> and <code class="language-plaintext highlighter-rouge">height</code>
are not properly set:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NG02954: The NgOptimizedImage directive (activated on an <img> element with the `rawSrc="/avatar.png"`) has detected that these required attributes are missing: "width", "height". Including "width" and "height" attributes will prevent image-related layout shifts. To fix this, include "width" and "height" attributes on the image tag.
</code></pre></div></div>
<p>This forces us to properly set the <code class="language-plaintext highlighter-rouge">width</code> and <code class="language-plaintext highlighter-rouge">height</code> attributes on the <code class="language-plaintext highlighter-rouge">img</code> tag.
But note that you need to set a width/height ratio coherent with your image’s intrinsic size.
For example, if the image is 800x600 pixels, then you need to set the <code class="language-plaintext highlighter-rouge">width</code> and <code class="language-plaintext highlighter-rouge">height</code> attributes to <code class="language-plaintext highlighter-rouge">800</code> and <code class="language-plaintext highlighter-rouge">600</code> respectively, or to values that respect the same ratio, like <code class="language-plaintext highlighter-rouge">400</code> and <code class="language-plaintext highlighter-rouge">300</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><img [rawSrc]="imageUrl" width="400" height="300" />
</code></pre></div></div>
<p>Otherwise, you get a warning letting you know that the image is distorted:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NG02952: The NgOptimizedImage directive (activated on an <img> element with the `rawSrc="/avatar.png"`) has detected that the aspect ratio of the image does not match the aspect ratio indicated by the width and height attributes. Intrinsic image size: 800w x 600h (aspect-ratio: 1.3333333333333333). Supplied width and height attributes: 300w x 300h (aspect-ratio: 1). To fix this, update the width and height attributes.
</code></pre></div></div>
<p>But the directive does more than just screaming at you 😉.</p>
<p>It automatically sets the <code class="language-plaintext highlighter-rouge">fetchpriority</code> attribute on the <code class="language-plaintext highlighter-rouge">img</code> tag.
This attribute is used by modern browsers to determine how it should prioritize the fetching of the image (see <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/fetchpriority">the MDN docs</a>). The directive will set the <code class="language-plaintext highlighter-rouge">fetchpriority</code> attribute to <code class="language-plaintext highlighter-rouge">high</code> if the image has the <code class="language-plaintext highlighter-rouge">priority</code> attribute (so the browser will fetch it right away), or to <code class="language-plaintext highlighter-rouge">auto</code> otherwise.</p>
<p>It also sets the <code class="language-plaintext highlighter-rouge">loading</code> attribute (see <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/loading">the MDN docs</a>) to <code class="language-plaintext highlighter-rouge">eager</code> if the image has the <code class="language-plaintext highlighter-rouge">priority</code> attribute, or to <code class="language-plaintext highlighter-rouge">lazy</code> otherwise.</p>
<p>This means that by default, the browser will only load images when they’re about to be visible in the viewport.</p>
<p>It checks a few more things when running in dev mode (<code class="language-plaintext highlighter-rouge">ng serve</code>).
If the image is treated by the browser as a Largest Contentful Paint (LCP) element (typically the case for above-the-fold images),
then it checks that the image has the <code class="language-plaintext highlighter-rouge">priority</code> attribute. If that’s not the case you get a warning in the console (<code class="language-plaintext highlighter-rouge">NG02955</code>).</p>
<p>Last but not least, the directive comes with the concept of “loaders”.
By default, the image is loaded from the <code class="language-plaintext highlighter-rouge">src</code> directory of your application, as usual.
But you can specify another loader if you are using a service like <a href="https://developers.cloudflare.com/images/image-resizing/">Cloudflare Image Resizing</a>,
<a href="https://cloudinary.com/">Cloudinary</a>, <a href="https://imagekit.io/">ImageKit</a> or <a href="https://imgix.com/">Imgix</a>.
To do so, you can define one of the provided loaders in your providers:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>providers: [
provideCloudflareLoader("https://ninja-squad.com/"),
// or `provideCloudinaryLoader`, `provideImageKitLoader`, `provideImgixLoader`
]
</code></pre></div></div>
<p>It is of course possible to create your own loader:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>providers: [
{
provide: IMAGE_LOADER,
useValue: (config: ImageLoaderConfig) => {
return `https://example.com/${config.src}-${config.width}.jpg}`;
}
}
]
</code></pre></div></div>
<p>The directive also supports width or density descriptors, like <code class="language-plaintext highlighter-rouge">400w</code> or <code class="language-plaintext highlighter-rouge">2x</code>,
with <code class="language-plaintext highlighter-rouge">rawSrcset</code> (<em>update</em>: this is now <code class="language-plaintext highlighter-rouge">ngSrcset</code> since Angular v15).</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><img rawSrcset="avatar.png" rawSrcset="100w, 200w" />
</code></pre></div></div>
<p>This directive is probably only useful in some specific cases, but it enforces best practices that we don’t always know as web developers.
It has been pushed by the <a href="https://developer.chrome.com/blog/introducing-aurora/">Aurora team</a> which is a collaboration between Chrome and open-source web frameworks.
Give it a try (and keep in mind this is experimental).
That’s why you can see similar work in other frameworks, like <a href="https://image.nuxtjs.org/">Nuxt Image</a> for example.</p>
<p>The Aurora team wrote <a href="https://developer.chrome.com/blog/angular-image-directive/">an in-depth article</a> if you want to learn more.</p>
<h2 id="core">Core</h2>
<p>A new function <code class="language-plaintext highlighter-rouge">createComponent()</code> has been added to the framework to help create components dynamically.
This is a replacement for the <code class="language-plaintext highlighter-rouge">ComponentFactory</code> that was usually used until it was deprecated in Angular v13.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const app = await bootstrapApplication(AppComponent);
const homeComponent = createComponent(HomeComponent, app.injector);
app.attachView(homeComponent.hostView);
</code></pre></div></div>
<p>Another new function called <code class="language-plaintext highlighter-rouge">createApplication</code> has been introduced to let developers start an application
without bootstrapping a component (unlike <code class="language-plaintext highlighter-rouge">bootstrapApplication</code>).
This can be useful if you want to render multiple root components in your application,
or if you are using Angular Elements like in the following example:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const app = await createApplication();
const HomeNgElementCtor = createCustomElement(HomeComponent, { injector: app.injector });
customElements.define('app-home', HomeNgElementCtor);
</code></pre></div></div>
<p>A low-level utility function called <code class="language-plaintext highlighter-rouge">reflectComponentType()</code> has also been added to the framework to help get the component metadata from a component type.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const mirror = reflectComponentType(UserComponent)!;
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">mirror</code> is a <code class="language-plaintext highlighter-rouge">ComponentMirror</code> object, which contains the metadata of the component:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">selector</code>, for example <code class="language-plaintext highlighter-rouge">app-user</code></li>
<li><code class="language-plaintext highlighter-rouge">type</code>, for example <code class="language-plaintext highlighter-rouge">UserComponent</code></li>
<li><code class="language-plaintext highlighter-rouge">inputs</code>, for example <code class="language-plaintext highlighter-rouge">[{ propName: 'userModel', templateName: 'userModel' }]</code></li>
<li><code class="language-plaintext highlighter-rouge">outputs</code>, for example <code class="language-plaintext highlighter-rouge">[{ propName: 'userSaved', templateName: 'userSaved' }]</code></li>
<li><code class="language-plaintext highlighter-rouge">ngContentSelectors</code>, for example <code class="language-plaintext highlighter-rouge">['*']</code></li>
<li><code class="language-plaintext highlighter-rouge">isStandalone</code>, for example <code class="language-plaintext highlighter-rouge">false</code></li>
</ul>
<p>Similarly, there is now a <code class="language-plaintext highlighter-rouge">provideRouterForTesting()</code> function that can be used in tests
instead of <code class="language-plaintext highlighter-rouge">RouterTestingModule</code>.</p>
<h2 id="forms">Forms</h2>
<p>Angular v14 introduced a new form element called <code class="language-plaintext highlighter-rouge">FormRecord</code>.
You can read more about it in our blog post about
<a href="/2022/04/21/strictly-typed-forms-angular">Strictly typed forms</a>.</p>
<p>But there was no method to create a <code class="language-plaintext highlighter-rouge">FormRecord</code> with the <code class="language-plaintext highlighter-rouge">FormBuilder</code>.
This is now fixed in Angular v14.2 (a small contribution from me 👉👈),
and you can use <code class="language-plaintext highlighter-rouge">fb.record({})</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.form = this.fb.group({
languages: this.fb.record({
english: true,
french: false
})
});
</code></pre></div></div>
<h2 id="standalone-components">Standalone components</h2>
<p>Angular v14.1 introduced the common directives and pipes as standalone entities,
v14.2 now introduces the router directives as standalone entities!</p>
<p>You can now import <code class="language-plaintext highlighter-rouge">RouterLinkWithHref</code> (for a <code class="language-plaintext highlighter-rouge">routerLink</code>), <code class="language-plaintext highlighter-rouge">RouterLinkActive</code> and <code class="language-plaintext highlighter-rouge">RouterOutlet</code> directly instead of importing the whole <code class="language-plaintext highlighter-rouge">RouterModule</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component({
standalone: true,
templateUrl: './user.component.html',
imports: [RouterLinkWithHref] // -> you can now use `routerLink` in the template
})
export class UserComponent {
</code></pre></div></div>
<p>Related to standalone components, the router is now usable without using <code class="language-plaintext highlighter-rouge">RouterModule</code>,
thanks to the new <code class="language-plaintext highlighter-rouge">provideRouter</code> function.</p>
<p>So instead of using:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(HttpClientModule),
importProvidersFrom(RouterModule.forRoot(ROUTES, { preloadingStrategy: PreloadAllModules }))
]
});
</code></pre></div></div>
<p>you can now write:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(HttpClientModule),
provideRouter(ROUTES,
withPreloading(PreloadAllModules)
)
]
});
</code></pre></div></div>
<p>Other <code class="language-plaintext highlighter-rouge">with...</code> functions are available to enable router features:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">withDebugTracing</code></li>
<li><code class="language-plaintext highlighter-rouge">withDisabledInitialNavigation</code></li>
<li><code class="language-plaintext highlighter-rouge">withEnabledBlockingInitialNavigation</code></li>
<li><code class="language-plaintext highlighter-rouge">withInMemoryScrolling</code></li>
<li><code class="language-plaintext highlighter-rouge">withRouterConfig</code></li>
</ul>
<p>These changes allow tree-shaking parts of the router module that aren’t actually used,
thus reducing the main bundle size.</p>
<h2 id="router">Router</h2>
<p>The router introduced the possibility of defining a page title on the route in Angular v14
(see <a href="/2022/06/02/what-is-new-angular-14.0">our blog post</a>).
With this v14.2 release, it is now possible to retrieve the resolved title on the <code class="language-plaintext highlighter-rouge">ActivatedRoute</code> and <code class="language-plaintext highlighter-rouge">ActivatedRouteSnapshot</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>constructor(private route: ActivatedRoute) {
this.title = route.snapshot.title;
}
</code></pre></div></div>
<p>It is now also possible to define guards and resolvers as simple functions.
You can now write something like:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
path: '/user/:id/edit',
component: EditUserComponent,
canDeactivate: [(component: EditUserComponent) => !component.hasUnsavedChanges]
}
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">RouterLink</code> directive received a tiny improvement that is noticeable:
all its boolean inputs (<code class="language-plaintext highlighter-rouge">preserveFragment</code>, <code class="language-plaintext highlighter-rouge">skipLocationChange</code> and <code class="language-plaintext highlighter-rouge">replaceUrl</code>)
now accept a string and coerce it to a boolean.
This means you can now write:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><a [routerLink]="['/user', user.id, 'edit']" skipLocationChange='true'>Edit</a>
<!-- or even -->
<a [routerLink]="['/user', user.id, 'edit']" skipLocationChange>Edit</a>
</code></pre></div></div>
<p>instead of:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><a [routerLink]="['/user', user.id, 'edit']" [skipLocationChange]="true">Edit</a>
</code></pre></div></div>
<h2 id="angular-cli">Angular CLI</h2>
<p>The new CLI version does not have many features this time.</p>
<p>One notable addition is the ability for <code class="language-plaintext highlighter-rouge">ng serve</code> to serve service workers.
It is enabled automatically if you have the option <code class="language-plaintext highlighter-rouge">"serviceWorker": true</code> in your builder configuration (which is the case by default when you add <code class="language-plaintext highlighter-rouge">@angular/pwa</code> to your application).
This is handy as it allows us to use the usual <code class="language-plaintext highlighter-rouge">ng serve</code> to test the PWA behavior,
whereas we previously had to build the application and serve it with another HTTP server to check it.</p>
<p>The work on the <code class="language-plaintext highlighter-rouge">esbuild</code> builder continues, and it is now faster to downlevel the JS code.
In the <code class="language-plaintext highlighter-rouge">esbuild</code> builder as well,
the Sass compilation now uses the <a href="https://sass-lang.com/documentation/js-api/">“modern API” of Sass</a>,
which is faster than the legacy one.
The classic <code class="language-plaintext highlighter-rouge">webpack</code> builder still uses the legacy Sass API,
but should switch to the modern one soon as well.</p>
<p>That’s all for this release, stay tuned!</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Angular 14.1?2022-07-21T00:00:00+00:00https://blog.ninja-squad.com/2022/07/21/what-is-new-angular-14.1<p>Angular 14.1.0 is here!</p>
<p style="text-align: center;">
<a href="https://github.com/angular/angular/releases/tag/14.1.0">
<img class="rounded img-fluid" style="max-width: 100%" src="/assets/images/angular.png" alt="Angular logo" />
</a>
</p>
<p>This is a minor release, but it is packed with interesting features: let’s dive in!</p>
<h2 id="router-new-guard-type-canmatch">Router new guard type: CanMatch</h2>
<p>The router gained a new guard type in this release: <code class="language-plaintext highlighter-rouge">CanMatch</code>.</p>
<p>The existing <code class="language-plaintext highlighter-rouge">CanActivate</code> guard decides whether or not a navigation can go through.
<code class="language-plaintext highlighter-rouge">CanLoad</code> guards decide if a module/component can be loaded.
But there is no guard that allows matching a route depending on business logic:
that’s what the <code class="language-plaintext highlighter-rouge">CanMatch</code> guard fixes.</p>
<p>It is now possible to define the same route several times, with different <code class="language-plaintext highlighter-rouge">CanMatch</code> guards,
and to navigate to a specific one:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[
{ path: '', component: LoggedInHomeComponent, canMatch: [IsLoggedIn] },
{ path: '', component: HomeComponent }
]
</code></pre></div></div>
<p>Note that a <code class="language-plaintext highlighter-rouge">CanMatch</code> guard that returns <code class="language-plaintext highlighter-rouge">false</code> does not cancel the navigation:
the route is skipped and the router simply continues matching other potential routes.</p>
<p>Here, navigating to <code class="language-plaintext highlighter-rouge">/</code> will render <code class="language-plaintext highlighter-rouge">LoggedInHomeComponent</code> if the user is logged in
and will render <code class="language-plaintext highlighter-rouge">HomeComponent</code> otherwise. Note that the URL will remain <code class="language-plaintext highlighter-rouge">/</code> in both cases.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Injectable({
providedIn: 'root'
})
export class IsLoggedIn implements CanMatch {
constructor(private userService: UserService) {}
canMatch(route: Route, segments: Array<UrlSegment>): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
return this.userService.isLoggedIn();
}
}
</code></pre></div></div>
<p>A <code class="language-plaintext highlighter-rouge">CanMatch</code> guard can also redirect to another route like other guards do.
To do so, you can return an <code class="language-plaintext highlighter-rouge">UrlTree</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Injectable({
providedIn: 'root'
})
export class IsLoggedIn implements CanMatch {
constructor(private userService: UserService, private router: Router) {}
canMatch(route: Route, segments: Array<UrlSegment>): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
return this.userService.isLoggedIn() || this.router.parseUrl('/');
}
}
</code></pre></div></div>
<p>As the route is not even considered when the <code class="language-plaintext highlighter-rouge">CanMatch</code> guard returns false,
it can be used to replace the <code class="language-plaintext highlighter-rouge">CanLoad</code> guard (and may even replace it in the future).</p>
<h2 id="router-navigation-events">Router navigation events</h2>
<p>The router now indicates why a navigation was canceled in a dedicated <code class="language-plaintext highlighter-rouge">code</code> field of the <code class="language-plaintext highlighter-rouge">NavigationCancel</code> event.
Previously, you could use the <code class="language-plaintext highlighter-rouge">reason</code> field of the event to get the same information,
but this was more a workaround than an intended feature.
The <code class="language-plaintext highlighter-rouge">code</code> can now be used, and the <code class="language-plaintext highlighter-rouge">reason</code> field should only be used for debugging purposes.
The <code class="language-plaintext highlighter-rouge">code</code> property can have the following values:
<code class="language-plaintext highlighter-rouge">NavigationCancellationCode.Redirect</code>, <code class="language-plaintext highlighter-rouge">NavigationCancellationCode.SupersededByNewNavigation</code>,
<code class="language-plaintext highlighter-rouge">NavigationCancellationCode.NoDataFromResolver</code>, or <code class="language-plaintext highlighter-rouge">NavigationCancellationCode.GuardRejected</code>.</p>
<p>The <code class="language-plaintext highlighter-rouge">NavigationError</code> also received a small improvement: the <code class="language-plaintext highlighter-rouge">target</code> of the navigation is now available in the event.</p>
<h2 id="standalone-components">Standalone components</h2>
<p>The built-in Angular directives and pipes offered by <code class="language-plaintext highlighter-rouge">CommonModule</code> (<code class="language-plaintext highlighter-rouge">NgIf</code>, <code class="language-plaintext highlighter-rouge">NgFor</code>, <code class="language-plaintext highlighter-rouge">DatePipe</code>, <code class="language-plaintext highlighter-rouge">DecimalPipe</code>, <code class="language-plaintext highlighter-rouge">AsyncPipe</code>, etc.)
are now available as standalone!</p>
<p>You can now import them directly, without having to import <code class="language-plaintext highlighter-rouge">CommonModule</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component({
standalone: true,
templateUrl: './user.component.html',
imports: [NgIf, DecimalPipe] // -> you can now use `*ngIf` and `| number` in the template
})
export class UserComponent {
</code></pre></div></div>
<p>A new function called <code class="language-plaintext highlighter-rouge">provideAnimations()</code> is also available to add the animation providers to your application,
instead of importing <code class="language-plaintext highlighter-rouge">BrowserAnimationModule</code>.
Similarly, you can use <code class="language-plaintext highlighter-rouge">provideNoopAnimations</code> instead of importing the <code class="language-plaintext highlighter-rouge">BrowserNoopAnimationsModule</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bootstrapApplication(AppComponent, {
providers: [provideAnimations()]
});
</code></pre></div></div>
<h2 id="inject-function">inject function</h2>
<p>The new <code class="language-plaintext highlighter-rouge">inject</code> function introduced in Angular v14.0 allows injecting dependencies
(see <a href="/2022/06/02/what-is-new-angular-14.0">our last blog post for more info</a>),
and a second parameter can be used to define the injection flag.
In v14, the second parameter was a bit field: <code class="language-plaintext highlighter-rouge">InjectFlags.Host</code>, <code class="language-plaintext highlighter-rouge">InjectFlags.Optional</code>, <code class="language-plaintext highlighter-rouge">InjectFlags.Self</code>, or <code class="language-plaintext highlighter-rouge">InjectFlags.SkipSelf</code>.
In v14.1, this signature has been deprecated in favor of a more ergonomic one with an object as the second parameter:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>value = inject(TOKEN, { optional: false });
</code></pre></div></div>
<p>The cool thing is that it improved the type safety of the function.
Previously, TypeScript had no idea of the flag signification, and the return type was always <code class="language-plaintext highlighter-rouge">T | null</code>,
even if the injection was not optional.
This is now working properly, and the above example has a return type <code class="language-plaintext highlighter-rouge">T</code>.</p>
<h2 id="runincontext-function">runInContext function</h2>
<p>The <code class="language-plaintext highlighter-rouge">inject</code> function mentioned above only works in the constructor,
or to initialize a field, or in a factory function.</p>
<p>So, how can you use it in a method/function that is not a constructor?
You can use the <code class="language-plaintext highlighter-rouge">EnvironmentInjector.runInContext</code> function that has been introduced for this purpose in v14.1!</p>
<p>For example, this doesn’t work:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export class AppComponent implements OnInit {
ngOnInit() {
console.log('AppComponent initialized', inject(UserService));
}
}
</code></pre></div></div>
<p>But this does, thanks to <code class="language-plaintext highlighter-rouge">runInContext</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export class AppComponent {
constructor(private injector: EnvironmentInjector) {}
ngOnInit() {
this.injector.runInContext(() => {
console.log('AppComponent initialized', inject(UserService));
});
}
}
</code></pre></div></div>
<h2 id="setinput">setInput</h2>
<p><code class="language-plaintext highlighter-rouge">ComponentRef</code> has a new method called <code class="language-plaintext highlighter-rouge">setInput</code> that can be called to set an input.
Why is that interesting?</p>
<p>Currently when you are testing an <code class="language-plaintext highlighter-rouge">OnPush</code> component,
it is not easy to test if the change of an input properly triggers what you want,
because manually setting the input does not trigger the change detection.</p>
<p>This is now no longer a problem if you call <code class="language-plaintext highlighter-rouge">setInput()</code>!
If your <code class="language-plaintext highlighter-rouge">UserComponent</code> component has an input called <code class="language-plaintext highlighter-rouge">userModel</code>, you can now write the following code in a test:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> const fixture = TestBed.createComponent(UserComponent);
fixture.componentRef.setInput('userModel', newUser);
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">setInput()</code> properly sets the input (even if it is aliased), calls the <code class="language-plaintext highlighter-rouge">NgOnChanges</code> lifecycle hook and triggers the change detection!</p>
<p>This feature is useful in tests, but also with any kind of dynamic component.
It even opens the door for the router to set the inputs of a component dynamically based on route params
(a feature that the Vue router has for example). Maybe we’ll see that in a future release!</p>
<h2 id="contentchild-descendants">ContentChild descendants</h2>
<p>The <code class="language-plaintext highlighter-rouge">ContentChild</code> decorator now supports the <code class="language-plaintext highlighter-rouge">descendants</code> option,
as <code class="language-plaintext highlighter-rouge">ContentChildren</code> does.
The default behavior does not change, and if you don’t specify the option,
then <code class="language-plaintext highlighter-rouge">ContentChild</code> looks for the query in the descendants.
This behavior is the same as specifying <code class="language-plaintext highlighter-rouge">@ContentChild({ descendants: true })</code>.
But you can now change it by specifying <code class="language-plaintext highlighter-rouge">@ContentChild({ descendants: false })</code>,
in which case Angular will only do a “shallow” search and look for the direct descendants.</p>
<h2 id="extended-template-diagnostics">Extended template diagnostics</h2>
<p>The team added a few more “extended diagnostics”.</p>
<p>The first one is <code class="language-plaintext highlighter-rouge">missingControlFlowDirective</code>, and it’s linked to the Standalone Components story.</p>
<p>With this check enabled, the compiler warns us when a <code class="language-plaintext highlighter-rouge">ngIf</code>, <code class="language-plaintext highlighter-rouge">ngFor</code>, or <code class="language-plaintext highlighter-rouge">ngSwitch</code> is used in the template of a standalone component, but the corresponding directive or the <code class="language-plaintext highlighter-rouge">CommonModule</code> is not imported:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: src/app/register/register.component.html:11:59 - error NG8103:
The `*ngFor` directive was used in the template,
but neither the `NgForOf` directive nor the `CommonModule` was imported.
Please make sure that either the `NgForOf` directive or the `CommonModule`
is included in the `@Component.imports` array of this component.
</code></pre></div></div>
<p>This is a nice addition, as it can be fairly easy to forget to import <code class="language-plaintext highlighter-rouge">CommonModule</code> or the directive itself
as I pointed out in <a href="/2022/05/12/a-guide-to-standalone-components-in-angular">our guide to standalone components</a>.
The message even mentions the directive you need to import, which can be tricky to figure out for <code class="language-plaintext highlighter-rouge">*ngFor</code>.</p>
<p>The second extended diagnostics is <code class="language-plaintext highlighter-rouge">textAttributeNotBinding</code>.
When enabled, the compiler warns us when a <code class="language-plaintext highlighter-rouge">class</code>, <code class="language-plaintext highlighter-rouge">style</code>, or <code class="language-plaintext highlighter-rouge">attr</code> binding does not have the <code class="language-plaintext highlighter-rouge">[]</code>,
or if the value is not interpolated.
For example, a template with <code class="language-plaintext highlighter-rouge">class.blue="true"</code> yields the following:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: src/app/register/register.component.html:2:8 - error NG8104:
Attribute, style, and class bindings should be
enclosed with square braces. For example, '[class.blue]="true"'.
</code></pre></div></div>
<p>Slightly related, the third one is <code class="language-plaintext highlighter-rouge">suffixNotSupported</code>.
When enabled, the compiler warns us when a suffix like <code class="language-plaintext highlighter-rouge">px</code>, <code class="language-plaintext highlighter-rouge">%</code> or <code class="language-plaintext highlighter-rouge">em</code> is used on attribute binding where it doesn’t work, unlike when used in a style binding:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: src/app/register/register.component.html:2:9 - error NG8106:
The '.px', '.%', '.em' suffixes are only supported on style bindings.
</code></pre></div></div>
<p>The fourth one is <code class="language-plaintext highlighter-rouge">missingNgForOfLet</code>.
when enabled, the compiler warns us when a <code class="language-plaintext highlighter-rouge">*ngFor</code> is used with the <code class="language-plaintext highlighter-rouge">let</code> keyword.
For example, <code class="language-plaintext highlighter-rouge">*ngFor="user of users"</code> throws with:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: src/app/users/users.component.html:1:7 - error NG8105:
Your ngFor is missing a value. Did you forget to add the `let` keyword?
</code></pre></div></div>
<p>The fifth and last one is <code class="language-plaintext highlighter-rouge">optionalChainNotNullable</code>, and it is slightly similar to the already existing <code class="language-plaintext highlighter-rouge">nullishCoalescingNotNullable</code> check. When enabled, the compiler warns us when an unnecessary optional check is used.
For example, if <code class="language-plaintext highlighter-rouge">user</code> is not nullable, then using `` in a template yields:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: src/app/user/user.component.html:2:21 - error NG8107:
The left side of this optional chain operation does not include 'null' or 'undefined' in its type,
therefore the '?.' operator can be replaced with the '.' operator.
</code></pre></div></div>
<h2 id="zonejs">Zone.js</h2>
<p><code class="language-plaintext highlighter-rouge">zone.js</code> has also been released in version v0.11.7, and contains a new feature that improves the debugging of asynchronous tasks, by using an experimental feature of Chrome.
You can leverage this new support by importing <code class="language-plaintext highlighter-rouge">import 'zone.js/plugins/async-stack-tagging';</code>.
When this is enabled, you’ll have nicer stack traces in case of an error in an async task.</p>
<h2 id="angular-cli">Angular CLI</h2>
<p>As usual, you can check out our dedicated article about the new CLI version:</p>
<p>👉 <a href="/2022/07/21/angular-cli-14.1">Angular CLI v14.1</a></p>
<p>That’s all for this release, stay tuned!</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Angular CLI 14.1?2022-07-21T00:00:00+00:00https://blog.ninja-squad.com/2022/07/21/angular-cli-14.1<p><a href="https://github.com/angular/angular-cli/releases/tag/14.1.0">Angular CLI 14.1.0</a> is out!✨</p>
<p>If you want to upgrade to 14.1.0 without pain (or to any other version, by the way), I have created a Github project to help: <a href="https://github.com/cexbrayat/angular-cli-diff">angular-cli-diff</a>. Choose the version you’re currently using (13.2.0 for example), and the target version (14.1.0 for example), and it gives you a diff of all files created by the CLI: <a href="https://github.com/cexbrayat/angular-cli-diff/compare/13.2.0...14.1.0">angular-cli-diff/compare/13.2.0…14.1.0</a>.
It can be a great help along with the official <code class="language-plaintext highlighter-rouge">ng update @angular/core @angular/cli</code> command.
You have no excuse for staying behind anymore!</p>
<p>Let’s see what we’ve got in this release.</p>
<h2 id="npm-init--yarn-create">npm init / yarn create</h2>
<p>It is now possible to create a new Angular CLI project by using:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm init @angular
yarn create @angular
</code></pre></div></div>
<p>Using this avoids the need to install the Angular CLI package globally.
The commands support the same options as <code class="language-plaintext highlighter-rouge">ng new</code>.</p>
<h2 id="esbuild-builder-improvements">esbuild builder improvements</h2>
<p>The new experimental builder that uses <a href="https://esbuild.github.io/">esbuild</a>
has been introduced in v14, but with some missing features.</p>
<p>Even if it is still not feature complete, the esbuild builder now supports service workers and Sass files.</p>
<p>It also allows declaring external dependencies with the new <code class="language-plaintext highlighter-rouge">externalDependencies</code> option.
When dependencies are listed in this option, they are excluded from the generated bundle.
Instead, the created bundle relies on these dependencies to be available during runtime.</p>
<p>So for example if you define <code class="language-plaintext highlighter-rouge">externalDependencies: ['@angular/core']</code>,
then the <code class="language-plaintext highlighter-rouge">@angular/core</code> package will not be bundled,
and you’ll need to include it on your page in another way.</p>
<p>This is useful if you want to run several Angular applications on the same page,
and use import maps to load Angular itself just once (instead of loading it in every bundle).</p>
<p>You’ll find more interesting features in our article about the
<a href="/2022/07/21/what-is-new-angular-14.1">framework v14.1.0 release</a>.</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Angular 14?2022-06-02T00:00:00+00:00https://blog.ninja-squad.com/2022/06/02/what-is-new-angular-14.0<p>Angular 14.0.0 is here!</p>
<p style="text-align: center;">
<a href="https://github.com/angular/angular/releases/tag/14.0.0">
<img class="rounded img-fluid" style="max-width: 100%" src="/assets/images/angular.png" alt="Angular logo" />
</a>
</p>
<p>This is a major release with really interesting features: let’s dive in!</p>
<h2 id="strictly-typed-forms">Strictly typed forms</h2>
<p>The <a href="https://github.com/angular/angular/issues/13721">most up-voted issue</a>
in the Angular repository is solved in Angular v14:
we now have strictly typed forms!</p>
<p>As there is quite a bit to explain between the migration,
the new API, and the addition of <code class="language-plaintext highlighter-rouge">FormRecord</code> and <code class="language-plaintext highlighter-rouge">NonNullableFormBuilder</code>,
we wrote a dedicated blog post:</p>
<p>👉 <a href="/2022/04/21/strictly-typed-forms-angular">Strictly typed forms</a></p>
<p>TL;DR: with some elbow grease, it’s now possible to have form values
perfectly typed, and no longer of type <code class="language-plaintext highlighter-rouge">any</code> ✨.</p>
<h4 id="other-forms-improvements">Other forms improvements</h4>
<p>It is now possible to use negative indices on <code class="language-plaintext highlighter-rouge">FormArray</code> methods,
like the <code class="language-plaintext highlighter-rouge">Array</code> methods do in JavaScript.
For example <code class="language-plaintext highlighter-rouge">formArray.at(-1)</code> is now allowed and returns the last control of the form array.</p>
<h2 id="standalone-components-see-ya-ngmodule">Standalone components (see ya NgModule!)</h2>
<p>The other big feature of the release is the addition of the (experimental) standalone APIs.
Same here: as there is a lot to cover, we wrote a dedicated blog post:</p>
<p>👉 <a href="/2022/05/12/a-guide-to-standalone-components-in-angular">A guide to standalone components</a></p>
<p>TL;DR: it’s now possible (but experimental) to get rid of NgModule in your applications,
and use the new standalone components/directives and pipes ✨.</p>
<h2 id="inject-function">inject function</h2>
<p>You can now use the <code class="language-plaintext highlighter-rouge">inject()</code> function from <code class="language-plaintext highlighter-rouge">'@angular/core'</code> (which already existed but has been improved)
to inject a token programmatically.</p>
<p>For example you can use it in a component:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>constructor() {
const userService = inject(UserService);
// ...
}
</code></pre></div></div>
<p>It can only be called in some specific areas:</p>
<ul>
<li>in a constructor as above;</li>
<li>to initialize a class field;</li>
<li>in a factory function.</li>
</ul>
<p>It opens the door to some interesting patterns,
especially for library authors.</p>
<h2 id="router">Router</h2>
<p>The router received a lot of attention in this release.</p>
<h4 id="page-title">Page title</h4>
<p>It’s now possible to set a page title directly in a route declaration:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export const ROUTES: Routes = [
{ path: '', title: 'Ninja Squad | Home', component: HomeComponent },
{ path: 'trainings', title: 'Ninja Squad | Trainings', component: TrainingsComponent }
]
</code></pre></div></div>
<p>You can also define a resolver for the title:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export const ROUTES: Routes = [
{ path: 'trainings/:trainingId', title: TrainingTitleResolver, component: TrainingComponent }
]
@Injectable({
providedIn: 'root'
})
export class TrainingTitleResolver implements Resolve<string> {
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): string {
return `Ninja Squad | Training ${route.paramMap.get('trainingId')}`;
}
}
</code></pre></div></div>
<p>This is not super flexible though, as you’ll probably often want to display something more meaningful
like <code class="language-plaintext highlighter-rouge">Ninja Squad | Angular training</code> and not <code class="language-plaintext highlighter-rouge">Ninja Squad | Training 13</code>,
and that data lives in the component: the resolver can’t access it.
But it’s still a nice addition for adding static (or not too dynamic) titles.</p>
<p>It’s also possible to write a custom strategy to build the title
by extending the built-in <code class="language-plaintext highlighter-rouge">TitleStrategy</code>.
For example, if I want to prepend <code class="language-plaintext highlighter-rouge">Ninja Squad | </code> to all titles, I can do:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Injectable()
export class CustomTitleStrategyService extends TitleStrategy {
constructor(@Inject(DOCUMENT) private readonly document: Document) {
super();
}
override updateTitle(state: RouterStateSnapshot) {
const title = this.buildTitle(state);
this.document.title = `Ninja Squad | ${title}`;
}
}
</code></pre></div></div>
<p>Then, use this custom strategy instead of the default one:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@NgModule({
//...
providers: [{ provide: TitleStrategy, useClass: CustomTitleStrategyService }]
})
export class AppModule {}
</code></pre></div></div>
<p>And just define the specific part of the title on each route:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export const ROUTES: Routes = [
{ path: '', title: 'Home', component: HomeComponent },
{ path: 'trainings', title: 'Trainings', component: TrainingsComponent }
]
</code></pre></div></div>
<h4 id="types">Types</h4>
<p>Some types have been improved in the router.
For example, all router events now have a <code class="language-plaintext highlighter-rouge">type</code> property, allowing to narrow their type like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// send hits to analytics API only on navigation end events
this.router.events
.pipe(
filter((event: Event): event is NavigationEnd => event.type === EventType.NavigationEnd),
mergeMap(event => this.sendHit(event.url))
)
.subscribe();
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">pathMatch</code> is now also more strictly typed and only accepts the two valid options <code class="language-plaintext highlighter-rouge">'full'|'prefix'</code>.
That’s why a migration will add the explicit <code class="language-plaintext highlighter-rouge">Route</code> or <code class="language-plaintext highlighter-rouge">Routes</code> types on your routes declaration
if you don’t have them when running <code class="language-plaintext highlighter-rouge">ng update</code>. Otherwise, TypeScript will be unhappy with <code class="language-plaintext highlighter-rouge">pathMatch: 'full'</code> as it’ll think that <code class="language-plaintext highlighter-rouge">'full'</code> is a <code class="language-plaintext highlighter-rouge">string</code> and not a const.</p>
<h4 id="route-providers-standalone-routes-loadcomponent">Route providers, standalone routes, loadComponent</h4>
<p>A bunch of new things have been added to the router to support the new standalone APIs.
For example, you can now define providers directly on a route, or lazy-load just a component.
Check out our blog post about <a href="/2022/05/12/a-guide-to-standalone-components-in-angular">standalone components to learn more</a>.</p>
<h4 id="accessibility">Accessibility</h4>
<p><code class="language-plaintext highlighter-rouge">routerLinkActive</code> gained a new input called <code class="language-plaintext highlighter-rouge">ariaCurrentWhenActive</code>,
which allows to set <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current"><code class="language-plaintext highlighter-rouge">aria-current</code> a11y property</a>.
The possible values are <code class="language-plaintext highlighter-rouge">'page' | 'step' | 'location' | 'date' | 'time' | true | false</code>.
For example:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><a class="nav-link" routerLink="/" routerLinkActive="active" ariaCurrentWhenActive="page">Home</a>
</code></pre></div></div>
<h2 id="testbed">TestBed</h2>
<p>It’s now possible to configure the TestBed
to throw errors on unknown elements or properties found in a template.
Currently, Angular has two compilation modes: Just in Time (jit) and Ahead of Time (aot).
When you run <code class="language-plaintext highlighter-rouge">ng serve</code> or <code class="language-plaintext highlighter-rouge">ng build</code>, the aot mode is used.
But when running <code class="language-plaintext highlighter-rouge">ng test</code>, the jit compilation is used.
Weirdly enough, an error in a template results in just a warning in the console when the template compilation fails on an unknown element or property in jit mode.
That’s why you can sometimes see <code class="language-plaintext highlighter-rouge">NG0303</code> and <code class="language-plaintext highlighter-rouge">NG0304</code> warnings in the console when you run your tests,
typically when you forgot to import or declare a component/directive necessary to test your component.</p>
<p>Starting with Angular v14, it is now possible to configure the TestBed to throw an error for these issues,
and thus make sure we don’t miss them. I think this is amazing (but that’s probably because I implemented it 😬):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting(),
{
errorOnUnknownElements: true,
errorOnUnknownProperties: true
}
);
</code></pre></div></div>
<p>The default for <code class="language-plaintext highlighter-rouge">errorOnUnknownElements</code> and <code class="language-plaintext highlighter-rouge">errorOnUnknownProperties</code> is <code class="language-plaintext highlighter-rouge">false</code>,
but we’ll probably change it to true in a future release.
You can also enable/disable them in a specific test with <code class="language-plaintext highlighter-rouge">TestBed.configureTestingModule({ /*...*/, errorOnUnknownElements: false })</code>.</p>
<p>In the distant future, the tests will maybe use the aot compilation,
but <a href="https://github.com/angular/angular/issues/43133#issuecomment-941151334">that’s not for tomorrow</a>.
In the meantime, these new options should be helpful!</p>
<h2 id="compiler">Compiler</h2>
<p>As you probably know, you can’t use a <code class="language-plaintext highlighter-rouge">private</code> member of a component in a template,
and you can only use <code class="language-plaintext highlighter-rouge">public</code> members.
Starting with v14, you can now also use <code class="language-plaintext highlighter-rouge">protected</code> members of a component in the template.</p>
<h2 id="http">Http</h2>
<p>This is more a bugfix than a feature, but as it is a breaking change that may have an impact,
let’s talk about it: <code class="language-plaintext highlighter-rouge">+</code> in query params are now properly encoded as <code class="language-plaintext highlighter-rouge">%2B</code>.
They used to be ignored by the <code class="language-plaintext highlighter-rouge">HttpClient</code> that was otherwise properly encoding the
other special characters in query parameters.
You had to manually take care of the <code class="language-plaintext highlighter-rouge">+</code> signs, but this is no longer necessary:
you can now delete the code that was manually encoding them after upgrading to v14.</p>
<h2 id="zonejs">Zone.js</h2>
<p>Zone.js now supports <code class="language-plaintext highlighter-rouge">Promise.any()</code>, a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any">new method</a> introduced in ES2021.</p>
<h2 id="service-worker">Service worker</h2>
<p>The <code class="language-plaintext highlighter-rouge">versionUpdates</code> observable now emits <code class="language-plaintext highlighter-rouge">NoNewVersionDetectedEvent</code>
if the service worker did not find a newer version.</p>
<h2 id="devtools">Devtools</h2>
<p>The Angular Devtools are now <a href="https://addons.mozilla.org/fr/firefox/addon/angular-devtools/">available on Firefox</a> as well 🎉.</p>
<h2 id="typescript-and-node">Typescript and Node</h2>
<p>Angular v14 drops the support of TypeScript v4.4 and 4.5 and now supports for v4.7
(recently <a href="https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/">released</a>).
It also drops the support of Node v12.</p>
<h2 id="angular-cli">Angular CLI</h2>
<p>As usual, you can check out our dedicated article about the new CLI version:</p>
<p>👉 <a href="/2022/06/02/angular-cli-14.0">Angular CLI v14</a></p>
<h2 id="summary">Summary</h2>
<p>This release is packed with features as you can see,
and the future is exciting with the standalone APIs.
The roadmap now also mentions some efforts on the server-side rendering story,
which is not the strong suite of Angular (compared to other mainstream frameworks).</p>
<p>That’s all for this release, stay tuned!</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Angular CLI 14?2022-06-02T00:00:00+00:00https://blog.ninja-squad.com/2022/06/02/angular-cli-14.0<p><a href="https://github.com/angular/angular-cli/releases/tag/14.0.0">Angular CLI 14.0.0</a> is out!✨</p>
<p>If you want to upgrade to 14.0.0 without pain (or to any other version, by the way), I have created a Github project to help: <a href="https://github.com/cexbrayat/angular-cli-diff">angular-cli-diff</a>. Choose the version you’re currently using (12.2.0 for example), and the target version (14.0.0 for example), and it gives you a diff of all files created by the CLI: <a href="https://github.com/cexbrayat/angular-cli-diff/compare/13.2.0...14.0.0">angular-cli-diff/compare/13.2.0…14.0.0</a>.
It can be a great help along with the official <code class="language-plaintext highlighter-rouge">ng update @angular/core @angular/cli</code> command.
You have no excuse for staying behind anymore!</p>
<p>Let’s see what we’ve got in this release.</p>
<h2 id="autocompletion">Autocompletion</h2>
<p>The first time you’re going to run an <code class="language-plaintext highlighter-rouge">ng </code> command in your terminal,
you’ll see that Angular CLI will ask about setting up the autocompletion.
If you accept, then pressing <code class="language-plaintext highlighter-rouge"><TAB></code> after typing <code class="language-plaintext highlighter-rouge">ng</code> will list the available commands and options.
However, this works only for Bash and Zsh shells on macOS and Linux operating systems.</p>
<p>If you don’t want to enable it and then change your mind,
you can run <code class="language-plaintext highlighter-rouge">ng completion</code> to set it up manually.</p>
<h2 id="esbuild-builder">esbuild builder</h2>
<p>A new experimental builder that uses <a href="https://esbuild.github.io/">esbuild</a>
instead of Webpack has been introduced.</p>
<p>You can give it a try by replacing <code class="language-plaintext highlighter-rouge">@angular-devkit/build-angular:browser</code> with <code class="language-plaintext highlighter-rouge">@angular-devkit/build-angular:browser-esbuild</code> in your <code class="language-plaintext highlighter-rouge">angular.json</code> file.
Note that the new builder is far from being complete.
It does not support a bunch of options (and does not understand Sass for example).</p>
<p>These are very early days of course, but you can give it a try if you’re curious.
It looks very promising, and we can hope for a faster builder in the future that would rely on esbuild.
And, who knows, a CLI that uses Vite instead of Webpack
(check out <a href="https://blog.ninja-squad.com/2022/02/23/getting-started-with-vite-and-vue/">our article about Vite</a> to learn more).</p>
<h2 id="ng-cache">ng cache</h2>
<p>A new <code class="language-plaintext highlighter-rouge">ng cache </code> command has been added to manage the cache system introduced in CLI v13
(see <a href="/2021/11/03/angular-cli-13.0/">our article</a>).</p>
<p>This new command has 4 subcommands:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">ng cache enable</code> to enable the cache.</li>
<li><code class="language-plaintext highlighter-rouge">ng cache disable</code> to disable the cache.</li>
<li><code class="language-plaintext highlighter-rouge">ng cache clean</code> to delete the cache from disk (useful when switching branches and Webpack gets lost).</li>
<li><code class="language-plaintext highlighter-rouge">ng cache info</code> which will print statistics and information about the cache.</li>
</ul>
<h2 id="ng-generate">ng generate</h2>
<p><code class="language-plaintext highlighter-rouge">ng generate</code> has a new <code class="language-plaintext highlighter-rouge">--standalone</code> flag to generate
<a href="/2022/05/12/a-guide-to-standalone-components-in-angular">standalone components/pipes/directives</a>.</p>
<p>It is now also possible to specify multiple schematics collections with <code class="language-plaintext highlighter-rouge">schematicCollections</code> in your <code class="language-plaintext highlighter-rouge">angular.json</code> file (instead of the now deprecated <code class="language-plaintext highlighter-rouge">defaultCollection</code>).</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"cli": {
"schematicCollections": ["@schematics/angular", "@angular/material"]
}
</code></pre></div></div>
<p>The CLI will then look for schematics in the specified list,
following the order of priority (the first schematic wins).</p>
<h2 id="ng-e2e-lint-and-deploy">ng e2e, lint and deploy</h2>
<p>The <code class="language-plaintext highlighter-rouge">ng e2e</code>, <code class="language-plaintext highlighter-rouge">ng lint</code>, and <code class="language-plaintext highlighter-rouge">ng deploy</code> don’t come with an implementation as you may know.
All these commands now ask you what implementation you’d like to add when you run them for the first time.
<code class="language-plaintext highlighter-rouge">ng lint</code> only offers <code class="language-plaintext highlighter-rouge">ESLint</code>, but <code class="language-plaintext highlighter-rouge">ng e2e</code> lets you pick between <code class="language-plaintext highlighter-rouge">Cypress</code>/<code class="language-plaintext highlighter-rouge">Nightwatch</code>/<code class="language-plaintext highlighter-rouge">WebdriverIO</code>, and <code class="language-plaintext highlighter-rouge">ng deploy</code> between <code class="language-plaintext highlighter-rouge">Amazon S3</code>/<code class="language-plaintext highlighter-rouge">Azure</code>/<code class="language-plaintext highlighter-rouge">Firebase</code>/<code class="language-plaintext highlighter-rouge">Netlify</code>/<code class="language-plaintext highlighter-rouge">NPM</code>/<code class="language-plaintext highlighter-rouge">GitHub Pages</code>.</p>
<h2 id="fun-with-flags">Fun with flags</h2>
<p>Some deprecated options have been removed and a schematic will automatically take care of migrating your project:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">--all</code> option from <code class="language-plaintext highlighter-rouge">ng update</code> has been removed without replacement</li>
<li><code class="language-plaintext highlighter-rouge">--prod</code> option has been removed from all builders. <code class="language-plaintext highlighter-rouge">--configuration production</code>/<code class="language-plaintext highlighter-rouge">-c production</code> should be used instead.</li>
<li><code class="language-plaintext highlighter-rouge">showCircularDependencies</code> option from <code class="language-plaintext highlighter-rouge">ng build</code> has been removed without replacement</li>
<li><code class="language-plaintext highlighter-rouge">defaultProject</code> workspace option has been deprecated (the default is now the current working directory)</li>
</ul>
<p>Note that the flag parser of the CLI changed and that camelCase arguments are no longer supported:
for example, you need to write <code class="language-plaintext highlighter-rouge">ng g c hello --skip-selector</code> instead of <code class="language-plaintext highlighter-rouge">ng g c hello --skipSelector</code>.</p>
<p>You’ll find more interesting features in our article about the
<a href="/2022/06/02/what-is-new-angular-14.0">framework v14.0.0 release</a>.</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
A guide to Standalone Components in Angular2022-05-12T00:00:00+00:00https://blog.ninja-squad.com/2022/05/12/a-guide-to-standalone-components-in-angular<p>Angular v14 introduces one major (experimental) feature,
after <a href="https://github.com/angular/angular/discussions/45554">months of discussion</a>:
the possibility to declare standalone components/pipes/directives,
and to get rid of <code class="language-plaintext highlighter-rouge">NgModule</code> in your application if you want to 😍.</p>
<p>In this article, we’ll see:</p>
<ul>
<li>how to declare standalone entities,</li>
<li>how to use them in existing applications</li>
<li>how to get rid of <code class="language-plaintext highlighter-rouge">NgModule</code> if you want to</li>
<li>how the router has changed to leverage this new feature</li>
<li>and more nerdy details!</li>
</ul>
<p><em>Disclaimer:</em> this blog post is based on early releases of Angular v14,
and some details may change based on the feedback the Angular team gets.
That’s why, for once, we write a blog post on a feature before its final release:
this is a great opportunity to give it a try and gather feedback!</p>
<h2 id="standalone-components">Standalone components</h2>
<p>Components, directives, and pipes can now be declared as <code class="language-plaintext highlighter-rouge">standalone</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component({
selector: 'ns-image',
standalone: true,
templateUrl: './image.component.html'
})
export class ImageComponent {
}
</code></pre></div></div>
<p>When that’s the case,
the component/directive/pipe can’t be declared in an <code class="language-plaintext highlighter-rouge">NgModule</code>.
But it can be directly imported into another standalone component.
For example, if my <code class="language-plaintext highlighter-rouge">ImageComponent</code> above is used in the template of a standalone <code class="language-plaintext highlighter-rouge">UserComponent</code>,
you have to import <code class="language-plaintext highlighter-rouge">ImageComponent</code> in <code class="language-plaintext highlighter-rouge">UserComponent</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component({
selector: 'ns-user',
standalone: true,
imports: [ImageComponent],
templateUrl: './user.component.html'
// uses `<ns-image>`
})
export class UserComponent {
}
</code></pre></div></div>
<p>This is true for every component/directive/pipe you use in a standalone component.
So if the template of <code class="language-plaintext highlighter-rouge">UserComponent</code> also uses a standalone <code class="language-plaintext highlighter-rouge">FromNowPipe</code> and a standalone <code class="language-plaintext highlighter-rouge">BorderDirective</code>, then they have to be declared into the imports of the component:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component({
selector: 'ns-user',
standalone: true,
imports: [ImageComponent, FromNowPipe, BorderDirective],
templateUrl: './user.component.html'
// uses `<ns-image>`, `fromNow` and `nsBorder`
})
export class UserComponent {
}
</code></pre></div></div>
<p>This is also true for components, directives, and pipes offered by Angular itself.
If you want to use <code class="language-plaintext highlighter-rouge">ngIf</code> in a template, the directive has to be declared.
But <code class="language-plaintext highlighter-rouge">ngIf</code> is not a standalone directive: it is offered via the <code class="language-plaintext highlighter-rouge">CommonModule</code>.
That’s why <code class="language-plaintext highlighter-rouge">imports</code> lets you import any <code class="language-plaintext highlighter-rouge">NgModule</code> used as well:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component({
selector: 'ns-user',
standalone: true,
imports: [CommonModule, RouterModule, ImageComponent, FromNowPipe, BorderDirective],
templateUrl: './user.component.html'
// uses `*ngIf`, `routerLink`, `<ns-image>`, `fromNow` and `nsBorder`
})
export class UserComponent {
}
</code></pre></div></div>
<p>You can of course import your own existing modules or modules offered by third-party libraries.
If you use the <code class="language-plaintext highlighter-rouge">DragDropModule</code> from Angular Material for example:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component({
selector: 'ns-user',
standalone: true,
imports: [CommonModule, RouterModule, DragDropModule, ImageComponent],
templateUrl: './user.component.html'
// uses `*ngIf`, `routerLink`, `cdkDrag`, `<ns-image>`
})
export class UserComponent {
}
</code></pre></div></div>
<p>A standalone component can also define <code class="language-plaintext highlighter-rouge">schemas</code> if you want to ignore some custom elements in its template with <code class="language-plaintext highlighter-rouge">CUSTOM_ELEMENTS_SCHEMA</code> or even ignore all errors with <code class="language-plaintext highlighter-rouge">NO_ERRORS_SCHEMA</code>.</p>
<h2 id="usage-in-existing-applications">Usage in existing applications</h2>
<p>This is all great, but how can we use our new standalone <code class="language-plaintext highlighter-rouge">UserComponent</code>
in an existing application that has no standalone components?</p>
<p>Maybe you guessed it:
you can import a standalone component like <code class="language-plaintext highlighter-rouge">UserComponent</code>
in the <code class="language-plaintext highlighter-rouge">imports</code> of an <code class="language-plaintext highlighter-rouge">NgModule</code>!</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HttpClientModule, UserComponent], // <---
bootstrap: [AppComponent]
})
export class AppModule {}
</code></pre></div></div>
<p>This is probably a sound strategy to start using
standalone components, pipes, and directives in existing applications.
Angular applications tend to have a <code class="language-plaintext highlighter-rouge">SharedModule</code> with commonly used
components, directives, and pipes.
You can take these and convert them to a standalone version.
It’s usually straightforward, as they have few dependencies.
And then, instead of importing the full <code class="language-plaintext highlighter-rouge">SharedModule</code> in
every <code class="language-plaintext highlighter-rouge">NgModule</code>, you can import just what you need!</p>
<h2 id="cli-support">CLI support</h2>
<p>The Angular CLI team added a new flag <code class="language-plaintext highlighter-rouge">--standalone</code> to <code class="language-plaintext highlighter-rouge">ng generate</code> in v14,
allowing to create standalone versions of components, pipes, and directives:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng g component --standalone user
</code></pre></div></div>
<p>The component skeleton then has the <code class="language-plaintext highlighter-rouge">standalone: true</code> option,
and the <code class="language-plaintext highlighter-rouge">imports</code> are already populated with <code class="language-plaintext highlighter-rouge">CommonModule</code>
(that will be used in pretty much all components anyway):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component({
selector: 'pr-user',
standalone: true,
imports: [CommonModule],
templateUrl: './user.component.html',
styleUrls: ['./user.component.css']
})
export class UserComponent implements OnInit {
</code></pre></div></div>
<p>The generated test is also slightly different.
A standalone component is declared in the <code class="language-plaintext highlighter-rouge">imports</code> option
of <code class="language-plaintext highlighter-rouge">TestBed.configureTestingModule()</code> instead of in the <code class="language-plaintext highlighter-rouge">declarations</code> option.</p>
<p>If you want to generate all components with the <code class="language-plaintext highlighter-rouge">--standalone</code> flag,
you can set the option directly in <code class="language-plaintext highlighter-rouge">angular.json</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"schematics": {
"@schematics/angular:component": {
"standalone": true
}
}
</code></pre></div></div>
<p>You can of course do the same for the <code class="language-plaintext highlighter-rouge">directive</code> and <code class="language-plaintext highlighter-rouge">pipe</code> schematics.</p>
<h2 id="application-bootstrap">Application bootstrap</h2>
<p>If you want to, you can go one step further and write an application
with only standalone entities, and get rid of all <code class="language-plaintext highlighter-rouge">NgModule</code>s.
In that case, we need to figure out a few details.</p>
<p>First, if we don’t have an Angular module, how can we start the application?
A typical <code class="language-plaintext highlighter-rouge">main.ts</code> contains a call to <code class="language-plaintext highlighter-rouge">platformBrowserDynamic().bootstrapModule(AppModule)</code> which bootstraps the main Angular module of the application.</p>
<p>In a standalone world, we don’t want to use <code class="language-plaintext highlighter-rouge">NgModule</code>,
so we don’t have an <code class="language-plaintext highlighter-rouge">AppModule</code>.</p>
<p>Angular now offers a new function called <code class="language-plaintext highlighter-rouge">bootstrapApplication()</code> in <code class="language-plaintext highlighter-rouge">@angular/platform-browser</code>.
The function expects the root standalone component as a parameter:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bootstrapApplication(AppComponent);
</code></pre></div></div>
<p>This creates an application and starts it.</p>
<p>For SSR, you can use the new <code class="language-plaintext highlighter-rouge">renderApplication</code> function,
which renders the application as a string:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const output: string = await renderApplication(AppComponent, { appId: 'app' });
</code></pre></div></div>
<h2 id="optional-ngmodules">Optional NgModules</h2>
<p><code class="language-plaintext highlighter-rouge">NgModule</code> is a weird concept in Angular if you think about it.
They fulfill several roles at once.
We use them to declare what is usable in the templates of the components,
but also to configure the available providers.
We can export entities, to make them available in other modules.
Modules are eagerly executed,
which means you can add code in their constructors if you want to run something on their initialization.
They are also necessary if you want to lazy-load parts of your application.</p>
<p>If modules are now optional, how can we do all these tasks?</p>
<h2 id="providers">Providers</h2>
<p><code class="language-plaintext highlighter-rouge">NgModule</code>s allow defining providers available for components in the module.
For example, if you want to use <code class="language-plaintext highlighter-rouge">HttpClient</code>,
you add <code class="language-plaintext highlighter-rouge">HttpClientModule</code> to the <code class="language-plaintext highlighter-rouge">imports</code> of your main module.</p>
<p>In an application with no module, you can achieve the same by
using the second parameters of <code class="language-plaintext highlighter-rouge">bootstrapApplication()</code>,
which allows declaring <code class="language-plaintext highlighter-rouge">providers</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bootstrapApplication(AppComponent, { providers: [] });
</code></pre></div></div>
<p>In the long run,
Angular will probably offer a function returning the HTTP providers.
For now, to bridge the gap with modules that expose providers,
we can use <code class="language-plaintext highlighter-rouge">importProvidersFrom(module)</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bootstrapApplication(AppComponent, {
providers: [importProvidersFrom(HttpClientModule)]
});
</code></pre></div></div>
<p>You can also use <code class="language-plaintext highlighter-rouge">importProvidersFrom</code> to configure the router:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bootstrapApplication(AppComponent, {
providers: [importProvidersFrom(RouterModule.forRoot([/*...*/]))]
});
</code></pre></div></div>
<p>Note that the <code class="language-plaintext highlighter-rouge">BrowserModule</code> providers are automatically included
when starting an application with <code class="language-plaintext highlighter-rouge">bootstrapApplication()</code>.</p>
<p>It’s also worth noting that you <em>can’t</em> use <code class="language-plaintext highlighter-rouge">importProvidersFrom</code>
in component providers: it’s only usable in <code class="language-plaintext highlighter-rouge">bootstrapApplication()</code>.
<code class="language-plaintext highlighter-rouge">bootstrapApplication()</code> is now responsible for the Dependency Injection work,
and that’s where providers must be declared.</p>
<p>Note: since Angular v15, it’s now possible to use <code class="language-plaintext highlighter-rouge">provideRouter()</code>
and <code class="language-plaintext highlighter-rouge">provideHttpClient()</code> (see our blog post about <a href="/2022/11/09/angular-http-in-standalone-applications/">Angular HTTP in a standalone application</a>).</p>
<h2 id="lazy-loading-routes">Lazy loading routes</h2>
<p>The lazy-loading story in Angular has always revolved around <code class="language-plaintext highlighter-rouge">NgModule</code>.
Let’s say you wanted to lazy-load an <code class="language-plaintext highlighter-rouge">AdminComponent</code>.
You had to write an <code class="language-plaintext highlighter-rouge">NgModule</code> like the following:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@NgModule({
declarations: [AdminComponent],
imports: [
CommonModule,
RouterModule.forChild([{ path: '', component: AdminComponent }])
],
})
export class AdminModule {}
</code></pre></div></div>
<p>and then load the module with the router function <code class="language-plaintext highlighter-rouge">loadChildren</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
}
</code></pre></div></div>
<p>You can now get rid of <code class="language-plaintext highlighter-rouge">AdminModule</code> if <code class="language-plaintext highlighter-rouge">AdminComponent</code> is standalone,
and directly lazy-load the component with <code class="language-plaintext highlighter-rouge">loadComponent</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
path: 'admin',
loadComponent: () => import('./admin/admin.component').then(m => m.AdminComponent)
}
</code></pre></div></div>
<p>This is a really nice addition!
All the lazy-loaded components must be standalone of course.
It’s worth noting that this feature exists in all other mainstream frameworks,
and Angular was lacking a bit on this.</p>
<p>We can also lazy-load several routes at once,
by directly loading the routes config with <code class="language-plaintext highlighter-rouge">loadChildren</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
path: 'admin',
loadChildren: () => import('./admin/admin.routes').then(c => c.adminRoutes)
}
</code></pre></div></div>
<p>We now have a nice symmetry between <code class="language-plaintext highlighter-rouge">children</code>/<code class="language-plaintext highlighter-rouge">loadChildren</code>
and <code class="language-plaintext highlighter-rouge">component</code>/<code class="language-plaintext highlighter-rouge">loadComponent</code>!</p>
<p>But <code class="language-plaintext highlighter-rouge">NgModule</code>s also allow to define providers for a lazy-loaded module:
the providers are then only available in the components
of the lazy-loaded module.
To achieve the same thing, you can now declare providers directly on a route,
and the providers will be available only for this route and its children:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
path: 'admin',
providers: [AdminService],
loadComponent: () => import('./admin/admin.component').then(c => c.AdminComponent)
}
</code></pre></div></div>
<p>This works with all types of routes
(with <code class="language-plaintext highlighter-rouge">component</code>, <code class="language-plaintext highlighter-rouge">loadComponent</code>, <code class="language-plaintext highlighter-rouge">children</code>, <code class="language-plaintext highlighter-rouge">loadChildren</code> with routes or <code class="language-plaintext highlighter-rouge">NgModule</code>).
In my example above, the component is lazy-loaded, but the service is not.
If you want to lazy-load the service as well, you can use:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
path: 'admin',
loadChildren: () => import('./admin/admin.routes').then(c => c.adminRoutes)
}
</code></pre></div></div>
<p>and define the <code class="language-plaintext highlighter-rouge">providers</code> in <code class="language-plaintext highlighter-rouge">adminRoutes</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export const adminRoutes: Routes = [
{
path: '',
pathMatch: 'prefix',
providers: [AdminService], // <--
children: [
{ path: '', component: AdminComponent }
]
}
];
</code></pre></div></div>
<h2 id="initialization">Initialization</h2>
<p>An <code class="language-plaintext highlighter-rouge">NgModule</code> can also be used to run some initialization logic,
as they are eagerly executed:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@NgModule({ /*...*/ })
export class AppModule {
constructor(currentUserService: CurrentUserService) {
currentUserService.init();
}
}
</code></pre></div></div>
<p>To achieve the same without a module,
we can now use a new multi-token <code class="language-plaintext highlighter-rouge">ENVIRONMENT_INITIALIZER</code>.
All the code registered with this token will be executed
during the application initialization.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bootstrapApplication(AppComponent, {
providers: [
{
provide: ENVIRONMENT_INITIALIZER,
multi: true,
useValue: () => inject(CurrentUserService).init()
}
]
});
</code></pre></div></div>
<p>Note that <code class="language-plaintext highlighter-rouge">importProvidersFrom(SomeModule)</code> is smart enough to automatically
register the initialization logic of <code class="language-plaintext highlighter-rouge">SomeModule</code> in <code class="language-plaintext highlighter-rouge">ENVIRONMENT_INITIALIZER</code>.</p>
<h2 id="angular-compiler-and-vite">Angular compiler and Vite</h2>
<p>On a low level, <code class="language-plaintext highlighter-rouge">NgModule</code>s are the smallest unit
that the compiler can re-compile when running <code class="language-plaintext highlighter-rouge">ng serve</code>.
Indeed, if you update the selector of a component for example,
then the Angular compiler has to check all the templates of the module that
contains this component to see if something changed,
and also all the modules that import that module.
Right now, the Angular compiler is tightly
coupled with the TypeScript compiler
and does a lot of bookkeeping to only recompile what’s necessary.
In an application with no <code class="language-plaintext highlighter-rouge">NgModule</code>s,
the compiler has a more straightforward task:
it will for example only recompile the components that directly import the modified component.</p>
<p>This can be good news for the future of Angular tooling.
The frontend world has been taken by storm by <a href="https://vitejs.dev/">Vite</a>.
We talked about Vite, and the differences with Webpack,
in <a href="https://blog.ninja-squad.com/2022/02/23/getting-started-with-vite-and-vue/">this blog post</a>.</p>
<p>TL;DR: Vite only re-compiles the files needed to display a page
and skips the TypeScript compilation to only do a simple transpilation,
often in parallel.</p>
<p>This works great for Vue, React, or Svelte, but not so great for Angular,
where a lot more needs to be recompiled, and where TypeScript is needed.
Standalone components are a nice step in this direction,
and may allow a future Angular CLI with Vite instead of Webpack
and way faster re-builds.</p>
<h2 id="caveats">Caveats</h2>
<p>To be honest, the standalone API feels great.
We migrated a few applications, and this is really nice to use,
and it feels good to get rid of <code class="language-plaintext highlighter-rouge">NgModule</code>s!</p>
<p>A few pain points though.</p>
<ol>
<li>
<p>There are no “global imports”: you need to import a component/pipe/directive
every time you use it.
<code class="language-plaintext highlighter-rouge">ngIf</code>, <code class="language-plaintext highlighter-rouge">ngFor</code>, and friends are available in every standalone component generated
by the CLI, as the skeleton includes the import of <code class="language-plaintext highlighter-rouge">CommonModule</code>.
But <code class="language-plaintext highlighter-rouge">routerLink</code> for example is not:
you need to import <code class="language-plaintext highlighter-rouge">RouterModule</code> if you need it.
Other frameworks, like Vue for example,
allow registering some components globally,
to avoid importing them over and over.
That’s not the case in Angular.</p>
</li>
<li>
<p>Sometimes you forget to add an import, and your template doesn’t work,
with no compilation error.
For example, adding a link with <code class="language-plaintext highlighter-rouge">[routerLink]="['/']</code> does not compile,
but <code class="language-plaintext highlighter-rouge">routerLink="/"</code> does compile (and doesn’t work).
I feel that these kinds of errors happen more often than they did
with <code class="language-plaintext highlighter-rouge">NgModule</code>.
IDEs will probably help us here, and I suppose typing <code class="language-plaintext highlighter-rouge">routerLink</code> in a template
will result in an automatic addition of <code class="language-plaintext highlighter-rouge">RouterModule</code> in the component’s imports
in VS Code/Webstorm/whatever is a few months.</p>
</li>
<li>
<p>You can’t bootstrap multiple components at once
with the new <code class="language-plaintext highlighter-rouge">bootstrapApplication()</code> function,
whereas it was possible with the NgModule-based bootstrap.</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">TestBed</code> works with standalone components,
but will probably include more specific APIs to simplify tests in the future.
Note that it is already easier to test standalone components than classic components,
as you don’t have to repeat in <code class="language-plaintext highlighter-rouge">configureTestingModule</code> all the dependencies the component needs.</p>
</li>
</ol>
<h2 id="summary">Summary</h2>
<p>Six years after the initial release,
we can finally get rid of <code class="language-plaintext highlighter-rouge">NgModule</code> in Angular if we want to.
Their addition to Angular was a bit rushed:
they were introduced in Angular v2.0.0-rc.5 two months (!!) before the stable release,
mainly to help the ecosystem build libraries.
As often in our field, the rushed design resulted in an entity that mixed several concerns,
with some concepts quite hard to understand for beginners.</p>
<p>The new “mental model” is easy to grasp:
providers are declared on an application level
and components just have to import what they need in their templates.
It will also probably be easier for newcomers to understand how Angular works.</p>
<p>These standalone APIs are trying to make things clearer,
and it looks like they did ♥️.</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
Strictly typed forms in Angular2022-04-21T00:00:00+00:00https://blog.ninja-squad.com/2022/04/21/strictly-typed-forms-angular<p>We finally have them!
6 years after the first release, and after months of
<a href="https://github.com/angular/angular/discussions/44513">discussion and feedback</a>,
the <a href="https://github.com/angular/angular/issues/13721">most up-voted issue</a>
in the Angular repository is now solved in Angular v14.</p>
<p>We now have forms correctly typed in Angular 🚀.</p>
<p><code class="language-plaintext highlighter-rouge">FormControl</code> now takes a generic type
indicating the type of the value it holds.
To make sure that nothing breaks in existing applications,
the Angular team released an automatic migration
in Angular v14.</p>
<p><em>Disclaimer:</em> this blog post is based on early releases of Angular v14,
and some details may change based on the feedback the Angular team gets.
That’s why, for once, we write a blog post on a feature before its final release:
this is a great opportunity to give it a try and gather feedback!</p>
<h2 id="migration-to-the-untyped-version">Migration to the untyped version</h2>
<p>When updating to Angular v14, a migration will automatically replace
all the form entities in your application by their untyped versions:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">FormControl</code> → <code class="language-plaintext highlighter-rouge">UntypedFormControl</code> (which is an alias for <code class="language-plaintext highlighter-rouge">FormControl<any></code>)</li>
<li><code class="language-plaintext highlighter-rouge">FormGroup</code> → <code class="language-plaintext highlighter-rouge">UntypedFormGroup</code> (which is an alias for <code class="language-plaintext highlighter-rouge">FormGroup<any></code>)</li>
<li><code class="language-plaintext highlighter-rouge">FormArray</code> → <code class="language-plaintext highlighter-rouge">UntypedFormArray</code> (which is an alias for <code class="language-plaintext highlighter-rouge">FormArray<any></code>)</li>
<li><code class="language-plaintext highlighter-rouge">FormBuilder</code> → <code class="language-plaintext highlighter-rouge">UntypedFormBuilder</code> (which is an alias for <code class="language-plaintext highlighter-rouge">FormBuilder<any></code>)</li>
</ul>
<p>This migration will run when launching:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng update @angular/core
</code></pre></div></div>
<p>Or on demand, if you already manually updated your application:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ng update @angular/core --migrate-only=migration-v14-typed-forms
</code></pre></div></div>
<p>At the end of the migration, all imports and instances are replaced
by their untyped versions.
And that can be a lot of files in large applications
(we had a few hundreds of files updated in some of our applications).</p>
<p>The cool thing is that now the application should work exactly as before.</p>
<h2 id="migration-to-the-typed-forms-api-step-by-step">Migration to the typed forms API, step by step</h2>
<p>The next step is to use the typed version of the API.
How do you do that?</p>
<p>Let’s take an example, a simple register form, and go through it step by step.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export class RegisterComponent {
registerForm: FormGroup;
constructor() {
this.registerForm = new FormGroup({
login: new FormControl(null, Validators.required),
passwordGroup: new FormGroup({
password: new FormControl('', Validators.required),
confirm: new FormControl('', Validators.required)
}),
rememberMe: new FormControl(false, Validators.required)
});
}
}
</code></pre></div></div>
<p>We have a login field, a subgroup, with a password field and a password confirmation field,
and a “remember me” field.</p>
<p>When using the automated migration, you end up with:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export class RegisterComponent {
registerForm: UntypedFormGroup;
constructor() {
this.registerForm = new UntypedFormGroup({
login: new UntypedFormControl(null, Validators.required),
passwordGroup: new UntypedFormGroup({
password: new UntypedFormControl('', Validators.required),
confirm: new UntypedFormControl('', Validators.required)
}),
rememberMe: new UntypedFormControl(false, Validators.required)
});
}
}
</code></pre></div></div>
<p>Our work is to remove all the <code class="language-plaintext highlighter-rouge">Untyped*</code> usage,
and properly type the form.
Let’s start with the code in the constructor as this is the most straightforward.</p>
<p>Each <code class="language-plaintext highlighter-rouge">UntypedFormControl</code> must be converted to <code class="language-plaintext highlighter-rouge">FormControl<T></code>,
with <code class="language-plaintext highlighter-rouge">T</code> the type of the value of the form control.
Most of the time, TypeScript can infer this information based on the initial value
given to the <code class="language-plaintext highlighter-rouge">FormControl</code>.</p>
<p>For example, <code class="language-plaintext highlighter-rouge">passwordGroup</code> can be converted easily:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>passwordGroup: new FormGroup({
password: new FormControl('', Validators.required), // inferred as `FormControl<string | null>`
confirm: new FormControl('', Validators.required) // inferred as `FormControl<string | null>`
}),
</code></pre></div></div>
<p>Note that the inferred type is <code class="language-plaintext highlighter-rouge">string | null</code> and not <code class="language-plaintext highlighter-rouge">string</code>.
This is because calling <code class="language-plaintext highlighter-rouge">.reset()</code> on a control without specifying a reset value,
resets the value to <code class="language-plaintext highlighter-rouge">null</code>.
This behavior is here since the beginning of Angular, so the inferred type reflects it.
We’ll come back to this possibly <code class="language-plaintext highlighter-rouge">null</code> value, in a dedicated section,
as it can be annoying (but can be worked around).</p>
<p>Sometimes though, TypeScript can’t infer the type of the control based on the initial value.
For example, our <code class="language-plaintext highlighter-rouge">login</code> field is initialized with <code class="language-plaintext highlighter-rouge">null</code>,
so TypeScript can’t know what type is intended here.
You can of course explicitly add it:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>login: new FormControl<string | null>(null, Validators.required),
</code></pre></div></div>
<p>Due to a <a href="https://github.com/microsoft/TypeScript/issues/48033">subtle TypeScript bug</a>,
you also have to help TS figure out that <code class="language-plaintext highlighter-rouge">false</code> is a boolean:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rememberMe: new FormControl<boolean | null>(false, Validators.required)
</code></pre></div></div>
<p>This will probably be fixed in the future, and the type inference will hopefully be enough.</p>
<p>Now let’s take the field <code class="language-plaintext highlighter-rouge">registerForm</code>.
Unlike <code class="language-plaintext highlighter-rouge">FormControl</code>, the generic type expected by <code class="language-plaintext highlighter-rouge">FormGroup</code>
is not the type of its value, but a description of its structure, in terms of form controls:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>registerForm: FormGroup<{
login: FormControl<string | null>;
passwordGroup: FormGroup<{
password: FormControl<string | null>;
confirm: FormControl<string | null>;
}>;
rememberMe: FormControl<boolean | null>;
}>;
constructor() {
this.registerForm = new FormGroup({
login: new FormControl<string | null>(null, Validators.required),
passwordGroup: new FormGroup({
password: new FormControl('', Validators.required),
confirm: new FormControl('', Validators.required)
}),
rememberMe: new FormControl<boolean | null>(false, Validators.required)
});
}
</code></pre></div></div>
<p>This is a bit verbose, but it works \o/.
It is possible to let TypeScript infer the type of <code class="language-plaintext highlighter-rouge">registerForm</code> if,
instead of initializing the field in the constructor like we usually do,
we initialize it directly when we declare it:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>registerForm = new FormGroup({
login: new FormControl<string | null>(null, Validators.required),
passwordGroup: new FormGroup({
password: new FormControl('', Validators.required),
confirm: new FormControl('', Validators.required)
}),
rememberMe: new FormControl<boolean | null>(false, Validators.required)
});
</code></pre></div></div>
<p>In this example, TypeScript properly infers the type of the form group,
without a lot of work on our part.</p>
<p>This is also possible if you use the <code class="language-plaintext highlighter-rouge">FormBuilder</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>registerForm = this.fb.group({
login: [null as string | null, Validators.required],
passwordGroup: {
password: ['', Validators.required],
confirm: ['', Validators.required]
},
rememberMe: [false, Validators.required]
});
constructor(private fb: FormBuilder) {}
</code></pre></div></div>
<h2 id="nullability">Nullability</h2>
<p>As explained above, the types of the controls are <code class="language-plaintext highlighter-rouge">string | null</code> and <code class="language-plaintext highlighter-rouge">boolean | null</code>,
and not <code class="language-plaintext highlighter-rouge">string</code> and <code class="language-plaintext highlighter-rouge">boolean</code> like we could expect,
because calling <code class="language-plaintext highlighter-rouge">.reset()</code> on a field resets its value to null.
Except if you give a value to reset, for example <code class="language-plaintext highlighter-rouge">.reset('')</code>,
but as TypeScript doesn’t know <em>if</em> and <em>how</em> you are going to call <code class="language-plaintext highlighter-rouge">.reset()</code>,
the inferred type is nullable.</p>
<p>You can tweak this behavior if you use the option <code class="language-plaintext highlighter-rouge">nonNullable</code>
(which replaces the new option introduced in Angular v13.2 <code class="language-plaintext highlighter-rouge">initialValueIsDefault</code>, see <a href="/2022/01/27/what-is-new-angular-13.2">our blog post</a> for more details).
With this option, you get rid of the null value if you want to!
On one hand, this is very handy if your application uses <code class="language-plaintext highlighter-rouge">strictNullChecks</code>.
But on the other hand, this is quite verbose, as you currently have to set this option
<em>on every field</em> (this might change in the future):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>registerForm = new FormGroup({
login: new FormControl<string>('', { validators: Validators.required, nonNullable: true }),
passwordGroup: new FormGroup({
password: new FormControl('', { validators: Validators.required, nonNullable: true }),
confirm: new FormControl('', { validators: Validators.required, nonNullable: true })
}),
rememberMe: new FormControl<boolean>(false, { validators: Validators.required, nonNullable: true })
}); // incredibly verbose version, that yields non-nullable types
</code></pre></div></div>
<p>Or you can use <code class="language-plaintext highlighter-rouge">NonNullableFormBuilder</code>.</p>
<h2 id="nonnullableformbuilder">NonNullableFormBuilder</h2>
<p>Angular v14 introduces a new property on <code class="language-plaintext highlighter-rouge">FormBuilder</code>, called <code class="language-plaintext highlighter-rouge">nonNullable</code>,
that returns a <code class="language-plaintext highlighter-rouge">NonNullableFormBuilder</code>.
This new builder offers the usual <code class="language-plaintext highlighter-rouge">control</code>, <code class="language-plaintext highlighter-rouge">group</code> and <code class="language-plaintext highlighter-rouge">array</code> methods
to build non-nullable controls:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>registerForm = this.fb.nonNullable.group({
login: ['', Validators.required]
});
// `registerForm.value` type is `{ login?: string }`
constructor(private fb: FormBuilder) {}
</code></pre></div></div>
<p>As using <code class="language-plaintext highlighter-rouge">fb.nonNullable</code> every time is a bit verbose,
you can directly inject <code class="language-plaintext highlighter-rouge">NonNullableFormBuilder</code> instead of <code class="language-plaintext highlighter-rouge">FormBuilder</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>registerForm = this.fb.group({
login: ['', Validators.required]
});
constructor(private fb: NonNullableFormBuilder) {}
</code></pre></div></div>
<h2 id="what-do-we-gain">What do we gain?</h2>
<h3 id="value-and-valuechanges">value and valueChanges</h3>
<p>Is this migration trouble worth it?
In my opinion, definitely.
The original forms API is not playing very well with TypeScript.
For example, the <code class="language-plaintext highlighter-rouge">value</code> of a control or group is typed as <code class="language-plaintext highlighter-rouge">any</code>.
So we could write <code class="language-plaintext highlighter-rouge">this.registerForm.value.whatever</code>
and the application would happily compile.
This can be a very painful issue when refactoring an application:
TypeScript would warn you about every mistake in TS and HTML files…
except in forms!</p>
<p>This is no longer the case:
the new forms API properly types <code class="language-plaintext highlighter-rouge">value</code> according to the types of the form controls.
In my example above (with <code class="language-plaintext highlighter-rouge">nonNullable</code>), the type of <code class="language-plaintext highlighter-rouge">this.registerForm.value</code> is:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
login?: string;
passwordGroup?: {
password?: string;
confirm?: string;
};
rememberMe?: boolean;
} // this.registerForm.value
</code></pre></div></div>
<p>You can spot some <code class="language-plaintext highlighter-rouge">?</code> in the type of the form value.
What does it mean?</p>
<p>In Angular, you can disable any part of a form.
When you disable a field, its value is removed from the form value:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.registerForm.get('passwordGroup').disable();
console.log(this.registerForm.value); // logs '{ login: null, rememberMe: false }'
</code></pre></div></div>
<p>This is a bit strange, but it explains why the fields are all marked as optional:
if they have been disabled, they are not present in the object returned by <code class="language-plaintext highlighter-rouge">this.registerForm.value</code>.
This is what TypeScript calls a <code class="language-plaintext highlighter-rouge">Partial</code> value.</p>
<p>If you want the complete object, with all its keys, even the disabled ones,
you can use <code class="language-plaintext highlighter-rouge">this.registerForm.getRawValue()</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
login: string;
passwordGroup: {
password: string;
confirm: string;
};
rememberMe: boolean;
} // this.registerForm.getRawValue()
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">this.registerForm.value</code> is probably more accurate, but it forces developers to add potentially <code class="language-plaintext highlighter-rouge">undefined</code>
when you <em>know</em> the value is present because the field is never disabled.
For example, imagine that this value is used as parameters for calling the method of a service:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export class UserService {
register(login: string, password: string): Observable<void> {
// ...
}
}
</code></pre></div></div>
<p>then when calling this method in our component above, we have an error:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const value = this.registerForm.value;
this.userService.register(value.login, value.passwordGroup.password).subscribe();
// does not compile as the `login` and `password` parameters must be strings
// and `value.login`, `value.passwordGroup`, `value.passwordGroup.password`
// can all theoretically be undefined
</code></pre></div></div>
<p>As the values can be undefined, and the <code class="language-plaintext highlighter-rouge">register</code> method expects strings,
and not potentially undefined values, TypeScript is not happy.</p>
<p>We can handle this case by checking if the values exist
(which also lets TypeScript know that they are not undefined):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const value = this.registerForm.value;
if (value.login && value.passwordGroup && value.passwordGroup.password) {
// TypeScript narrows the types to `string` inside the `if` block
this.userService.register(value.login, value.passwordGroup.password).subscribe();
}
</code></pre></div></div>
<p>But this is sometimes a bit annoying, as we know these values are present:
we never disabled these fields!</p>
<p>In that case, you can use the lazy, but always efficient, “non-null assertion” operator <code class="language-plaintext highlighter-rouge">!</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const value = this.registerForm.value;
this.userService.register(value.login!, value.passwordGroup!.password!).subscribe();
// not pretty, but gets the job done
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">valueChanges</code> is of course properly typed as well:
instead of getting an <code class="language-plaintext highlighter-rouge">Observable<any></code> as we used to,
you now get <code class="language-plaintext highlighter-rouge">Observable<string | null></code> for <code class="language-plaintext highlighter-rouge">this.registerForm.get('login')</code>.</p>
<p><code class="language-plaintext highlighter-rouge">setValue</code> and <code class="language-plaintext highlighter-rouge">patchValue</code> are also type-safe:
you can’t set a number on a <code class="language-plaintext highlighter-rouge">FormControl<string></code> for example.</p>
<h3 id="get">get()</h3>
<p>The <code class="language-plaintext highlighter-rouge">get(key)</code> method is also more strictly typed.
This is great news, as you could previously call it with a key that did not exist,
and the compiler would not see the issue.</p>
<p>Thanks to some hardcore TypeScript magic, the key is now checked and the returned control
is properly typed!</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.registerForm.get('login') // AbstractControl<string> | null
this.registerForm.get('passwordGroup.password') // AbstractControl<string> | null 😲
</code></pre></div></div>
<p>It also works with the array syntax for the key, if you add <code class="language-plaintext highlighter-rouge">as const</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.registerForm.get(['passwordGroup', '.password'] as const) // AbstractControl<string> | null
</code></pre></div></div>
<p>And it even works with nested form arrays and groups!
For example, if our form has a <code class="language-plaintext highlighter-rouge">hobbies</code> FormArray, containing a FormGroup:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.registerForm.get('hobbies.0.name') // AbstractControl<string> | null 🤯
</code></pre></div></div>
<p>If you use a key that does not exist in your form, you get an error:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.registerForm.get('logon' /* typo */)!.setValue('cedric'); // does not compile 🚀
</code></pre></div></div>
<p>As you can see, <code class="language-plaintext highlighter-rouge">get()</code> returns a potentially <code class="language-plaintext highlighter-rouge">null</code> value:
this is because you have no guarantee that the control exists at runtime,
so you have to check its existence or use <code class="language-plaintext highlighter-rouge">!</code> like above.</p>
<p>Note that the keys you use in your templates for <code class="language-plaintext highlighter-rouge">formControlName</code>, <code class="language-plaintext highlighter-rouge">formGroupName</code>, and <code class="language-plaintext highlighter-rouge">formArrayName</code> aren’t checked, so you can still have undetected issues in your templates.</p>
<h2 id="a-newcomer-formrecord">A newcomer: FormRecord</h2>
<p><code class="language-plaintext highlighter-rouge">FormRecord</code> is a new form entity that has been added to the API.
A <code class="language-plaintext highlighter-rouge">FormRecord</code> is similar to a <code class="language-plaintext highlighter-rouge">FormGroup</code> but the controls must all be of the same type.
This can help if you use a <code class="language-plaintext highlighter-rouge">FormGroup</code> as a map,
to which you add and remove controls dynamically.
In that case, properly typing the <code class="language-plaintext highlighter-rouge">FormGroup</code> is not really easy,
and that’s where <code class="language-plaintext highlighter-rouge">FormRecord</code> can help.</p>
<p>It can be handy when you want to represent a list of checkboxes for example,
where your user can add or remove options.
For example, our users can add and remove the language they understand (or don’t understand) when they register:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>languages: new FormRecord({
english: new FormControl(true, { nonNullable: true }),
french: new FormControl(false, { nonNullable: true })
});
// later
this.registerForm.get('languages').addControl('spanish', new FormControl(false, { nonNullable: true }));
</code></pre></div></div>
<p>If you try to add a control of a different type, TS throws a compilation error:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.registerForm.get('languages').addControl('spanish', new FormControl(0, { nonNullable: true })); // does not compile
</code></pre></div></div>
<p>But as the keys can be any string, there is no type-checking on the key in <code class="language-plaintext highlighter-rouge">removeControl(key)</code> or <code class="language-plaintext highlighter-rouge">setControl(key)</code>.
Whereas if you use a <code class="language-plaintext highlighter-rouge">FormGroup</code>, with well-defined keys, you <em>do</em> have type checking
on these methods: <code class="language-plaintext highlighter-rouge">setControl</code> only allows a known key,
and <code class="language-plaintext highlighter-rouge">removeControl</code> only allows a key marked as optional (with a <code class="language-plaintext highlighter-rouge">?</code> in its type definition).</p>
<p>TL;DR: If you have a <code class="language-plaintext highlighter-rouge">FormGroup</code> on which you want to add and remove control dynamically,
you’re probably looking for the new <code class="language-plaintext highlighter-rouge">FormRecord</code> type.</p>
<h2 id="conclusion">Conclusion</h2>
<p>We’re very excited to see this new forms API landing in Angular!
This is, by far, one of the biggest changes in recent years for developers.
Ivy was big but didn’t need us to make a lot of changes in our applications.
Typed forms are another story: the migration is likely to impact dozens,
hundreds, or thousands of files in your applications!
In our applications, for the forms we migrated to the typed version,
most of the work was very straightforward and repetitive.
And we even caught some hidden bugs in our code!
The TypeScript support in Angular has always been outstanding,
but had a major blind spot with forms: this is no longer the case!</p>
<p>Big thanks to <a href="https://twitter.com/dylhunn">Dylan Hunn</a> who has been in charge of this work,
and who very patiently listened to our very early feedback!</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>
What's new in Angular 13.3?2022-03-16T00:00:00+00:00https://blog.ninja-squad.com/2022/03/16/what-is-new-angular-13.3<p>Angular 13.3.0 is here!</p>
<p style="text-align: center;">
<a href="https://github.com/angular/angular/releases/tag/13.3.0">
<img class="rounded img-fluid" style="max-width: 100%" src="/assets/images/angular.png" alt="Angular logo" />
</a>
</p>
<p>This is a minor release, mainly to support TS v4.6.
But it also comes with a few improvements that are worth exploring: let’s dive in!</p>
<h2 id="typescript-46">TypeScript 4.6</h2>
<p>The reason of this v13.3 release is the support of TypeScript v4.6.
TS v4.6 was released in February, and the Angular team did not wanted to wait for the v14 release to support it (scheduled in May).</p>
<p>If you want to use the new <code class="language-plaintext highlighter-rouge">es2022</code> target, the improved control flow analysis or
the possibility to call code before <code class="language-plaintext highlighter-rouge">super()</code> in constructors, you now can.
Here are <a href="https://devblogs.microsoft.com/typescript/announcing-typescript-4-6/">the full release notes</a> if you want to learn more about the new TypeScript version.</p>
<h2 id="bundle-size">Bundle size</h2>
<p>The team refactored the codebase to make some errors, providers
and tokens tree-shakable and removed a few useless classes (now that IE is no longer supported).
This should lead to slightly lighter bundles.</p>
<p>The code generated by the compiler has also been improved to allow chaining some instructions.
For example, consider a template like <code class="language-plaintext highlighter-rouge"><div><div><a>Hello</a></div></div></code>.</p>
<p>Angular v13.2 generated:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// simplified compiled template in ng v13.2
elementStart(0, "div");
elementStart(1, "div");
element(2, "a");
elementEnd();
elementEnd();
</code></pre></div></div>
<p>Now, Angular v13.3 generates:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// simplified compiled template in ng v13.3
elementStart(0, "div")(1, "div")
element(2, "a");
elementEnd()();
</code></pre></div></div>
<p>This can make a little difference depending on your templates,
but not <em>that</em> much, as the code that was previously generated was compressing very well.</p>
<h2 id="forms">Forms</h2>
<p>It is listed as a fix rather than a new feature, but I think this is noteworthy if you’re using <code class="language-plaintext highlighter-rouge">ngModel</code> in forms: you can use a non-null assertion in the “banana-in-box” syntax:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><input [(ngModel)]="user.name!" />
<!-- same as -->
<input [ngModel]="user.name!" (ngModelChange)="user.name = $event">
</code></pre></div></div>
<p>This of course works for the “banana-in-box” syntax in general,
not only with <code class="language-plaintext highlighter-rouge">ngModel</code>.</p>
<p>That’s all for this release, stay tuned!</p>
<p>All our materials (<a href="https://books.ninja-squad.com/angular">ebook</a>, <a href="https://angular-exercises.ninja-squad.com/">online training</a> and <a href="https://ninja-squad.com/training/angular">training</a>) are up-to-date with these changes if you want to learn more!</p>