DEV Community

Gaëtan Redin
Gaëtan Redin

Posted on • Originally published at Medium on

From Angular 14 to Angular 16

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

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

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

Now

// my.component.ts
@Component({
  standalone: true,
  imports: [AnotherStuff]
  ...
})
export class MyComponent {}
Enter fullscreen mode Exit fullscreen mode

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:

bootstrapApplication(AppComponent, {
  ...
  importProvidersFrom(/* the imported modules in the app.module file*/),
  ...
});
Enter fullscreen mode Exit fullscreen mode

Now there are many features you can provide without module, think to read the docs.

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.

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...
Enter fullscreen mode Exit fullscreen mode

Now ,

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

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

const form: FormGroup<FormType> = new FormGroup<FormType>({
  lastName: new FormControl(''),
  firstName: new FormControl(''),
});
Enter fullscreen mode Exit fullscreen mode

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

export interface Data {
  lastName: string; // no FormControl
  firstName: string; // no FormControl
}
Enter fullscreen mode Exit fullscreen mode

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:

export type FormTyped<T> = {
  [Key in keyof T]: FormControl<T[Key]>;
};
Enter fullscreen mode Exit fullscreen mode

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.

@Component({
  template: `{{ myProperty }}`
})
class Component {
  public myProperty?: string;
}
Enter fullscreen mode Exit fullscreen mode

Now ,

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

@Component({
  template: `{{ myProperty }}`
})
class Component {
  protected myProperty?: string;
}
Enter fullscreen mode Exit fullscreen mode

Angular v15

RouterTestingHarness

Since RouterTestingHarness has come, I find it easier to test routing, check guards calls and co.

@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);
});
Enter fullscreen mode Exit fullscreen mode

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.

// 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)
  ]
});
Enter fullscreen mode Exit fullscreen mode

Directive composition API

I have written two articles about it:

Functional router guards

Before ,

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

@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']);
  }
Enter fullscreen mode Exit fullscreen mode

Now ,

you simply can do it in your routes file:

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

      if (authService.isAuthenticated()) return true;

      return inject(Router).createUrlTree(['/contact']);
    },
  ],
}];
Enter fullscreen mode Exit fullscreen mode

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

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

  if (authService.isAuthenticated()) return true;

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

export const routes = [{
...
  canActivate: [isAuthenticated],
}];
Enter fullscreen mode Exit fullscreen mode

Advantage

More readable, less code. Just what we love.

Self-closing tags

It allows to streamline the HTML code.

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

<!-- now -->
<app-my-component/>
Enter fullscreen mode Exit fullscreen mode

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

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

@Component({ ... })
class MyComponent {
  @Input({ required: true }) myProp!: string;
}
Enter fullscreen mode Exit fullscreen mode

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

private readonly activatedRoute = inject(ActivatedRoute);

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

Now

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

@Input()
set id(heroId: string) {
  this.hero$ = this.service.getHero(heroId);
}
Enter fullscreen mode Exit fullscreen mode

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

providers: [
  provideRouter(appRoutes, withComponentInputBinding()),
]
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (2)

Collapse
 
renancferro profile image
Renan Ferro

Hello how are you?! I hope you're okay!

Interesting article! I have a tip for you and your articles!

In your code blocks you can specify what is the language of the code, try to do below like in the picture:

Image description

Collapse
 
gaetanrdn profile image
Gaëtan Redin

Thank you very much. Hmm it’s an import from medium but okay I will take that into account 👍🏼