Skip to content

Getting Started with Angular & Apollo

GraphQL came about to solve some of the issues present with RESTful API architecture. Issues like: over-fetching (getting far more data than I need), under-fetching (not getting enough data and having to make another round-trip), all-or-nothing (if an error occurs while trying to retrieve any of the data, the operation fails), type-safety (require what is expected, know what will be returned; thus, fewer bugs), etc. It is a very powerful spec (all hail the mighty resolver) that has caused a pretty massive shift in the industry.

The focus of this article will be on interacting with a GraphQL API to query and mutate data through an Angular SPA. We will build an app around an Event Calendar, view events, create & edit events, etc. To connect to our GraphQL API we will be using the Apollo Angular library. Apollo is a great platform implementation of GraphQL with a variety of awesome libraries, toolsets, even a GraphQL server.

Follow along with the repo here.

Setup

To begin, let’s create a new Angular Application using the angular cli. If you have not installed the angular cli, it is very simple, open your favorite terminal and install it globally with npm:

npm i -g @angular/cli

Once completed, you can validate that it was installed successfully by checking the version:

ng --version

Create the App

Now lets create the app using the cli (you will want to be in the directory where you want the app installed, if so, cd into that directory first):

$ ng new event-calendar --style=scss --routing=true

And huzzah! A new angular app! For sanity sake, let’s make sure everything went smoothly, cd into the new app directory and run it:

$ cd event-calendar
$ ng serve

If the app started successfully, you should be able to open a web browser window and navigate to http://localhost:4200/ and see the app. Note. 4200 is the default port, if you would like to run it on some other port, add the port as a param to the ng serve command:

$ ng serve --port 4201

Adding Apollo

The ng add functionality of the cli makes it incredibly easy to add new modules to our angular apps. Including the angular apollo GraphQL client implementation module;

$ ng add apollo-angular

This command does a few things for us:

  • Installs and adds the required dependencies to our package.json
  • Creates a GraphQL module: ./src/app/graphql.module.ts which has the initial setup required to create a connection to our graphql server and expose the connection as an angular module
  • Adds the GraphQL module to the Application module: ./src/app/app.module imports property to make it available to our application.

This is awesome as it took care of the initial boilerplate work for us. The only thing we need to do is set the URI of where our GraphQL API server is running. Open the ./src/app/graphql.module.ts GraphQL Module file and you will see a const variable named: uri that is currently and empty string with a helpful comment saying telling you to “<-- add the URL of the GraphQL server here”. Go ahead and do as the comment says and let's add our GraphQL Server URI. For the purpose of this article, the URI will be: http://127.0.0.1:3000/graphql. Note this is a local graphql api I have running, checkout the repo for more information.

Adding Angular Material Styling

The focus of this article is not on Angular Material, but it is a component of this project. Checkout the Angular Material Docs for usage, component docs, guides, etc. We will add the angular material module to our app using ng add just like we did with apollo:

$ ng add @angular/material

This will prompt you with some questions:

  1. Choose a prebuilt theme name, or “custom” for a custom theme → I went with Deep Purple/Amber. Choose whatever you would like.
  2. Set up HammerJS for gesture recognition → Y
  3. Set up browser animations for Angular Material → Y

And just like with apollo, this will install the required dependencies and update the package.json. It also add the theming info to ./src/styles.scss as well as importing the Roboto & Material Design icon fonts.

Getting Calendar Events

That is enough boilerplate/setup. Time to start leveraging the power of GraphQL. Let’s start with a query to get a list of events for our calendar and display these events.

Creating the calendar-event module

Angular architecture promotes the creation of modules; think package-by-feature. A module should contain all of the necessary components to provide for the full feature. Things like:

  • Routing
  • Models
  • Services
  • Route Guards
  • Components

Those are all traditional angular class types, we will also have:

  • Queries
  • Mutations

To support this module interacting with our GraphQL server to query & mutate data. Again, we will use the cli to create our module:

$ ng g module calendar-event --routing=true

This creates a directory called ./src/app/calendar-event for us with 2 files: ./src/app/calendar-event/calendar-event-routing.module.ts and ./src/app/calendar-event/calendar-event.module.ts. These are the building blocks of our module. We can leave these alone for now.

Calendar Event Models

Let’s create a model that will represent a calendar event. Create a directory called models inside of ./src/app/calendar-event. And in this directory create a file: calendar-event.model.ts. This is where we will define the models that represent a calendar event entry.

export type EventStatus =UPCOMING|STARTED|COMPLETED;
export type AttendingStatus =GOING|PENDING|NOT_GOING;

