Telerik blogs

Angular 15 brings two amazing features out of developer preview: Standalone APIs (Components, Pipes, Directives) and the Image Directive! Take a closer look!

Happy Fall, to those of you on the same side of the globe as me. And HAPPY ANGULAR VERSION 15 to all!

Alyssa holds up 1 finger and her son holds up 5 fingers

Check out the official release Angular Blog Release written by the lovely Minko and the release notes detailed in the Angular CHANGELOG or the much easier to read Breaking Changes in the docs! Some of my favorite highlights are two amazing features out of developer preview: Standalone APIs (i.e., Components, Pipes, Directives) and the Image Directive!

Updates include but are not limited to:

Getting Updated

First thing is first, though. We need to update in order to access the new goods! The official Angular Update Guide is always a great place to start. But the meat of the update is this command: ng update @angular/core@15 @angular/cli@15.

Now this girl always runs into new and exciting npm issues, so I am sure it was just a fluke. That said, if you do run into some as well for your Angular apps, remember, deleting your node_modules folder and your package-lock.json and re-running npm install after updating dependencies can get you out of many-a-pickle.

For this latest version, you can remove the line in your tsconfig about enableIvy (since Ivy is the only rendering engine option for version 15 of Angular).

Breaking Changes

This particular breaking change did catch me: https://angular.io/guide/update-to-version-15#the-title-property-is-required-on-activatedroutesnapshot

Object literal may only specify known properties, and 'text' does not exist in type 'Route'.

Simply changing “text” to “title” in my routes fixed this:

src/app/app-routing.module.ts [old]

const routes: Routes = [
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  { path: 'detail/:id', component: HeroDetailComponent },
  { path: "dashboard", component: DashboardComponent, text: "Dashboard" },
  { path: "heroes", component: HeroesComponent, text: "Heroes" },
];

src/app/app-routing.module.ts

const routes: Routes = [
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  { path: 'detail/:id', component: HeroDetailComponent },
  { path: "dashboard", component: DashboardComponent, title: "Dashboard" },
  { path: "heroes", component: HeroesComponent, title: "Heroes" },
];

Standalone APIs

As touched on above, standalone APIs are now out of developer preview and ready for mainstream use with Angular version 15. For getting started with standalone components, you should check out Jessica Janiuk’s video here in the docs: https://angular.io/guide/standalone-components#creating-standalone-components.

There are also a couple articles and repos I recommend written by fellow GDEs:

Manfred Steyer [Articles]

And the web component article is at least indirectly about Standalone APIs:

Marko Stanimirović [Article]
(This article is before the standalone NgRx and Router APIs were released.)

Dhananjay Kumar [Article]

Q Ray @tipster22 [Repo]

Netanel Basal @NetanelBasal [Repo]

Lonli-Lokli @sirlonlilokli [Repo]

Follow along with Mike Brocchi’s recent twitter chain for emerging repos with standalone APIs in use: https://twitter.com/Brocco/status/1593234105613058050

Giving Standalone a Try

All of this standalone hype has got me itching to try this feature. So, I pulled up my go-to-demo (Tour of Ponies) and decided to give it a try. You can use the CLI to generate a standalone component: ng -g component standalone-card --``standalone.

CLI to generate standalone component

I want to replace my hero-card component with this standalone-component and see if there is any difference in practical usage. So far, in generating, the only difference (other than not being included in any module) are these two lines in the component’s TypeScript file:

standalone-true, imports: CommonModule

Jessica mentions it in her tutorial, but the CommonModule being imported here is what gives our standalone component access to things like ngIf, ngFor and other directives. After creating the standalone component, we still need include it in our app module so our app knows about it. Note, it goes in the imports, not the declarations.

in imports-appmodule, add standalone component

Now I replaced hero-card with this standalone component card and even copied over the markup/logic from our hero card. These components are identical now, except one is module-less. However, when we serve up our app, you can see by not only the UI but the console errors, our standalone component doesn’t know about the Kendo UI library elements we were using in hero-card.

standalone card component in vscode throwing errors about unknown kendo ui bits

standalone card component in browser throwing errors about unknown kendo ui

Similar to feature modules, you need to include your dependencies inside the component’s TypeScript file, like I did below for the Kendo UI Layout module. (This is the npm package that includes the desired kendo-card.)

import {layoutmodule} from 'progresss/kendo-angular-layout'

However, the hero-card also uses a custom pipe I made called ellipsis-pipe:

<p kendoCardSubtitle *ngIf="hero.residence" title="{{ hero.residence }}">{{ hero.alias }} from {{ hero.residence | ellipsis:25 }}</p>

In order to use this pipe, we need to export it like so with its own module:

export class PipeModule{}

Then we can import it into our standalone card component:

import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Hero } from '../hero';
import { LayoutModule } from '@progress/kendo-angular-layout';
import { AppRoutingModule } from '../app-routing.module';
import { PipesModule } from '../pipes/pipes.module';
import { EllipsisPipe } from '../pipes/ellipsis.pipe';
@Component({
  selector: 'standalone-card',
  standalone: true,
  imports: [CommonModule, LayoutModule, AppRoutingModule, PipesModule],
  templateUrl: './standalone-card.component.html',
  styleUrls: ['./standalone-card.component.css']
})
export class StandaloneCardComponent {
  @Input() hero: Hero;
}

HOWEVER, the standalone API not only allows you to create components without modules, but directives and pipes as well. So it seems silly to create a card component without a module, only to need to create a module for our custom pipe. We’d end up module-neutral and we are trying to reduce the number of modules in our app! So let’s make our pipe standalone while still exporting it for use in our standalone component.

