Skip to content

Zone.js deep diving - Execution Context

Zone.js deep diving

Chapter 1: Execution Context

As an Angular developer, you may know NgZone, which is a service for executing work inside, or outside of the angular Zone. You may also know that this NgZone service is based on a library called zone.js, but most developers may not directly use the APIs of zone.js, or know what zone.js is, so I would like to use several articles to explain zone.js to you.

My name is Jia Li. I am a senior software engineer at This Dot Labs, and I have contributed to zone.js for more than 3 years. Now, I am the code owner of angular/zone.js package (zone.js had been merged into angular monorepo), and I am also an Angular collaborator.

What is Zone.js

Zone.js is a library created by Brian Ford in 2010 and is inspired by Dart. It provides a concept called Zone, which is an execution context that persists across async tasks.

A Zone can:

  1. Provide execution context that persist across async tasks.
  2. Intercept async task, and provide life cycle hooks.
  3. Provide centralized error handler for async tasks.

We will discuss those topics one by one.

Execution Context

So what is Execution Context? This is a fundamental term in Javascript. Execution Context is an abstract concept that holds information about the environment within the current code being executed. The previous sentence may be a little difficult to understand without context, so let's use some code samples to explain it. For better understanding of Execution Context/Scope, please refer to this great book from getify

  1. Global Context
const globalThis = this;
let a = 0;

function testFunc() {
  let b = 0;
  console.log('this in testFunc is:', this === globalThis);
}

testFunc();

So in this first example, we have a global execution context, which will be created before any code is created. It needs to know it's scope, which means the execution context needs to know which variables and functions it can access. In this example, the global execution context can access variable a. Then, after the scope is determined, the Javascript engine will also determine the value of this. If we run this code in Browser, the globalThis will be window, and it will be global in NodeJS.

Then, we execute testFunc. When we are going to run a new function, a new execution context will be created, and again, it will try to decide the scope and the value of this. In this example, the this in the function testFunc will be the same with globalThis, because we are running the testFunc without assigning any context object by using apply/call. And the scope in testFunc will be able to access both a and b.

This is very simple. Let's just see another example to recall the Javascript 101.

const testObj = {
  testFunc: function() {
    console.log('this in testFunc is:', this);
  }
};

// 1. call testFunc with testObj
testObj.testFunc();

const newTestFunc = testObj.testFunc;
// 2. call newTestFunc who is referencing from testObj.testFunc
newTestFunc();

const newObj = {};
// 3. call newTestFunc with apply
newTestFunc.apply(newObj);

const bindObj = {};
const boundFunc = testObj.testFunc.bind(bindObj);
// 4. call bounded testFunc
boundFunc();
boundFunc.apply(somethingElse);

Here, testFunc is a property of testObj. We call testFunc in several ways. We will not go very deeper about how it works. We just list the results here. Again, please check getify for more details.

  1. call testObj.testFunc, this will be testObj.
  2. create a reference newTestFunc, this will be globalThis.
  3. call with apply, this will be newObj.
  4. call bounded version, this will always be bindObj.

So we can see that this will change depending on how we call this function. This is a very fundamental mechanism in Javascript.

So, back to Execution Context in Zone. What is the difference? Let's see the code sample here:

const zoneA = // create a new zone ...;
zoneA.run(function() {
  // function is in the zone
  // just like `this`, we have a zoneThis === zoneA
  expect(zoneThis).toBe(zoneA);
  setTimeout(function() {
    // the callback of async operation
    // will also have a zoneThis === zoneA
    // which is the zoneContext when this async operation
    // is scheduled.
    expect(zoneThis).toBe(zoneA);
  });
  Promise.resolve(1).then(function() {
    // all async operations will be in the same zone
    // when they are scheduled.
    expect(zoneThis).toBe(zoneA);
  });
});

So, in this example, we created a zone (we will talk about how to create a zone in the next chapter). As suggested by the term zone, when we run a function inside the zone, suddenly we have a new execution context provided by zone. Let's call it zoneThis for now. Unlike this, the value of zoneThis will always equal the zone, where the functions is being executed in no matter if it is a sync or an async operation.

