Skip to content

Introduction to building an Angular app with Nx Workspace

Introduction to building an Angular app with Nx Workspace

Nx Workspace is a tool suite designed to architect, build and manage monorepos at any scale. It has out-of-the-box support for multiple frontend frameworks like Angular and React as well as backend technologies including Nest, Next, and Express. In this article, we will focus on building a workspace for an Angular-based project.

Monorepo fundamentals

The most basic definition of a monorepo is that it is a single repository that consists of multiple applications and libraries. This all is accompanied by a set of tooling, which enables us to work with those projects. This approach has several benefits including:

  • shared code - it enables us to share code across the whole company or organization. This can result in code that is more DRY as we can reuse the common patterns, components, and types. This enables to share the logic between frontend and backend as well.
  • atomic changes - without the monorepo approach, whenever we need to make a change that will affect multiple projects, we might need to coordinate those changes across multiple repositories, and possibly by multiple teams. For example, an API change might need to be reflected both on a server app and a client app. With monorepo, all of those changes can be applied in one commit on one repository, which greatly limits the coordination efforts necessary
  • developer mobility - with a monorepo approach we get one consistent way of performing similar tasks even when using multiple technologies. The developers can now contribute to other teams' projects, and make sure that their changes are safe across the whole organization.
  • single set of dependencies - By using a single repository with one set of dependencies, we make sure that our whole codebase depends on one single version of the given dependency. This way, there are no version conflicts between libraries. It is also less likely that the less used part of the repository will be left with an obsolete dependency because it will be updated along the way when other parts of the repository do this update.