export type Guest = {
	_id: string;
	name: string;
	email: string;
	attending: AttendingStatus;
}

export type CalendarEvent = {
	_id: string;
	Status: EventStatus;
	eventStart: string;
	startTime: string;
	eventEnd: string;
	endTime: string;
	name: string;
	description?: string;
	location?: string;
	guests?: Guest[];
}

GraphQL Queries

To solve the over/under-fetching problem with REST, GraphQL exists as a querying framework for your API. What this means is, as the client/consumer of the API, you can define what fields you want returned from the API. This is incredibly powerful. It allows us to select only the fields we want/need to be returned without the potential overhead of all the fields.

On the flipside, there is not a potential second round trip request. For instance, if you have a list of objects and in your list you only need the id and a couple fields, then that is your query and that is what the API returns; no extra bloat from fields you do not need. Then if the user navigates to a details page, you can run another query to return all of the fields in the object and display those. We can also specify multiple queries in one request and it will return the results from all queries.

Note it does not process the queries necessarily in the order you give them.

Let’s look at a quick example of this.

For this example, we have an API that has two queries it exposes: hello which returns a string: ‘HELLO’, and world which also returns a string: ‘WORLD’. To retrieve both, we can write a query:

query RetrieveHelloWorld {
	hello
	world
}

And when submitted, it will run both queries and return both in the response:

{
	“data”: {
		“hello”: “HELLO”,
		“world”: “WORLD”
	}
}

Awesome! Both queries and data returned. This is amazing. Think on load of an application, you could get the authenticated user and any initial data you would like instead of having to make each request separately making multiple roundtrips to the server.

Adding Queries in Apollo Angular

Apollo provides a couple ways we can query for data in Angular. Let’s explore them now.

The first way to run a query is by utilizing the Apollo service provided by the apollo-angular library. Just like any service provider, inject the Apollo service in the constructor of your component and pass the graphql query in to the .watchQuery method on the Apollo service. The valueChanges of this method return an Observable of our data that we can interact with. Let’s create a component to query for a list of CalendarEvent records:

//src/app/calendar-event/containers/calendar-events-list-container/calendar-events-list-container.component.ts
import { Component, OnInit } from '@angular/core';
import { Apollo } from 'apollo-angular';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import gql from 'graphql-tag';

import { CalendarEvent } from '../../models;

const calendarEventsListQuery = gql`
	query CalendarEventsQuery {
		events {
			_id
			name
			description
			eventStart
			startTime
			eventEnd
			endTime
		}
	}
`;

@Component({
	selector: ‘app-calendar-events-list-container’,
	templateUrl:./calendar-events-list-container.component.html’,
	styleUrls: [./calendar-events-list-container.component.scss’]
})
export class CalendarEventsListContainerComponent implements OnInit {
	calendarEvents$: Observable<CalendarEvent[]>;

	constructor(private readonly apollo: Apollo) {}

	ngOnInit() {
		// use injected apollo service to run query
		// response JSON returns as { data: { events: [] } }
		// to get the calendarEvents$, map to the data.events
		this.calendarEvents$ = this.apollo.
			.watchQuery({ query: calendarEventsListQuery })
			.valueChanges.pipe(map(({ data }) => data.events));
	}
}

Super simple. The valueChanges getter on the Apollo service returns an observable of our data. We can use this with the map operator to select the events from the returned JSON data. We can also pass variables into our queries by passing an object to the variables prop in the .watchQuery method. If we want to pass in a variable like first to get the first # of results that match the query, update the query to include the variable:

const calendarEventsListQuery = gql`
  query CalendarEventsQuery($first: Int!) {
    events(first: $first) {
      _id
      name
      description
      eventStart
      startTime
      eventEnd
      endTime
    }
  }
`;

And then update the call to the Apollo service:

const variables = { first: 10 }
this.calendarEvents$ = this.apollo.
  .watchQuery({ query: calendarEventsListQuery, variables })
	.valueChanges.pipe(map(({ data }) => data.events));

Check out the Query Apollo Angular docs here for more info.

The other, and my preferred, way to query for data is to create a custom service provider class that extends Query and defines our query. Query is a type exposed by the apollo-angular library and accepts two generic types: the response type, and a type representing any variables to pass to the query. Let’s move our Calendar Events list query from above and build out a query service for it instead:

import { Injectable } from '@angular/core';
import { Query } from 'apollo-angular';
import gql from 'graphql-tag';

import { CalendarEvent } from '../../models;

type CalendarEventsListResponse = {
	events: CalendarEvent[];
}

