DEV Community

Lucas Paganini
Lucas Paganini

Posted on

What's new in Angular 14?

Discover what changed!

Introduction

Angular 14 was recently released with a bunch of exciting features. And today, I'll present them to you. They are:

  1. Standalone components
  2. Route Providers
  3. ENVIRONMENT_INITIALIZER
  4. Typed Forms
  5. The inject() Function
  6. Setting the Page title from the route
  7. Autocomplete in the Angular CLI
  8. Optional injectors in Embedded Views

So let's get started!

Standalone components

For most people, the most significant change in this version is the possibility of creating components without @NgModules! Yeah, you got that right.

Image description

Before Angular 14

If you are a bit lost, let me show you a folder structure from a classic Angular component:

home 
| --home.component.html
|--home.component.css
|--home.component.ts
|--home.module.ts;
Enter fullscreen mode Exit fullscreen mode

There, we have an .html file for the template, a .css file for the styles, a .ts file for the component, and another .ts file for the @NgModule. This last file imports the dependencies of our component, declares the component, and can also define some providers.

A new possibility with Angular 14

In Angular 14, we can do all that directly in the component, without needing an @NgModule.

home 
|--home.component.html 
|--home.component.css 
|--home.component.ts;
Enter fullscreen mode Exit fullscreen mode

To do that, we just need to set the standalone property in our component to true.

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss'],
  standalone: true,
})
Enter fullscreen mode Exit fullscreen mode

Be aware!

Be aware:

  1. Standalone components are not the new way of declaring components! They are another way of declaring components. The classic way of defining @NgModules will not be deprecated.

Well, I can't really promise that it won't be deprecated. But yeah, so far, there are no intentions of deprecating @NgModules.

As a matter of fact. I will still use @NgModules instead of standalone components because I like the isolation that @NgModules provides.

  1. Don’t migrate your entire application to standalone components yet! Standalone components are very recent, and it will take us a while to create conventions and define best practices. I recommend waiting a bit longer before jumping ship.

Route Providers

But hey, if we drop the @NgModule and use standalone components, how can we set provider by route, like we used to do with @NgModules?

To address that, Angular added route providers. So, basically, route definitions now have a property called providers. Allowing us to provide values only to the modules and components rendered by that route.

const NAME = new InjectionToken<string>('token');

export const routes: Routes = [
  {
    path: '',
    component: HomeComponent,
    providers: [
      {
        provide: NAME,
        useValue: 'foo'
      }
    ]
  }
];
Enter fullscreen mode Exit fullscreen mode

ENVIRONMENT_INITIALIZER

Another thing we used to do with NgModules was run a setup script when a lazy loaded module was initialized. In other words, the constructor of a lazy loaded module would only run when the module was initialized, and some developers take advantage of that to run some kind of setup.

export class HomeModule {
  constructor() {
    console.log('You can run something here');
  }
}
Enter fullscreen mode Exit fullscreen mode

How can we do that with standalone components? The solution is to use the ENVIRONMENT_INITIALIZER token.

const routes: Routes = [
  {
    path: '',
    pathMatch: 'full',
      component: HomeComponent
    providers:[
      {
        provide: ENVIRONMENT_INITIALIZER,
        multi: true,
        useValue: () => console.log("You can run something right here too")
      }
    ]
  }
]
Enter fullscreen mode Exit fullscreen mode

This token allows us to provide a setup function that will run before the environment is initialized.

If we want it to run when we navigate to a route, we can provide this token using route providers.

This has the same effect as our previous solution using NgModule constructors and has the added benefit of being more explicit.

Typed Forms

Another long-awaited feature of Angular 14 is typed forms.

Before version 14, form values were typed as any. This means that we lose all the awesome type-safety of TypeScript.

  const control = new FormControl(""),
    control.value
    //=> control.value: any
Enter fullscreen mode Exit fullscreen mode

By the way, there is a type-safe way of saying that you don't know the type of something. If you're interested in that, check out this one minute video explaining the differences between any and unknown.

Anyway, we don't have that problem anymore. Because Angular 14 has strict types for forms. So if a FormControl deals with a string, the value will be typed as a string instead of any.

const control = new FormControl(""),
control.value
//=> control.value: string | null
Enter fullscreen mode Exit fullscreen mode

The inject() Function

Now, this is the most interesting feature to me.

Angular 14 introduces the inject() function. And it looks very much like React hooks. It gives us a universe of possibilities to reuse our code, the most relevant being that we can now create reusable functions which use dependency injection internally.

export function getPosts() {
  const http = inject(HttpClient);
  return http.get('/api/getPosts');
}

export class HomeComponent {
  readonly posts = getPosts();
}
Enter fullscreen mode Exit fullscreen mode

If you're as interested n functional programming as I am, you know that means a lot! But as Uncle Ben has once said:

"With great power comes great responsibility”

This creates implicit dependencies in your functions. I expect my function dependencies to be explicitly declared in the function arguments. So if I see a function with no arguments, I imagine it to have no dependencies. But now, we can create functions that seem pure but inject a lot of dependencies internally, making them harder to test and more coupled to the Angular framework.

A good solution is to follow the same convention that we have on React, which is to prefix those functions with "use". So that way, you can easily identify functions that use inject() internally.