If you want to read more about monorepos, here are some useful links:

  • (Monorepo in Git)[https://www.atlassian.com/git/tutorials/monorepos]
  • (Monorepo != monolith)[https://blog.nrwl.io/misconceptions-about-monorepos-monorepo-monolith-df1250d4b03c]
  • (Nrwl Nx Resources)[https://nx.dev/latest/angular/getting-started/resources]

Create a new workspace

With all that said about the monorepo, how do we actually create one using Nx Workspace and Angular? Just like with Angular CLI, there is an Nx CLI that does all the heavy lifting for us. With the following command, we can create a new workspace that leverages all of the aforementioned benefits of a monorepo:

npx create-nx-workspace --preset=angular

The tool will ask for a project name, stylesheet format, and linting tool. For the linting, I recommend using ESLint, which is a more modern tool. The CLI will also ask whether we want to use Nx Cloud in our workspace. We can opt-out from this for now as we can easily add that later on. After the command finishes, we end up with a brand new project all set up. Let's start by analyzing what has been generated for us.

Nx uses certain toolset by default:

  • Jest for testing (instead of Karma and Jasmine)
  • Cypress for e2e testing (instead of Protractor)
  • ESLint for linting (instead of TSLint) in case you decide to use it when creating a workspace

All of these are modern tools, and I recommend sticking to them as they provide very good developer experiences, and are actively maintained.

The base structure that is created for us looks as follows:

- apps/
  - {{appName}}
  - {{appName}}-e2e
- libs
- tools
  • apps/*: here go all the application projects - by default, it'll be the app we created and an accompanying e2e tests app
  • libs/*: where all of the libraries that we create go
  • tools/*: here, we can put all of the necessary tooling scripts etc that are necessary in our project
  • and all the root configuration files like angular.json, config files for Jest, ESLint, Prettier, etc

This whole structure is created for us so that we can focus on building the solution right from the beginning.

Migration from an existing Angular project

If you already have an existing Angular app that was built using the Angular CLI, you can still easily migrate to an Nx Workspace. A project that contains only a single Angular app can be migrated automatically with just one command:

ng add @nrwl/workspace

This will install all of the dependencies, required by Nx, and create the folder structure mentioned in the previous section. It will also migrate the app into apps folder and e2e suite into apps/{{appName}}-e2e folder. Nx modifies package.json script, and decorates Angular CLI so you can still use the same commands like ng build, ng serve, or npm start.

It is important to remember that the version of Angular and Nx must match so that this process goes smoothly. For example, if your project is using version 10 of Angular, please make sure to use the latest 10.x.x version of Nx CLI.

In case you already have multiple projects, you still can migrate with few manual steps described in the Nx docs.

Nx CLI

In the following sections, we will use Nx CLI to simplify performing operations on the monorepo. You can install it globally by running one of the following commands:

npm install nx --global
yarn global add nx

If you don't want to install a global dependency, you can always invoke local nx via either

npm run nx

or

yarn nx

Create a library

One of the core ideas behind the Nx Workspace monorepo approach is to divide our code into small, manageable libraries. So by using Nx, we will end up creating a library often. Luckily, you can do this by typing one command in the terminal:

nx g @nrwl/angular:lib mylib

This will create a libs/mylib folder with the library set up so we can build, test, and use it in other libraries or applications right away. To group the libraries you can use the --directory={{subfolderName}} additional parameter to specify a subfolder under which a library should be created. You don't have to worry about choosing the perfect place for your library from the start, though. You can always move it around later on using @nrwl/workspace:move schematics, and you can find all the other options for generating a new Angular library in the official docs.

Every library has an index.ts file at its root, which should be the only access point to a library. Each part of the library that we want to be part of the lib's public API should be exported from this file. Everything else is considered private to the library. This is important for maintaining the correct boundaries between libraries and applications, which makes for more well-structured code.

Affected

One of the greatest things about Nx Workspace is that it understands dependencies within the workspace. This allows for testing and linting only the projects that are affected by a given change. Nx comes with a few built-in commands for that.

npx nx affected:lint
npx nx affected:test
npx nx affected:e2e
npx nx affected:build

Those commands will run lint, test, e2e, and build targets, but only on projects that are affected, and therefore they will lower the execution time by a lot in most use-cases. The commands below are equivalent to the ones above, but they use more generic syntax, which can be extended to different targets if necessary.

nx affected --target=lint
nx affected --target=test
nx affected --target=e2e
nx affected --target=build

For all of the commands mentioned above, we can parallelize them by using --parallel flag and --maxParallel={{nr}} to cap the number of parallel tasks. There are multiple additional useful parameters that the affected task can take. Please visit the official docs for more details.

Conclusion

Working with a monorepo has a lot of advantages, and Nx Workspace provides us with multiple tools to get the most of that. By using it, we can speed up our development loop by being able to create atomic changes to the repository, and make sure that the whole workspace is compatible with that change. All of this is done with blazing fast tooling that can be scaled to any project size we might have.

In case you have any questions, you can always tweet or DM me @ktrz. I'm always happy to help!

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

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!...

A Guide to (Typed) Reactive Forms in Angular - Part III (Creating Custom Form Controls) cover image

A Guide to (Typed) Reactive Forms in Angular - Part III (Creating Custom Form Controls)

So far in the series, we have learned the basics of Angular Reactive forms and created some neat logic to construct and display dynamic forms. But our work is still not done yet. Whether we just want to make our controls look good and enhance them with some markup, or whether we need a more complex control than a simple textarea, input or checkbox, we'll either need to use a component library such as Angular Material Components or get familiar with the ControlValueAccessor` interface. Angular Material, by the way, uses ControlValueAccessor` in its components and I recommend looking into the source code if you want to learn some advanced use cases (I have borrowed a lot of their ideas in the past). In this post, however, we will build a basic custom control from scratch. A common requirement for a component that cannot be satisfied by using standard HTML markup I came across in many projects is having a searchable combobox**. So let's build one. We will start by creating a new Angular component and we can do that with a handy ng cli command: ` ng generate component form-fields/combobox ` Then we'll implement displaying data passed in the form of our FormField` class we have defined earlier in a list and allowing for filtering and selecting the options: `TypeScript // combobox.component.ts import { Component, ElementRef, Input, ViewChild } from '@angular/core'; import { FormField } from '../../forms.model'; @Component({ selector: 'app-combobox', templateUrl: './combobox.component.html', styleUrls: ['./combobox.component.scss'], }) export class ComboboxComponent { private filteredOptions?: (string | number)[]; // a simple way to generate a "unique" id for each component // in production, you should rather use a library like uuid public id = String(Date.now() + Math.random()); @ViewChild('input') public input?: ElementRef; public selectedOption = ''; public listboxOpen = false; @Input() public formFieldConfig!: FormField; public get options(): (string | number)[] { return this.filteredOptions || this.formFieldConfig.options || []; } public get label(): string { return this.formFieldConfig.label; } public toggleListbox(): void { this.listboxOpen = !this.listboxOpen; if (this.listboxOpen) { this.input?.nativeElement.focus(); } } public closeListbox(event: FocusEvent): void { // timeout is needed to prevent the list box from closing when clicking on an option setTimeout(() => { this.listboxOpen = false; }, 150); } public filterOptions(filter: string): void { this.filteredOptions = this.formFieldConfig.options?.filter((option) => { return option.toString().toLowerCase().includes(filter.toLowerCase()); }); } public selectOption(option: string | number): void { this.selectedOption = option.toString(); this.listboxOpen = false; } } ` `HTML {{ label }} ▼ {{ option }} ` > Note: For the sake of brevity, we will not be implementing keyboard navigation and aria labels. I strongly suggest referring to W3C WAI patterns to get guidelines on the markup and behavior of an accessible combo box. While our component now looks and behaves like a combo box, it's not a form control yet and is not connected with the Angular forms API. That's where the aforementioned ControlValueAccessor` comes into play along with the `NG_VALUE_ACCESSOR` provider. Let's import them first, update the `@Component` decorator to provide the value accessor, and declare that our component is going to implement the interface: `TypeScript import { ControlValueAccessor, NGVALUE_ACCESSOR } from '@angular/forms'; @Component({ selector: 'app-combobox', templateUrl: './combobox.component.html', styleUrls: ['./combobox.component.scss'], providers: [ { // provide the value accessor provide: NGVALUE_ACCESSOR, // for our combobox component useExisting: ComboboxComponent, // and we don't want to override previously provided value accessors // we want to provide an additional one under the same "NGVALUE_ACCESSOR" token instead multi: true, }, ], }) export class ComboboxComponent implements ControlValueAccessor { ` Now, the component should complain about a few missing methods that we need to satisfy the ControlValueAccessor` interface: - A writeValue` method that is called whenever the form control value is updated from the forms API (e.g. with `patchValue()`). - A registerOnChange` method, which registers a callback function for when the value is changed from the UI. - A registerOnTouched` method that registers a callback function that marks the control when it's been interacted with by the user (typically called in a `blur` handler). - An optional setDisabledState` method that is called when we change the form control `disabled` state- Our (pretty standard) implementation will look like the following: `TypeScript private onChanged!: Function; private onTouched!: Function; public disabled = false; // This will write the value to the view if the form control is updated from outside. public writeValue(value: any) { this.value = value; } // Register a callback function that is called when the control's value changes in the UI. public registerOnChange(onChanged: Function) { this.onChanged = onChanged; } // Register a callback function that is called by the forms API on initialization to update the form model on blur. public registerOnTouched(onTouched: Function) { this.onTouched = onTouched; } public setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; } public setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; } ` We don't have to update the template a lot, but we can add [disabled]="disabled"` attribute on our button and input to disable the interactive UI elements if the provided form control was disabled. The rest of the work can be done in the component's TypeScript code. We'll call `this.onTouched()` in our `closeListbox` method, and create a `value` setter that updates our internal value and also notifies the model about the value change: `TypeScript public set value(val: string | number) { this.selectedOption = val.toString(); this.onChanged && this.onChanged(this.selectedOption); this.onTouched && this.onTouched(); } ` You can check out the full implementation on StackBlitz. Conclusion In this series, we've explored the powerful features of Angular reactive forms, including creating and managing dynamic typed forms. We also demonstrated how to use the ControlValueAccessor interface to create custom form controls, such as a searchable combo box. This knowledge will enable you to design complex and dynamic forms in your Angular applications. While the examples provided here are basic, they serve as a solid foundation for building more advanced form controls and implementing validation, accessibility, and other features that are essential for a seamless user experience. By mastering Angular reactive forms and custom form controls, you'll be able to create versatile and maintainable forms in your web applications. If you want to further explore the topic and prefer a form of a video, you can check out an episode of JavaScript Marathon by my amazing colleague Chris. Happy coding!...

How to Write a Custom Structural Directive in Angular - Part 2 cover image

How to Write a Custom Structural Directive in Angular - Part 2

How to write a custom structural directive in Angular - part 2 In the previous article I've shown how you can implement a custom structural directive in Angular. We've covered a simple custom structural directive that implements interface similar to Angular's NgIf directive. If you don't know what structural directives are, or are interested in basic concepts behind writing custom one, please read the previous articlefirst. In this article, I will show how to create a more complex structural directive that: - passes properties into the rendered template - enables strict type checking for the template variables Starting point I am basing this article on the example implemented in the part 1 article. You can use example on Stackblitz as a starting point if you wish to follow along with the code examples. Custom NgForOf directive This time, I would like to use Angular's NgForOf directive as an example to re-implement as a custom CsdFor` directive. Let's start off by using Angular CLI to create a new module, and directive files: `shell ng generate module for ng generate directive for/for --module for or shorthand ng g m for ng g d for/for --module for ` First, we need to follow similar steps as with the CsdIf` directive. - add constructor with TemplateRef`, and `ViewContainerRef` injected - add an @Input` property to hold the array of items that we want to display `ts import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core'; @Directive({ selector: '[csdFor]', }) export class ForDirective { constructor( private templateRef: TemplateRef, private vcr: ViewContainerRef ) {} @Input() csdForOf: T[] = []; } ` Then, in the ngOnInit` hook we can render all the items using the provided template: `ts export class ForDirective implements OnInit { private items: T[] = []; constructor( private templateRef: TemplateRef, private vcr: ViewContainerRef ) {} @Input() csdForOf: T[] = []; ngOnInit(): void { this.renderItems(); } private renderItems(): void { this.vcr.clear(); this.csdForOf.map(() => { this.vcr.createEmbeddedView(this.templateRef); }); } } ` Now, we can verify that it displays the items properly by adding the following template code to our AppComponent`. `html This is item ` It displays the items correctly, but doesn't allow for changing the displayed collection yet. To implement that, we can modify the csdForOf` property to be a setter and rerender items then: `ts export class ForDirective { private items: T[] = []; constructor( private templateRef: TemplateRef, private vcr: ViewContainerRef ) {} @Input() set csdForOf(items: T[]) { this.items = items; this.renderItems(); } private renderItems(): void { this.vcr.clear(); this.items.map(() => { this.vcr.createEmbeddedView(this.templateRef); }); } } ` Now, our custom directive will render the fresh items every time the collection changes (its reference). Accessing item property The above example works nice already, but it doesn't allow us to display the item's content yet. The following code will display "no content"` for each template rendered. `html This is item: {{ item || '"no content"' }} ` To resolve this, we need to provide a value of each item into a template that we are rendering. We can do this by providing second param to createEmbeddedView` method of `ViewContainerRef`. `ts export class ForDirective { / rest of the class */ private renderItems(): void { this.vcr.clear(); this.items.map((item) => { this.vcr.createEmbeddedView(this.templateRef, { // provide item value here }); }); } } ` The question is what key do we provide to assign it under item` variable in the template. In our case, the item is a default param, and Angular uses a reserved `$implicit` key to pass that variable. With that knowledge, we can finish the renderItems` method: `ts export class ForDirective { / rest of the class */ private renderItems(): void { this.vcr.clear(); this.items.map((item) => { this.vcr.createEmbeddedView(this.templateRef, { $implicit: item, }); }); } } ` Now, the content of the item is properly displayed: Adding more variables to the template's context Original NgForOf` directives allows developers to access a set of useful properties on an item's template: - index` - the index of the current item in the collection. - count` - the length of collection - first` - true when the item is the first item in the collection - last` - true when the item is the last item in the collection - even` - true when the item has an even index in the collection - odd` - true when the item has an odd index in the collection We can pass those as well when creating a view for a given element along with the $implicit` parameter: `ts export class ForDirective { / rest of the class */ private renderItems(): void { this.vcr.clear(); this.items.map((item, index, arr) => { this.vcr.createEmbeddedView(this.templateRef, { $implicit: item, index, first: index === 0, last: index === arr.length - 1, even: (index & 1) === 0, odd: (index & 1) === 1, count: arr.length, }); }); } } ` And now, we can use those properties in our template. `html This is item: {{ item }}. Index: {{ i }} First: {{ isFirst }} Last: {{ isLast }} Even: {{ isEven }} Odd: {{ isOdd }} Count: {{ size }} ` Improve template type checking Lastly, as a developer using the directive it improves, the experience if I can have type checking in the template used by csdFor` directive. This is very useful as it will make sure we don't mistype the property name as well as we only use the `item`, and additional properties properly. Angular's compiler allows us to define a static `ngTemplateContextGuard` methods on a directive that it will use to type-check the variables defined in the template. The method has a following shape: `ts static ngTemplateContextGuard( dir: DirectiveClass, ctx: unknown): ctx is DirectiveContext { return true; } ` This makes sure that the properties of template rendered by our DirectiveClass` will need to conform to `DirectiveContext`. In our case, this can be the following: `ts interface ForDirectiveContext { $implicit: T; index: number; first: boolean; last: boolean; even: boolean; odd: boolean; count: number; } @Directive({ selector: '[csdFor]', }) export class ForDirective { static ngTemplateContextGuard( dir: ForDirective, ctx: unknown ): ctx is ForDirectiveContext { return true; } / rest of the class */ } ` Now, if we eg. try to access item's property that doesn't exist on the item's interface, we will get a compilation error: `html This is item: {{ item.someProperty }}. ` The same would happen if we made a typo in any of the context property names: `html This is item: {{ item }}. ` Summary In this article, we've created a clone of Angular's built-in NgForOf` directive. The same approach can be used to create any other custom directive that your project might need. As you can see, implementing a custom directive with additional template properties and great type checking experience is not very hard. If something was not clear, or you want to play with the example directive, please visit the example on Stackblitz. In case you have any questions, you can always tweet or DM me at @ktrz. I'm always happy to help!...

Being a CTO at Any Level: A Discussion with Kathy Keating, Co-Founder of CTO Levels cover image

Being a CTO at Any Level: A Discussion with Kathy Keating, Co-Founder of CTO Levels

In this episode of the engineering leadership series, Kathy Keating, co-founder of CTO Levels and CTO Advisor, shares her insights on the role of a CTO and the challenges they face. She begins by discussing her own journey as a technologist and her experience in technology leadership roles, including founding companies and having a recent exit. According to Kathy, the primary responsibility of a CTO is to deliver the technology that aligns with the company's business needs. However, she highlights a concerning statistic that 50% of CTOs have a tenure of less than two years, often due to a lack of understanding and mismatched expectations. She emphasizes the importance of building trust quickly in order to succeed in this role. One of the main challenges CTOs face is transitioning from being a technologist to a leader. Kathy stresses the significance of developing effective communication habits to bridge this gap. She suggests that CTOs create a playbook of best practices to enhance their communication skills and join communities of other CTOs to learn from their experiences. Matching the right CTO to the stage of a company is another crucial aspect discussed in the episode. Kathy explains that different stages of a company require different types of CTOs, and it is essential to find the right fit. To navigate these challenges, Kathy advises CTOs to build a support system of advisors and coaches who can provide guidance and help them overcome obstacles. Additionally, she encourages CTOs to be aware of their own preferences and strengths, as self-awareness can greatly contribute to their success. In conclusion, this podcast episode sheds light on the technical aspects of being a CTO and the challenges they face. Kathy Keating's insights provide valuable guidance for CTOs to build trust, develop effective communication habits, match their skills to the company's stage, and create a support system for their professional growth. By understanding these key technical aspects, CTOs can enhance their leadership skills and contribute to the success of their organizations....