From Angular 14 to Angular 16
Tags: cleancode, overview, angular, features
Published: 2023-05-25 10:18:50

---
title: From Angular 14 to Angular 16
published: true
date: 2023-05-24 16:27:36 UTC
tags: cleancode,overview,angular,features
canonical_url: https://medium.com/@redin.gaetan/from-angular-14-to-angular-16-1804736e25d9
---

What I have changed in my coding approach

From v14, The Angular team has certainly been busy. I work with Angular from the beginning of Angular 2. And I have never seen so many features in such a short amount of time.

My last article was edited 7 months ago and it’s time to share with you how I have changed my daily coding approach since v14.

In some cases, it will be a before / after comparison with the feature to show you quickly the benefits. It will allow you to understand how to update your code with examples.

### Angular v14

#### Standalone components

**Before**

```typescript 
// my.component.ts
@Component({
  ...
})
export class MyComponent {}

// my.module.ts
@NgModule({
  declarations: [MyComponent],
  exports: [MyComponent],
  imports: [AnotherStuff]
})
export class MyModule {}
```

**Now**

```typescript
// my.component.ts
@Component({
  standalone: true,
  imports: [AnotherStuff]
  ...
})
export class MyComponent {}
```

**Advantage**

One less file to maintain and less code. Be careful, the standalone component must be imported **_._**

this can also be applied to the _AppComponent,_ you “just” have to te rewrite your main.ts as follow:

```typescript
bootstrapApplication(AppComponent, {
  ...
  importProvidersFrom(/* the imported modules in the app.module file*/),
  ...
});
```