@Injectable()
export class CalendarEventsQuery extends Query<CalendarEventsListResponse> {
	document = gql`
		query CalendarEventsQuery {
			events {
				_id
				name
				description
				eventStart
				startTime
				eventEnd
				endTime
			}
		}
	`;
}

Because this is a service provider, and is annotated Injectable(), we need to provide it to our module to make it available for dependency injection. To achieve this, add it to the providers prop on the NgModule

// imports
import { CalendarEventsQuery } from./graphql’;

@NgModule({
	// declarations, imports, etc
	providers: [
		// other services
		CalendarEventsQuery
	]
})
export class CalendarEventModule {}

And we can now update our container component:

//src/app/calendar-event/containers/calendar-events-list-container/calendar-events-list-container.component.ts
import { Component, OnInit } from '@angular/core';
import { Apollo } from 'apollo-angular';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import gql from 'graphql-tag';

import { CalendarEventsQuery } from '../../graphql;

@Component({
	selector: ‘app-calendar-events-list-container’,
	templateUrl:./calendar-events-list-container.component.html’,
	styleUrls: [./calendar-events-list-container.component.scss’]
})
export class CalendarEventsListContainerComponent implements OnInit {
	calendarEvents$: Observable<CalendarEvent[]>;

	constructor(private readonly calendarEventsQuery: CalendarEventsQuery) {}

	ngOnInit() {
		// use injected apollo service to run query
		// response JSON returns as { data: { events: [] } }
		// to get the calendarEvents$, map to the data.events
		this.calendarEvents$ = this.calendarEventsQuery.watch().valueChanges.pipe(map({ data }) => data.events));
	}
}

And, just like with the first option, we can add variables as well. First we need to update our CalendarEventsQuery service class:

import { Injectable } from '@angular/core';
import { Query } from 'apollo-angular';
import gql from 'graphql-tag';

import { CalendarEvent } from '../../models;

type CalendarEventsListResponse = {
	events: CalendarEvent[];
}

export type CalendarEventsListVariables = {
	first: number;
}

@Injectable()
export class CalendarEventsQuery extends Query<CalendarEventsListResponse, CalendarEventsListVariables> {
	document = gql`
		query CalendarEventsQuery($first: Int!) {
			events(first: $first) {
				_id
				name
				description
				eventStart
				startTime
				eventEnd
				endTime
			}
		}
	`;
}

And now we pass the variables into the watch method on our injected query instance in our component:

//src/app/calendar-event/containers/calendar-events-list-container/calendar-events-list-container.component.ts
import { Component, OnInit } from '@angular/core';
import { Apollo } from 'apollo-angular';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import gql from 'graphql-tag';

import { CalendarEventsQuery } from '../../graphql;

@Component({
	selector: ‘app-calendar-events-list-container’,
	templateUrl:./calendar-events-list-container.component.html’,
	styleUrls: [./calendar-events-list-container.component.scss’]
})
export class CalendarEventsListContainerComponent implements OnInit {
	calendarEvents$: Observable<CalendarEvent[]>;

	constructor(private readonly calendarEventsQuery: CalendarEventsQuery) {}

	ngOnInit() {
		// use injected apollo service to run query
		// response JSON returns as { data: { events: [] } }
		// to get the calendarEvents$, map to the data.events
		this.calendarEvents$ = this.calendarEventsQuery.watch({ first: 10  }).valueChanges.pipe(map({ data }) => data.events));
	}
}

Very similar to what we had in the first option. The advantage here is that, because we separated out the query into its own service class, we can inject it into any component to connect and run our query. It makes the query reusable in case multiple components wanted to consume the data. For more information about this method of querying for data, check out the query service docs here.

Creating a Calendar Event

Queries are all about fetching data. The convention is that a Query should not change any data in a data platform. However, creating, updating and deleting data is almost always a requirement. GraphQL handles this with the concept of a Mutation. A mutation is really similar in structure to a query: you pass in the name of the mutation, any necessary variables, and the data you want returned. The key differences are that a mutation request starts with the keyword mutation and if we need to pass input to the mutation (like the object to create/update) that object needs to be an input type object.

GraphQL Mutations

Let’s go through a quick example from the docs First let’s define our input type which we will pass as a variable to our mutation:

input ReviewInput {
	stars: Int!
	commentary: String
}

The key here is that our type of ReviewInput is input. The mutation will fail if this not declared as input. This is one of the biggest complaints against GraphQL as it can lead to duplicated types. I personally find this helpful as it helps me to be very specific about the shape of my input and what I want to require to create or update a data object. And now to write our mutation:

mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
	createReview(episode: $ep, review: $review) {
		start
		commentary
	}
}