export function useGetPosts() {
  const http = inject(HttpClient);
  return http.get('/api/getPosts');
}

export class HomeComponent {
  readonly posts = useGetPosts();
}
Enter fullscreen mode Exit fullscreen mode

Set Page title from the route

One common task in web applications is changing the page title on each route.

In Angular, this used to be a very manual process, but now, we can simply define the page title in the route definition.

export const routes: Routes = [
  {
    path: '',
    component: HomeComponent,
    title: 'Home',
    pathMatch: 'full'
  }
];
Enter fullscreen mode Exit fullscreen mode

But I know what you're thinking:

"Lucas, I can't set the title in the route definition because my title is not static, it depends on something I get from my API".

Don't worry. Angular 14 got you covered.

If you need to customize the title dynamically, you can do so by extending the TitleStrategy class from the @angular/router, and providing your new, custom title strategy instead of the default one.

@Injectable({ providedIn: 'root' })
export class PageTitleStrategy extends TitleStrategy {
  constructor(@Inject(Title) private title: Title) {
    super();
  }

  override updateTitle(routerState: RouterStateSnapshot) {
    const title = this.buildTitle(routerState);
    if (title !== undefined) {
      this.title.setTitle(`CompanyName | ${title}`);
    } else {
      this.title.setTitle(`CompanyName | Page without title`);
    }
  }
}

export const routes: Routes = [
  {
    path: '',
    component: HomeComponent,
    title: 'Home',
    pathMatch: 'full'
  }
];

bootstrapApplication(AppComponent, {
  providers: [
    importProvidersFrom(RouterModule.forRoot(routes)),
    { provide: TitleStrategy, useClass: PageTitleStrategy }
  ]
});
Enter fullscreen mode Exit fullscreen mode

Angular CLI Autocomplete

Another nice little thing is that we now have autocomplete in the Angular CLI.

Just type ng on your terminal and press TAB to let the Angular CLI either autocomplete your command or give you some help.

> ng se
            //=> If you press TAB here, the CLI will complete

> ng serve
Enter fullscreen mode Exit fullscreen mode
> ng
        //=> If you press TAB here, the CLI will show a list of commands

> ng
    add           -- Adds support for an external library to your project.
    analytics     -- Configures the gathering of Angular CLI usage metrics.
    build         -- Compiles an Angular application or library into an outpu...
    cache         -- Configure persistent disk cache and retrieve cache sta...
    ...
Enter fullscreen mode Exit fullscreen mode

Activating the CLI autocomplete

To activate it, you must run the ng completion command and confirm that you want to enable the autocomplete by typing Yes.

> ng completion

? Would you like to enable autocompletion? This will set up your terminal so pressing
TAB while typing Angular CLI commands will show possible options and autocomplete arguments.
(Enabling autocompletion will modify configuration files in your home directory.)

> Yes
Enter fullscreen mode Exit fullscreen mode

Finally, just restart your terminal to have autocomplete enabled.

Optional injectors in Embedded Views

Last but not least, we can now pass optional injectors in embedded views. In the past, we had to use other APIs to do that, such as the ngComponentOutlet.

@Directive({ selector: '[foo]' })
export class FooDirective implements OnInit {
  constructor(
    private vcr: ViewContainerRef,
    private templateRef: TemplateRef<unknown>
  ) {}

  ngOnInit(): void {
    this.vcr.createEmbeddedView(
      this.templateRef,
      {}, // context
      {
        injector: Injector.create({
          // pass an injector :)
          providers: [
            {
              provide: 'foo',
              useValue: 'bar'
            }
          ]
        })
      }
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Don't Stop Here

These were the most relevant changes in Angular 14, in my opinion. If you want to see all changes, you can do so by checking out the references in the description.

If you want to dive deeper into the Angular framework, consider subscribing to our newsletter at lucaspaganini.com. It's spam-free. We keep the emails few and valuable.

And if your company is looking for remote web developers, consider contacting my team and me. You can do so at lucaspaganini.com.

As always, references are in the description. Like. Subscribe. Have a great day. And I will see you in the next one.

– Lucas

References

  1. Angular 14 - Academind on Youtube
  2. O que há de novo no revolucionário Angular 14 - Andrew Rosário on Medium
  3. Novidades no Angular v14 - Vida Fullstack
  4. What is new in Angular 14? - Nevzatopcu on Medium
  5. Angular v14 is now available! - Angular Blog on Medium
  6. What’s New in Angular v14 - Netanel Basal
  7. Unleash the Power of DI Functions in Angular - Netanel Basal
  8. Getting to know the ENVIRONMENT_INITIALIZER Injection Token in Angular - Netanel Basal
  9. Typed Reactive Forms in Angular — No Longer a Type Dream - Netanel Basal
  10. Handling Page Titles in Angular - Netanel Basal
  11. Angular Standalone Components: Welcome to a World Without NgModule - Netanel Basal
  12. Pass an Injector to Embedded Views - Netanel Basal
  13. Setting page title from the route module - Brandon Roberts
  14. Standalone components with Custom Title Strategy in Angular 14

Oldest comments (1)

Collapse
 
libnikim profile image
LibniKim

We can import the required modules in the component itself. prière pour séparer deux personnes en 24h