Making Our Angular Pipe Standalone

This was by far the easiest thing I have ever done in Angular. 🔥 I added standalone: true to the pipe declaration and then wherever I was importing the pipes module (app.module.ts and standalone-card.component.ts), I instead just included the pipe itself:

me adding standalone true to the ellipsis pipe

standalone-card.component.ts

import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Hero } from '../hero';
import { LayoutModule } from '@progress/kendo-angular-layout';
import { AppRoutingModule } from '../app-routing.module';
import { EllipsisPipe } from '../pipes/ellipsis.pipe';
@Component({
  selector: 'standalone-card',
  standalone: true,
  imports: [CommonModule, LayoutModule, AppRoutingModule, EllipsisPipe],
  templateUrl: './standalone-card.component.html',
  styleUrls: ['./standalone-card.component.css']
})
export class StandaloneCardComponent {
  @Input() hero: Hero;
}

We have imported things like commonModule, LayoutModule and AppRoutingModule because standalone components manage their own dependencies. This might seem like an extra step, however, the explicit-ness inline is quite eye-opening to what you’re actually using per component. I think it’s nice to have it included here, versus a global app module or even feature module. It’s more verbose on the component level, but less steps overall.

And, as easy as that, all places once using my hero-card component are now using my standalone-card.

tour of heroes top heroes page now using standalone card

“But Alyssa,” you might say to yourself, “What was it all for? We’ve added zero new functionality.” And alas, you are correct. But the door that module-less things opens is a component-centered and exciting one. It makes the API face of our language that much simpler and attainable to beginners and that much easier for maintainers. I personally am excited to walk through it.

Directive Composition API

The next step in this module-less evolution is the directive composition API, which opens up new code reuse strategies. The composing directives with this API only works with standalone directives (as one would imagine) and was truly the missing piece in this module-less puzzle. 🧩

@Component({
  selector: 'navbar-anchor',
  template: 'navbar-anchor.html',
  hostDirectives: [AnchorFunc],
})
export class NavbarAnchor { }

So here I am applying the AnchorFunc directive to the host element, NavbarAnchor. (Again, to try some of this stuff out, would highly recommend Manfred’s blogs above to help guide you along with the official docs. Manfred is a 10/10 teacher.)

I did attempt to put our pipe inside the hostDirective, to avoid creating a module for it. However, I ended up crashing the ALS. So I don’t believe pipes are supported here. But it was worth a try! lol

The Angular Language Service server crashed 5 times in the last 3...

So, only use the directive composition feature with directives AND use them sparingly.

While the directive composition API offers a powerful tool for reusing common behaviors, excessive use of host directives can impact your application’s memory use. If you create components or directives that use many host directives, you may inadvertently balloon the memory used by your application. — Angular Docs


Image Directive

The image directive feature is another one I am DYING to try. So let’s try it out on this rainbow image in the header!

My Little Pony - Tour of Heroes header

First, we need to include the new image directive’s module in our app.module.ts. Do I find it odd that he isn’t standalone? Yes, yes I do. But perhaps it is too soon for such things?

including optimized image directive in our app module

Now we can use ngSrc instead of src on the rainbow img. However, this optimized image directive requires a width and height (or fill mode) to render. There are dynamic options as well as considerations for differing screen sizes along the way. I was really surprised with how fully thought-out the docs felt on this—check them out!

<img src="../assets/images/retro-mlp/my-littt-ponies-rainbow.png" alt="retro my little pony rainbow">
  <img ngSrc="../assets/images/retro-mlp/my-littt-ponies-rainbow.png" width="670" height="200" alt="retro my little pony rainbow">

2022, That’s a Wrap

As always, I am super impressed with this latest release of Angular. I love this framework and was reminded just today that it is more than the features that draw me to it. It has been and will always be the people.

I am organizing a stream for The State of JS Survey when it releases early next year. I have ever been an Angular girl, so my connections on that side of JS are naturally stronger. For this year’s stream, I wanted to mix it up and add some new faces. The person I had in mind is a very big fish and fairly high profile, so no open DMs for them. I reached out on their framework’s community discord to attempt to contact the author. I, of course, did not get a hold of this person. Not a huge deal, I knew it was a long shot.

However, what I didn’t expect was the blatant ridicule I got for even daring to speak in that discord. I was even made fun of for how little I posted in that community. It was a stark reminder about how special the Angular community really is. I know this one interaction was not representative of the community as a whole; however, I have never been made to feel this way when asking dumb questions in Angular channels.

In fact, for the above demo, when I made the Angular Language Service crash, I reached out to the Angular team about it. They not only told me that it shouldn’t be happening, but had it fixed within the hour. They also informed me, super kindly, that directive compositions were not for pipes.

I only mention this because as we wrap this year and look back, perhaps we could look forward too. At the end of the day, we are all web devs just making a living on JavaScript’s back. We should be kinder to each other, no matter our background. The way you respond to someone matters—sometimes a great deal. Let’s bring kindness into our communities this next year and make it more inclusive for all. Happy Holidays everyone, and happy Angular v15!


AlyssaNicoll
About the Author

Alyssa Nicoll

Alyssa is an Angular Developer Advocate & GDE. Her two degrees (Web Design & Development and Psychology) feed her speaking career. She has spoken at over 30 conferences internationally, specializing in motivational soft talks, enjoys gaming on Xbox and scuba diving in her spare time. Her DM is always open, come talk sometime.

Related Posts

Comments

Comments are disabled in preview mode.