Now there are many features you can provide without module, think to read the [docs](https://angular.io/guide/standalone-components).

#### Typed Angular Forms

So highly anticipated and at the same time, somewhat disappointing for me.

**Before** ,

we only worked with an _any_ type for all of our forms. It was a nightmare to maintain and to evolve.

```typescript
const form: FormGroup = new FormGroup({
  lastName: new FormControl(''),
  firstName: new FormControl(''),
});

form.setValue({ ... }); // no type check
form.controls.(...); // no autocompletion
form.controls.firstName.setValue(0); // no type check every thing is ok...
```

**Now** ,

all of these points are resolved but there is still a problem. I find the syntax a bit indigest.

```typescript
type FormType = { lastName: FormControl<string>; firstName: FormControl<string> };

const form: FormGroup<FormType> = new FormGroup<FormType>({
  lastName: new FormControl(''),
  firstName: new FormControl(''),
});
```

So why I find it indigest? Because our data models are not defined with Form[Control|Group|Array] types. it’s more like:

```typescript
export interface Data {
  lastName: string; // no FormControl
  firstName: string; // no FormControl
}
```

So how to use the Data model with our FormGroup? Until now, no native solution, we have to use som hack.

I choose to use an intermediate type:

```typescript
export type FormTyped<T> = {
  [Key in keyof T]: FormControl<T[Key]>;
};
```

It does not handle sub FormGroup or FormArray but in most of my cases it does the work. If you have any suggestion to improve this, don’t hezitate let me a comment.

**Advantage**

Type checking, autocompletion.

If you want / need to stay like in the old way you can use the UntypedForm[Control|Group|Array|Builder].

#### Bind to protected component members

**Before** ,

only public properties could be accessible from the HTML template. It has often been a bit frustrating for me to make some properties public just because of the template.

```typescript
@Component({
  template: `{{ myProperty }}`
})
class Component {
  public myProperty?: string;
}
```

**Now** ,

we can really choose what to publicly expose from our component:

```typescript
@Component({
  template: `{{ myProperty }}`
})
class Component {
  protected myProperty?: string;
}
```

### Angular v15

#### RouterTestingHarness

Since [RouterTestingHarness](https://angular.io/api/router/testing/RouterTestingHarness) has come, I find it easier to test routing, check guards calls and co.

```typescript
@Component({standalone: true, template: 'hello {{name}}'})
class TestCmp {
  name = 'world';
}

it('navigates to routed component', async () => {
  TestBed.configureTestingModule({
    providers: [provideRouter([{path: '', component: TestCmp}])],
  });

  const harness = await RouterTestingHarness.create();
  const activatedComponent = await harness.navigateByUrl('/', TestCmp);
  expect(activatedComponent).toBeInstanceOf(TestCmp);
});
```

**Advantage**

I use a specific routes.spec.ts file for only testing routing. It makes tests more readable.

Before, I often have seen projects without routing tests. Now, there is no excuse.

#### Simplify routes files and use Router API

I don’t want to rewrite an app-routing.module.ts… I know you know what I mean. But here how to do now, bonus it’s a lazy load example.

```typescript
// app.routes.ts
export const appRoutes: Routes = [{
  path: 'lazy',
  loadChildren: () => import('./lazy/lazy.routes')
    .then(routes => routes.lazyRoutes)
}];

// lazy.routes.ts
import {Routes} from '@angular/router';

import {LazyComponent} from './lazy.component';

export const lazyRoutes: Routes = [{path: '', component: LazyComponent}];

// main.tous
bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(appRoutes)
  ]
});
```

#### Directive composition API

I have written two articles about it:

- [Angular 15: hostDirectives](https://dev.to/gaetanrdn/angular-15-hostdirectives-287b)
- [Angular 15: hostDirectives + InjectionToken](https://dev.to/gaetanrdn/angular-15-hostdirectives-injectiontoken-kk0)

#### Functional router guards

**Before** ,

we have to define our guard in a specific file like below:

```typescript
@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  constructor(private authService: AuthService, private router: Router){};
  canActivate(...): boolean | UrlTree {
    let isLoggedIn = this.authService.isAuthenticated();

    if (isLoggedIn) return true

    return this.router.createUrlTree(['/contact']);
  }
```

**Now** ,

you simply can do it in your routes file:

```typescript
export const routes = [{
...
  canActivate: [
    (): boolean | UrlTree => {
      const authService = inject(AuthService);

      if (authService.isAuthenticated()) return true;

      return inject(Router).createUrlTree(['/contact']);
    },
  ],
}];
```

Or you can extract it into a plain function, it depends on your needs:

```typescript
function isAuthenticated(): boolean | UrlTree {
  const authService = inject(AuthService);

  if (authService.isAuthenticated()) return true;

  return inject(Router).createUrlTree(['/contact']);
};

export const routes = [{
...
  canActivate: [isAuthenticated],
}];
```

**Advantage**

More readable, less code. Just what we love.

#### Self-closing tags

It allows to streamline the HTML code.

```html
<!-- before -->
<app-my-component></app-my-component>

<!-- now -->
<app-my-component/>
```

### Angular v16

#### Required inputs

How to handle required inputs before Angular v16? You will find the answer in one of my previous article:

[How to Handle Required Inputs in Angular](https://dev.to/gaetanrdn/how-to-handle-required-inputs-in-angular-584o)

Since v16, no hack is needed, even custom decorators are deprecated, you just have to do this:

```typescript
@Component({ ... })
class MyComponent {
  @Input({ required: true }) myProp!: string;
}
```

#### Passing router data as component inputs

I think this is one of my favourite feature. Why? Because we don’t need to have the ActivatedRoute dependency in our component, we don’t need to handle static or resolved route data, path parameters, matrix parameters, and query parameters no more.

Before

```typescript
private readonly activatedRoute = inject(ActivatedRoute);

private hero$: Observable<Hero> = this.activatedRoute.queryParams.pipe(
  switchMap(({ heroId }: Params) => this.service.getHero(heroId)),
);
```

**Now**

In your component you just have to define the prop with the @Input decorator:

```typescript
@Input()
set id(heroId: string) {
  this.hero$ = this.service.getHero(heroId);
}
```

Don’t forget to add this in your routing config:

```typescript
providers: [
  provideRouter(appRoutes, withComponentInputBinding()),
]
```

**Advantage**

Your component is more generic and can be used in all cases.

### Conclusion

I don’t talk about signal here. It’s a great feature but for me it will be very interesting from the v16.1 and the add of the transform feature for Inputs.

As we can see, many features to make Angular easier, more accessible and more readable. But yes, I’m like you I’m waiting for all the signals feature. It seems we have to wait until the v17.

If you need more details about a feature let me a comment, don’t hezitate.

Thanks for reading.