This should look very familiar. As I mentioned, the shape of the request is very similar to a Query. Key difference being the mutation keyword to start the request.

Adding Mutations in Apollo Angular

Now for the angular part. Just like queries above, we can use the Apollo service and then dispatch a mutation with the mutate({ mutation }) method. But instead, let’s use a Mutation service provider; code-reusability and all. The Mutation class we will extend is very similar to the Query class, it accepts a couple generics: the return type from the mutation, and a type that represents the variables to pass to the mutation.

import { Injectable } from@angular/core’;
import { Mutation } from ‘apollo-angular’;
import gql from ‘graphql-tag’;

import { CalendarEvent } from../../models’;

type CreateCalendarEventResponse = {
	createEvent: CalendarEvent;
};

type CreateCalendarEventVariables = {
	event: CalendarEvent;
};

@Injectable()
export class CreateCalendarEventMutation extends Mutation<CreateCalendarEventResponse, CreateCalendarEventVariables> {
	document = gql`
		mutation CreateCalendarEvent($event: CalendarEventInput!) {
			createEvent(event: $event) {
				_id
				status
				name
				description
				eventStart
				startTime
				eventEnd
				endTime
				location
				recurring
				guests {
					_id
					name
					email
					attending
				}
			}
		}
	`;
}

Super straightforward. Create a mutation request object where we can pass in our $event variable that will be provided through the CreateCalendarEventVariables type, give it the name of our mutation - createEvent - reference the variable, and list the fields we want returned.

Just like the query, since this is annotated as @Injectable(), we need to provide it to our module, open the calender-event.module.ts again and lets add it to our providers prop:

// imports
import { CalendarEventsQuery, CreateCalendarEventMutation } from./graphql’;

@NgModule({
	// declarations, imports, etc
	providers: [
		// other services
		CalendarEventsQuery,
		CreateCalendarEventMutation
	]
})
export class CalendarEventModule {}

Create a container component that will inject the CreateCalendarEventMutation service. It exposes a mutate method where we will pass our variables: { event: CalendarEvent } and then subscribe to the result. To keep it simple, on subscribe, we will route to the CalendarEventDetails component which will display the details of the newly created CalendarEvent:

import { Component } from@angular/core’;
import { Router } from@angular/router’;

import { CalendarEvent } from../../models’;
import { CreateCalendarEventMutation } from../../graphql’;

@Component({
	selector: ‘app-create-calendar-event-container’,
	templateUrl:./create-calendar-event-container.component.html’,
	styleUrls: [./create-calendar-event-container.component.scss’]
})
export class CreateCalendarEventContainerComponent {
	constructor(private readonly createCalendarEventMutation: CreateCalendarEventMutation, private readonly router: Router) {}