You can also see, in the callback of setTimeout, that the zoneThis will be the same value when setTimeout is scheduled. So this is another principle of Zone. The zone execution context will be kept as the same value as it is scheduled.

So you may also wonder how to get zoneThis. Of course, we are not inventing a new Javascript keyword zoneThis, so to get this zone context, we need to use a static method, introduced by Zone.js, which is Zone.current.

const zoneA = // create a new zone ...;
zoneA.run(function() {
  // function is in the zone
  // just like `this`, we have a Zone.current === zoneA
  expect(Zone.current).toBe(zoneA);
  setTimeout(function() {
    // the callback of async operation
    // will also have a Zone.current === zoneA
    // which is the zoneContext when this async operation
    // is scheduled.
    expect(Zone.current).toBe(zoneA);
  });

Because there is a Zone execution context we can share inside a zone, we can also share some data.

const zoneA = Zone.current.fork({
  name: 'zone',
  properties: {key: 'sharedData'}
});
zoneA.run(function() {
  // function is in the zone
  // we can use data from zoneA
  expect(Zone.current.get('key')).toBe('sharedData');
  setTimeout(function() {
    // the callback of async operation
    // we can use data from zoneA
    expect(Zone.current.get('key')).toBe('sharedData');
  });

Execution Context is the fundamental feature of Zone.js. Based on this feature, we can monitor/track/intercept the lifecycle of async operations. We will talk about those hooks in the next chapter.

This Dot Labs is a development consultancy that is trusted by top industry companies, including Stripe, Xero, Wikimedia, Docusign, and Twilio. This Dot takes a hands-on approach by providing tailored development strategies to help you approach your most pressing challenges with clarity and confidence. Whether it's bridging the gap between business and technology or modernizing legacy systems, you’ll find a breadth of experience and knowledge you need. Check out how This Dot Labs can empower your tech journey.

You might also like

You Don't Need NgRx To Write a Good Angular App cover image

You Don't Need NgRx To Write a Good Angular App

NgRx is a great tool that allows you to manage state and side effects in Angular applications in a Redux-like manner. It streamlines state changes with its unidirectional data flow, and offers a structured approach to handling data and side effects. Numerous posts on our blog detail its strengths and affiliated techniques. Some Angular developers even argue that incorporating NgRx is imperative once an app expands beyond two features. While NgRx can undoubtedly enhance an Angular application or library by simplifying debugging, translating business logic into code, and improving the architecture, it does present a steep learning curve. Despite the provocative title, there is some truth to the statement: your app or library may indeed not need NgRx. Surprisingly, I successfully developed a suite of enterprise Angular libraries over five years without involving NgRx. In that project, we decided to opt out of using a state management library like NgRx because of its steep learning curve. Developers with varying levels of Angular expertise were involved, and the goal was to simplify their experience. My bold assertion is that, with careful consideration of architectural patterns, it is entirely possible to develop a robust app or library using only Angular, without any third-party libraries. Employing select design patterns and leveraging Angular's built-in tools can yield a highly maintainable app, even without a dedicated state management library. Having shared my somewhat audacious opinion, let me now support it by outlining a few patterns that facilitate the development of a maintainable, stateful Angular application or library without NgRx. Services and the Singleton Pattern Services provided in root` or a module yield a shared instance across the entire app or module, effectively rendering them singletons. This characteristic makes them ideal for managing and sharing state across components without requiring a dedicated state management tool like NgRx. Particularly, for small to medium-sized applications, a "state service" can be a straightforward and effective alternative to a comprehensive state management solution when implemented correctly. To accurately implement state in a singleton service, consider the following: - Restrict state data to private properties and expose them only through public methods or observables to prevent external mutations. Such a pattern safeguards the integrity of your state by averting unauthorized modifications. - Utilize BehaviorSubjects or signals to enable components to respond to state changes. Both BehaviorSubject` and `SettableSignal` retain the current value and emit it to new subscribers immediately. Components can then subscribe to these to receive the current value and any subsequent updates. - Expose public methods in your service that manage state modifications to centralize the logic for updating the state and incorporate validation, logging, or other necessary side effects. - When modifying state, always return a new instance of the data rather than altering the original data. This ensures that references are broken and components that rely on change detection can accurately detect changes. Good Component Architecture Distinguish your UI components into stateful (containers) and stateless (presentational) components. Stateful components manage data and logic, while stateless components merely receive data via inputs and emit events without maintaining an internal state. Do not get dragged into the rabbit hole of anti-patterns such as input drilling or event bubbling while trying to make as many components presentational as possible. Instead, use a Data Service Layer to provide a clean abstraction over backend API calls and handle error management, data transformation, caching, and even state management where it makes sense. Although injecting a service into a component technically categorizes it as a "smart" component, segregating the data access logic into a separate service layer ultimately enhances modularity, maintainability, scalability, and testability. Immutability A best practice is to always treat your state as immutable. Instead of modifying an object or an array directly, you should create a new copy with the changes. Adhering to immutability ensures predictability and can help in tracking changes. Applying the ChangeDetectionStrategy.OnPush strategy to components whenever possible is also a good idea as it not only optimizes performance since Angular only evaluates the component for changes when its inputs change or when a bound event is triggered, but it also enforces immutability. Change detection is only activated when a different object instance is passed to the input. Leveraging Angular Router Angular's router is a powerful tool for managing application state. It enables navigation between different parts of an application, allowing parameters to be passed along, effectively using the URL as a single source of truth for your application state, which makes the application more predictable, bookmarkable, and capable of maintaining state across reloads. Moreover, components can subscribe to URL changes and react accordingly. You can also employ router resolvers to fetch data before navigating to a route, ensuring that the necessary state is loaded before the route is activated. However, think carefully about what state you store in the URL; it should ideally only contain the state essential for navigating to a specific view of your application. More ephemeral states, like UI state, should be managed in components or services. Conclusion Angular provides lots of built-in tools and features you can effectively leverage to develop robust, maintainable applications without third-party state management libraries like NgRx. While NgRx is undoubtedly a valuable tool for managing state and side effects in large, complex applications, it may not be necessary for all projects. By employing thoughtful design patterns, such as the Singleton Pattern, adhering to principles of immutability, and leveraging Angular's built-in tools like the Router and Services, you can build a highly maintainable and stateful Angular application or library....

Angular 17: Continuing the Renaissance cover image

Angular 17: Continuing the Renaissance

Angular 17: A New Era November 8th marked a significant milestone in the world of Angular with the release of Angular 17. This wasn't just any ordinary update; it was a leap forward, signifying a new chapter for the popular framework. But what made this release truly stand out was the unveiling of Angular's revamped website, complete with a fresh brand identity and a new logo. This significant transformation represents the evolving nature of Angular, aligning with the modern demands of web development. To commemorate this launch, we also hosted a release afterparty, where we went deep into its new features with Minko Gechev from the Angular core team, and Google Developer Experts (GDEs) Brandon Roberts, Deborah Kurata, and Enea Jahollari. But what exactly are these notable new features in the latest version? Let's dive in and explore. The Angular Renaissance Angular has been undergoing a significant revival, often referred to as Angular's renaissance, a term coined by Sarah Drasner, the Director of Engineering at Google, earlier this year. This revival has been particularly evident in its recent versions. The Angular team has worked hard to introduce many new improvements, focusing on signal-based reactivity, hydration, server-side rendering, standalone components, and migrating to esbuild and Vite for a better and faster developer experience. This latest release, in particular, marks many of these features as production-ready. Standalone Components About a year ago, Angular began a journey toward modernity with the introduction of standalone components. This move significantly enhanced the developer experience, making Angular more contemporary and user-friendly. In Angular's context, a standalone component is a self-sufficient, reusable code unit that combines logic, data, and user interface elements. What sets these components apart is their independence from Angular's NgModule system, meaning they do not rely on it for configuration or dependencies. By setting a standalone: true` flag, you no longer need to embed your component in an NgModule and you can bootstrap directly off that component: `typescript // ./app/app.component.ts @Component({ selector: 'app', template: 'hello', standalone: true }) export class AppComponent {} // ./main.ts import { bootstrapApplication } from '@angular/platform-browser'; import { AppComponent } from './app/app.component'; bootstrapApplication(AppComponent).catch(e => console.error(e)); ` Compared to the NgModules way of adding components, as shown below, you can immediately see how standalone components make things much simpler. `ts // ./app/app.component.ts import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent { title = 'CodeSandbox'; } // ./app/app.module.ts import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } // .main.ts import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; platformBrowserDynamic() .bootstrapModule(AppModule) .catch((err) => console.error(err)); ` In this latest release, the Angular CLI now defaults to generating standalone components, directives, and pipes. This default setting underscores the shift towards a standalone-centric development approach in Angular. New Syntax for Enhanced Control Flow Angular 17 introduces a new syntax for control flow, replacing traditional structural directives like ngIf` or `ngFor`, which have been part of Angular since version 2. This new syntax is designed for fine-grained change detection and eventual zone-less operation when Angular completely migrates to signals. It's more streamlined and performance-efficient, making handling conditional or list content in templates easier. The @if` block replaces `*ngIf` for expressing conditional parts of the UI. `ts @if (a > b) { {{a}} is greater than {{b}} } @else if (b > a) { {{a}} is less than {{b}} } @else { {{a}} is equal to {{b}} } ` The @switch` block replaces `ngSwitch`, offering benefits such as not requiring a container element to hold the condition expression or each conditional template. It also supports template type-checking, including type narrowing within each branch. ```ts @switch (condition) { @case (caseA) { Case A. } @case (caseB) { Case B. } @default { Default case. } } ``` The @for` block replaces `*ngFor` for iteration and presents several differences compared to its structural directive predecessor, `ngFor`. For example, the tracking expression (calculating keys corresponding to object identities) is mandatory but offers better ergonomics. Additionally, it supports `@empty` blocks. `ts @for (item of items; track item.id) { {{ item.name }} } ` Defer Block for Lazy Loading Angular 17 introduces the @defer` block, a dramatically improving lazy loading of content within Angular applications. Within the `@defer` block framework, several sub-blocks are designed to elegantly manage different phases of the deferred loading process. The main content within the `@defer` block is the segment designated for lazy loading. Initially, this content is not rendered, becoming visible only when specific triggers are activated or conditions are met, and after the required dependencies have been loaded. By default, the trigger for a `@defer` block is the browser reaching an idle state. For instance, take the following block: it delays the loading of the calendar-imp` component until it comes into the viewport. Until that happens, a placeholder is shown. This placeholder displays a loading message when the `calendar-imp` component begins to load, and an error message if, for some reason, the component fails to load. `ts @defer (on viewport) { } @placeholder { Calendar placeholder } @loading { Loading calendar } @error { Error loading calendar } ` The on` keyword supports a wide a variety of other conditions, such as: - idle` (when the browser has reached an idle state) - interaction` (when the user interacts with a specified element) - hover` (when the mouse has hovered over a trigger area) - timer(x)` (triggers after a specified duration) - immediate` (triggers the deferred load immediately) The second option of configuring when deferring happens is by using the when` keyword. For example: `ts @defer (when isVisible) { } ` Server-Side Rendering (SSR) Angular 17 has made server-side rendering (SSR) much more straightforward. Now, a --ssr` option is included in the `ng new` command, removing the need for additional setup or configurations. When creating a new project with the `ng new` command, the CLI inquires if SSR should be enabled. As of version 17, the default response is set to 'No'. However, for version 18 and beyond, the plan is to enable SSR by default in newly generated applications. If you prefer to start with SSR right away, you can do so by initializing your project with the `--ssr` flag: `shell ng new --ssr ` For adding SSR to an already existing project, utilize the ng add` command of the Angular CLI: `shell ng add @angular/ssr ` Hydration In Angular 17, the process of hydration, which is essential for reviving a server-side rendered application on the client-side, has reached a stable, production-ready status. Hydration involves reusing the DOM structures rendered on the server, preserving the application's state, and transferring data retrieved from the server, among other crucial tasks. This functionality is automatically activated when server-side rendering (SSR) is used. It offers a more efficient approach than the previous method, where the server-rendered tree was completely replaced, often causing visible UI flickers. Such re-rendering can adversely affect Core Web Vitals, including Largest Contentful Paint (LCP), leading to layout shifts. By enabling hydration, Angular 17 allows for the reuse of the existing DOM, effectively preventing these flickers. Support for View Transitions The new View Transitions API, supported by some browsers, is now integrated into the Angular router. This feature, which must be activated using the withViewTransitions` function, allows for CSS-based animations during route transitions, adding a layer of visual appeal to applications. To use it, first you need to import withViewTransitions`: `ts import { provideRouter, withViewTransitions } from '@angular/router'; ` Then, you need to add it to the provideRouter` configuration: `ts bootstrapApplication(AppComponent, { providers: [ provideRouter(routes, withViewTransitions()) ] }) ` Other Notable Changes - Angular 17 has stabilized signals, initially introduced in Angular 16, providing a new method for state management in Angular apps. - Angular 17 no longer supports Node 16. The minimal Node version required is now 18.13. - TypeScript version 5.2 is the least supported version starting from this release of Angular. - The @Component` decorator now supports a `styleUrl` attribute. This allows for specifying a single stylesheet path as a string, simplifying the process of linking a component to a specific style sheet. Previously, even for a single stylesheet, an array was required under `styleUrls`. Conclusion With the launch of Angular 17, the Angular Renaissance is now in full swing. This release has garnered such positive feedback that developers are showing renewed interest in the framework and are looking forward to leveraging it in upcoming projects. However, it's important to note that it might take some time for IDEs to adapt to the new templating syntax fully. While this transition is underway, rest assured that you can still write perfectly valid code using the old templating syntax, as all the changes in Angular 17 are backward compatible. Looking ahead, the future of Angular appears brighter than ever, and we can't wait to see what the next release has in store!...

What's new in Angular v12 cover image

What's new in Angular v12

Angular 12 Released Angular v12 has been released! Here are some main updates. Nullish coalescing operator support ` @Component(){ selector: 'app-comp', template: Hello, {{title ?? 'Angular'}}` }) ` Core APP_INITIALIZER now supports Observable. It is also a long-waited feature request: in Angular v12, we can now use Observable` as `APP_INITIALIZER`. ` import { APPINITIALIZER } from '@angular/core'; function observableInitializer() { // Before Angular v12, you have to return a Promise. // return () => someObservable().toPromise(); // Now you can return the observable directly. return () => someObservable(); } @NgModule({ imports: [...], providers: [{ provide: APPINITIALIZER, useFactory: observableInitializer, multi: true }], ... }) export class AppModule {} ` Http HttpClient Before Angular 12, if you wanted to pass boolean` or `number` as `HttpParams`, you had to: HttpClient.get('api/endpoint', { param1: '10', param2: 'true' }) With Angular 12, the HttpParams` has been updated to accept the `boolean` and the `number` values: HttpClient.get('api/endpoint', { param1: 10, param2: true }) Http Interceptor Context This is a very long waited feature. In Angular 12, you can now pass data between interceptors. Here is a great article by Netanel Basal. More readable Http status code An enum HttpStatusCode` is provided, so the user can use this enum to write more readable code for status code check. httpClient.post('api/endpoint', postBody, {observe: 'response'}).subscribe(res => { if (res.status === HttpStatusCode.Ok) { ... } }); Form Form: Support min/max validator HTML5 standards support built in validator such as min/max` for `input` element. Before Angular 12, the min/max` attributes are ignored by `FormsModule`, so the Angular FormControl `valid` property will still be true even the input data exceed the `min` or `max` range. But the underlying DOM HTMLInputElement's `validity.valid` will still be false. From Angular 12, the min/max` validator is implemented so the `valid` result will be consistent with native DOM status. Animation Sometimes we want to disable animations. Before Angular 12, we needed to provide the NoopAnimationModule`. In Angular 12, there is a new way to do that by using `BrowserAnimationsModule.withConfig({disableAnimations: true})` so the user can disable animations based on the runtime information. Router Router: Support location.historyGo() functionality. Support location.historyGo(relativePosition: number)` to navigate to the specific history page so it will be compatible with the DOM history.go() API. Router: Provides RouterOutletContract interface Provides RouterOutletContract` interface allows the 3rd party library to implement its own `RouterOutlet` more easily (such as add own logic for navigation, animation). For details, please refer to this PR https://github.com/angular/angular/pull/40827. Tooling Angular Linker Ivy has been released from Angular 9 and has become the default in the Angular App from Angular 10. It is fast, easy to debug, and has a lot of possibilities in the future. However, for Angular library development, the user still has to compile the lib with the old ViewEngine` to publish to `npm`. The Angular library deploy process is: 1. The developer compiles the lib with ViewEngine`. 2. When the Angular app is using the lib, when yarn install`, Angular compiler will travel the `node_modules` directory and use `ngcc` to convert the `ViewEngine` library to `Ivy`. This process is slow and needs to run whenever `node_modules` changes. The reason we need this process is if we remove ngcc` and publish the `Ivy instructions` to npm, the instruction code will become the `public API`. It is very difficult to change it in the future. `ViewEngine` is not instruction but metadata, so it can be used across multiple versions even for `Ivy`. To resolve this issue, Angular provides an RFC last year, https://github.com/angular/angular/issues/38366, with an idea which is Angular Linker`. It is an advanced version `ngcc`. It compiles the Angular library code with `partial` mode. - The output is not Ivy` instructions, so it allows future changes. - The output is still metadata`, but not like `ViewEngine`, the metadata is `self-contained`, so the output for one component contains all the information needed to compile this component without any other global knowledge(such as `NgModule`). It is fast and be able to do incremental/cache/parallel build. - The new intermediate output can be directly consumed by Angular Compiler, so the node_modules` folder doesn't need to be touched. To use this feature, update the option in the tsconfig.lib.prod.json` like this. "enableIvy": "true", "compilationMode": "partial" Typescript 4.2 support - Supports Typescript 4.2. E2E protractor RFC Angular is using protractor` for `e2e` test. The `protractor` has a long history from `2013`. It helped users write `e2e` test, and to support better `async` test, `protractor` did a lot of work via ControlFlow to make `async/await` work. Since the native async/await` is introduced into Javascript standard, `protractor` ControlFlow becomes incompatible with the `native async/await`, so `protractor` drops the ControlFlow and uses the solution of `selenium-webdriver` completely. Also protractor` has a lot of features just for `AngularJS` such as `locator/mock`. After removing these codes, `protractor` becomes a wrapper of `selenium wrapper` without any valuable functionalities. And now the Angular developers are using several alternative solutions to do the e2e` test such as: - Cypress` - PlayWright` - Puppeteer` The Angular team posts an RFC to deprecate the protractor` and make Angular easy to integrate with the 3rd party e2d platform. Here is the RFC https://github.com/angular/protractor/issues/5502 Based on the RFC, Angular plans to drop the protractor` support until `Angular v15` and will guide the Angular developers to select an alternative platform when the user tries to run `ng e2e`. CLI: Webpack 5 support Webpack 5 support many new features such as: - Module federation is a new way to develop micro-frontend - Disk caching for faster build - Better tree shaking CLI: Lint Angular v11 deprecated to use TSLint` as the default linter, and Angular v12 remove `TSLint` and `codelyzer` from `cli`, so please reference the following article to migrate to `eslint`. (https://github.com/angular-eslint/angular-eslint#migrating-from-codelyzer-and-tslint) CLI: Build When we build an Angular app, we use ng build --prod` all the time. The options often confuse the user that `--prod` will make a production build, but it is just an alias of `ng build --configuration=production`. To make this more clear, `CLI` remove `--prod` and add a `defaultConfiguration` setting with the value `production`. Now, `ng build` default will build with `production configuration`. CLI: Build bundles with ES2017+ Until Angular 12, Angular only supports output bundles in ES2015 format. ES2017+ is not supported because Zone.js` is not supporting `native async/await` patching. From Angular 12, CLI supports to only transpile the `native async/await` code to `Promise` but keep the other ES2017+ code untouched, so you can specify the output bundle format to ES2017+ from Angular 12. CLI: `inlineCritical` by default The inlineCritical` settings of styles optimization in the `angular.json` becomes true by default. Please reference here for more details. CLI: `strict` mode by default The workspace generated by CLI will use strict mode by default. To generate non-strict mode, use --no-strict` option. CLI: Built in `tailwindcss` support Tailwindcss` is a popular CSS library. Before Angular 12, to integrate with `Tailwindcss`, the user needs to do some `postcss` configuration themselves. With Angular 12, this is done by the `CLI` so it is super easy to do the integration. npm install -D tailwindcss npx tailwindcss init This is the same process to install the normal Tailwindcss`. CLI: confirm `ng add` before install packages from npm. Now ng add` will ask the user to confirm the package/version before installing the packages. This gives the user a chance to stop the process when the user has a `typo` in the project name or want to initialize the project with other options. Angular Language Service for Ivy Angular Ivy has been released since v9, but the Angular Language Service is still using View Engine` to compile the template and did a lot of `magic` work to make everything work in `Ivy`, which is not efficient. Also, since `View Engine` is deprecated, some issues and features may not be implemented in the `View Engine` version of Language Service for `Ivy`. Now we have the new Angular Language Service written in Ivy` and for `Ivy`. You can opt-in the new Angular Language Service beta with Ivy support now. To use it in VSCode`, try the following steps. 1. Install the latest extension in the VSCode Marketplace 2. Set strictTemplates: true in tsconfig.json Angular compiler options 3. Go to Preferences -> Settings -> Extensions -> Angular Language Service -> Check “Experimental Ivy” Please reference this blog for more details. CLI: Support inlineStyleLanguage option A new build option inlineStyleLanguage` is added. Now you can write `css/sass/scss/less` in the inline style of a component! Angular Components MDC Web based new component Angular Component is continuing to create new MDC Web based components and the backward-compatible. Here is the design. For the new Angular Material Component`, `Angular CDK` provides the behavior and the `MDC Web Component` provides the user experiences defined by the `Material Design` spec. The `Angular Material` team doesn't need to maintain another implementation of the `Material Design` and the UI will be more consistent. New `@use` SASS module system You may already use the @import` syntax in the SASS style file, but it has some flaws and make the module system hard to maintain. For details, please reference the article here. Now Angular Components` are using the new `@use` syntax for the better `module system` of SASS. To use the new module system, you can use the following schematic to migrate. ng g @angular/material:themingApi Also, the node-sass` package will be replaced by the new `sass` package. Deprecations - IE11 support is deprecated! - ViewEngine` for application build is removed. All applications are using `Ivy` to build. - entryComponent` is removed from `Component` schematic. - string` based lazy loading support is dropped. { path: 'Lazy', component: LazyComponent }, { path: 'lazy', loadChildren: './modules/lazy/lazy.module#LazyModule', } is not supported, please use dynamic import` instead. { path: 'Lazy', component: LazyComponent }, { path: 'lazy', loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule) } For the full deprecation list, please check here. How to update to v12 Use the Angular CLI: ng update @angular/cli @angular/core You can find more information from update.angular.io/....

Testing a Fastify app with the NodeJS test runner cover image

Testing a Fastify app with the NodeJS test runner

Introduction Node.js has shipped a built-in test runner for a couple of major versions. Since its release I haven’t heard much about it so I decided to try it out on a simple Fastify API server application that I was working on. It turns out, it’s pretty good! It’s also really nice to start testing a node application without dealing with the hassle of installing some additional dependencies and managing more configurations. Since it’s got my stamp of approval, why not write a post about it? In this post, we will hit the highlights of the testing API and write some basic but real-life tests for an API server. This server will be built with Fastify, a plugin-centric API framework. They have some good documentation on testing that should make this pretty easy. We’ll also add a SQL driver for the plugin we will test. Setup Let's set up our simple API server by creating a new project, adding our dependencies, and creating some files. Ensure you’re running node v20 or greater (Test runner is a stable API as of the 20 major releases) Overview `index.js` - node entry that initializes our Fastify app and listens for incoming http requests on port 3001 `app.js` - this file exports a function that creates and returns our Fastify application instance `sql-plugin.js` - a Fastify plugin that sets up and connects to a SQL driver and makes it available on our app instance Application Code A simple first test For our first test we will just test our servers index route. If you recall from the app.js` code above, our index route returns a 501 response for “not implemented”. In this test, we're using the createApp` function to create a new instance of our Fastify app, and then using the `inject` method from the Fastify API to make a request to the `/` route. We import our test utilities directly from the node. Notice we can pass async functions to our test to use async/await. Node’s assert API has been around for a long time, this is what we are using to make our test assertions. To run this test, we can use the following command: By default the Node.js test runner uses the TAP reporter. You can configure it using other reporters or even create your own custom reporters for it to use. Testing our SQL plugin Next, let's take a look at how to test our Fastify Postgres plugin. This one is a bit more involved and gives us an opportunity to use more of the test runner features. In this example, we are using a feature called Subtests. This simply means when nested tests inside of a top-level test. In our top-level test call, we get a test parameter t` that we call methods on in our nested test structure. In this example, we use `t.beforeEach` to create a new Fastify app instance for each test, and call the `test` method to register our nested tests. Along with `beforeEach` the other methods you might expect are also available: `afterEach`, `before`, `after`. Since we don’t want to connect to our Postgres database in our tests, we are using the available Mocking API to mock out the client. This was the API that I was most excited to see included in the Node Test Runner. After the basics, you almost always need to mock some functions, methods, or libraries in your tests. After trying this feature, it works easily and as expected, I was confident that I could get pretty far testing with the new Node.js core API’s. Since my plugin only uses the end method of the Postgres driver, it’s the only method I provide a mock function for. Our second test confirms that it gets called when our Fastify server is shutting down. Additional features A lot of other features that are common in other popular testing frameworks are also available. Test styles and methods Along with our basic test` based tests we used for our Fastify plugins - `test` also includes `skip`, `todo`, and `only` methods. They are for what you would expect based on the names, skipping or only running certain tests, and work-in-progress tests. If you prefer, you also have the option of using the describe` → `it` test syntax. They both come with the same methods as `test` and I think it really comes down to a matter of personal preference. Test coverage This might be the deal breaker for some since this feature is still experimental. As popular as test coverage reporting is, I expect this API to be finalized and become stable in an upcoming version. Since this isn’t something that’s being shipped for the end user though, I say go for it. What’s the worst that could happen really? Other CLI flags —watch` - https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--watch —test-name-pattern` - https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--test-name-pattern TypeScript support You can use a loader like you would for a regular node application to execute TypeScript files. Some popular examples are tsx` and `ts-node`. In practice, I found that this currently doesn’t work well since the test runner only looks for JS file types. After digging in I found that they added support to locate your test files via a glob string but it won’t be available until the next major version release. Conclusion The built-in test runner is a lot more comprehensive than I expected it to be. I was able to easily write some real-world tests for my application. If you don’t mind some of the features like coverage reporting being experimental, you can get pretty far without installing any additional dependencies. The biggest deal breaker on many projects at this point, in my opinion, is the lack of straightforward TypeScript support. This is the test command that I ended up with in my application: I’ll be honest, I stole this from a GitHub issue thread and I don’t know exactly how it works (but it does). If TypeScript is a requirement, maybe stick with Jest or Vitest for now 🙂...