	createCalendarEvent(event: CalendarEvent) {
		this.createCalendarEventMutation.mutate({ event }).subscribe(({ created }) => {
			// created is the data.createdEvent response from the mutation
			// the type is CalendarEvent
			// route to the details page
			this.router.navigate([/calendar-events/details’, created._id]);
		}
	}
}

When the createCalendarEvent function is invoked with the event data, like from the user entering the data from a form, we use the injected CreateCalendarEventMutation instance to dispatch our mutation with the CalenderEvent. When it completes, we subscribe to the results which should contain the created CalendarEvent data and navigate to the event details page.

Conclusion

There is a lot more to the apollo angular library. It helps a lot with the abstraction of connecting to a GraphQL API, handling data caching, and providing convenience classes to perform our queries and mutations.

This article only covers a couple quick examples of queries and mutations. Check out the repo for more, as well as the forms to enter and save CalendarEvent records. I hope you found this article helpful, thanks for reading along.

References

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 Custom Angular Attribute Directives cover image

A Guide to Custom Angular Attribute Directives

When working inside of Angular applications you may have noticed special attributes such as NgClass, NgStyle and NgModel. These are special attributes that you can add to elements and components that are known as attribute directives. In this article, I will cover how these attributes are created and show a couple of examples. What are Attribute Directives? Angular directives are special constructs that allow modification of HTML elements and components. Attribute directives are also applied through attributes, hence the name. There exist other types of directives such as structural directives as well, but we’re just going to focus on attribute directives. If you’ve used Angular before then you have almost certainly used a couple of the attribute directives I mentioned earlier before. You are not limited to just the built-in directives though. Angular allows you to create your own! Creating Attribute Directives Directives can be created using code generation via the ng CLI tool. ` ng generate directive ` This will create a file to house your directive and also an accompanying test file as well. The contents of the directive are very barebones to start with. Let’s take a look. ` import { Directive } from '@angular/core'; @Directive({ selector: '[appExample]', }) export class ExampleDirective { constructor() {} } ` You will see here that directives are created using a @Directive decorator. The selector in this case is the name of the attribute as it is intended to be used in your templates. The square brackets around the name make it an attribute selector, which is what we want for a custom attribute directive. I would also recommend that a prefix is always used for directive names to minimize the risk of conflicts. It should also go without saying to avoid using the ng prefix for custom directives to avoid confusion. Now, let’s go over the lifecycle of a directive. The constructor is called with a reference to the ElementRef that the directive was bound to. You can do any initialization here if needed. This element reference is dependency injected, and will be available outside the constructor as well. You can also set up @HostListener handlers if you need to add functionality that runs in response to user interaction with the element or component, and @Input properties if you need to pass data to the directive. Click Away Directive One useful directive that doesn’t come standard is a click away directive. This is one that I have used before in my projects, and is very easy to understand. This directive uses host listeners to listen for user input, and determine whether the element that directive is attached to should be visible or not after the click event occurs. ` @Directive({ selector: '[appClickAway]', }) export class ClickAwayDirective { @Output() onClickAway: EventEmitter = new EventEmitter(); constructor(private elementRef: ElementRef) {} @HostListener('document:click', ['$event']) onClick(event: PointerEvent): void { if (!this.elementRef.nativeElement.contains(event.target)) { this.onClickAway.emit(event); } } } ` There are a few new things in this directive we’ll briefly go over. The first thing is the event emitter output onClickAway. A generic directive isn’t going to know how to handle click away behavior by itself as this will change based on your use case when using the directive. To solve this issue, we make the directive emit an event that the user of the directive can listen for. The other part is the click handler. We use @HostListener to attach a click handler so we can run our click away logic whenever clicks are done. The one interesting thing about this directive is that it listens to all click events since we’ve specified ‘document’ in the first parameter. The reason for this is because we care about listening for clicking anything that isn’t the element or component that the directive is attached to. If we didn’t do this, then the event handler would only fire when clicking on the component the directive is attached to, which defeats the purpose of a click away handler. Once we’ve determined the element was not clicked, we emit the aforementioned event. Using this directive makes it trivial to implement click away functionality for both modals and context menus alike. If we have a custom dialog component we could hook it up like this: ` Dialog Box This is a paragraph with content! ` If you want to see this directive in action, then you can find it in our blog demos repo here. Drag and Drop Directive Another useful directive is one that assists with drag and drop operations. The following directive makes elements draggable, and executes a function with a reference to the location where the element was dragged to. ` @Directive({ selector: '[appDragDrop]', }) export class DragDropDirective implements OnInit, OnDestroy { @Output() onDragDrop: EventEmitter = new EventEmitter(); mouseDown$ = new Subject(); mouseUp$ = new Subject(); destroy$ = new Subject(); constructor(private elementRef: ElementRef) {} ngOnInit(): void { this.mouseDown$ .pipe(takeUntil(this.destroy$)) .pipe(exhaustMap(() => this.mouseUp$.pipe(take(1)))) .subscribe((event) => { if ( event.target && event.target instanceof Element && !this.elementRef.nativeElement.contains(event.target) ) { this.onDragDrop.emit(event); } }); } ngOnDestroy(): void { this.destroy$.next(null); this.destroy$.complete(); } @HostListener('mousedown', ['$event']) onMouseDown(event: MouseEvent): void { this.mouseDown$.next(event); } @HostListener('document:mouseup', ['$event']) onMouseUp(event: MouseEvent): void { this.mouseUp$.next(event); } } ` Just like the previous directive example an event emitter is used so the user of the directive can associate custom functionality with it. RxJs is also utilized for the drag and drop detection. This directive uses the exhaustMap function to create an observable that emits both after a mouse down, and finally a mouse up is done. With that observable, we can subscribe to it and call the drag and drop callback so long as the element that’s dragged on isn’t the component itself. Note how the mouse down event is local to the component while the mouse up event is attached to the document. For mouse down, this is done since we only want the start of the dragging to be initiated from clicking the component itself. The mouse up must listen to the document since the dragging has to end on something that isn’t the component that we’re dragging. Just like the previous directive, we simply need to reference the attribute and register an event handler. ` Drag me over something! ` Conclusion In this article, we have learned how to write our own custom attribute directives and demonstrated a couple of practical examples of directives you might use or encounter in the real world. I hope you found this introduction to directives useful, and that it helps you with writing your own directives in the future! You can find the examples shown here in our blog demos repository if you want to use them yourself....

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