Angular Anti-Patterns

By Tyler Korff



BugReplay Guest Blog Written by Tristan Jesse

=

Angular Anti-Patterns

I've been developing with Angular for the better part of a year now and along the way I've come across some common anti-patterns that can really hamper progress as your application scales. There are all sorts of anti-patterns, which I could go on for days about, but here I'll be talking about file structure, naming conventions, and logical structure. For newer developers, even skilled ones from time to time, falling into programming anti-patterns is easy and common.

What every developer should aim for is to follow design patterns (generally agreed upon as good practice) as much as they can to complete their task. If you aren't following good design patterns, then more often than not down the line you will run into bugs you can't track down, spaghetti code, and bloated components or modules giving you headaches. With Angular, there are quite a few rules and recommendations to be followed that will make your code easier to read, review and reuse down the line.

Structure and Single Responsibility

No matter the size of the app it is important to consider the file structure from the start. Even with a small scale app being able to navigate through your structure quickly will make life easier for you. Keep this in mind from the beginning but also be aware of the direction your app will be taking in the future. So let's take it from the top with a freshly generated project.

If you open up your `app.component.ts` file you'll find that you can get just about everything accomplished just within this file. I've just pulled this example straight from the Angular docs.

```typescript
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Component, OnInit } from '@angular/core';

class Hero {
  id: number;
  name: string;
}

@Component({
  selector: 'my-app',
  template: `
      <h1>{{title}}</h1>
      <pre>{{heroes | json}}</pre>
    `,
  styleUrls: ['app/app.component.css']
})
class AppComponent implements OnInit {
  title = 'Tour of Heroes';

  heroes: Hero[] = [];

  ngOnInit() {
    getHeroes().then(heroes => this.heroes = heroes);
  }
}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ AppComponent ],
  exports: [ AppComponent ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

platformBrowserDynamic().bootstrapModule(AppModule);

const HEROES: Hero[] = [
  {id: 1, name: 'Bombasto'},
  {id: 2, name: 'Tornado'},
  {id: 3, name: 'Magneta'},
];

function getHeroes(): Promise<Hero[]> {
  return Promise.resolve(HEROES); // TODO: get hero data from the server;
}
```

This is a fairly simple app but already just looking at this file gives me anxiety! Coding like this does not follow the single responsibility principle, so if you ever come back to this project you will have a hard time figuring out what each file's function is. Just take a look at everything going on in here: a model is declared, our app component is declared and bootstrapped, we've got our `NgModule` defined, there's a bit of mock data declared and a function is created to eventually fetch data. Anyone that comes across this after you (including you) will hate trying to decipher all of this.

These can easily be split up into several files that make things clear and concise down the line. When creating new files try to keep things organized in folders with relevant names; keeping models together or keeping services together for example. It's a good idea to keep things as flat as possible but not let that get out of hand. If you find yourself with more than 7 files in one folder it might be time to consider adding in another folder for clarity.

Naming Conventions

While following a consistent naming scheme may not seem that important (or that exciting), it can have a big impact on readability and understandability in the future. If you've been coding for awhile, many of the conventions you should already be following will apply to Angular as well but there are also some new items to take into account. With Angular, there are several different types of files and we should be consistent with their naming schema. The Angular CLI will help you with this as well as you generate new files for you project.

In an Angular project there may be services, components, guards, directives and a few other types of files. When naming a file it's best to have a descriptive name of the feature, then the type of file, followed by the extension: `feature.type.extension`. So if I wanted to create a user service, a proper filename for typescript would be `user.service.ts`. When using the Angular CLI command of `ng g service user` this will automatically generate a `user` folder and files with this naming convention followed. It's always simpler to use the CLI to help you create your files to help with structure and naming.

Deciding on the descriptive name would often trip me up however. Again I would want to create a user service but I use the command `ng g service user-service` thinking that is the best description. What is generated here however ends up looking quite sloppy. I end up with a filename `user-service.service.ts` and here is the generated code:

```typescript
import { Injectable } from '@angular/core';

@Injectable()
export class UserServiceService {

  constructor() { }

}
```

Remember when generating files with the CLI that it will automatically append the type of file it is which means you do not need to put that information in the description.

Placing your Logic

Occasionally I've found myself building my front end and wanting to do some math on one of my variables. Take a look at this next code block, and decide if there are any anti-patterns here:

```typescript
@Component({
  selector: 'toh-hero-list',
  template: `
    <section>
      Our list of heroes:
      <hero-profile *ngFor="let hero of heroes" [hero]="hero">
      </hero-profile>
      Total powers: {{totalPowers}}<br>
      Average power: {{totalPowers / heroes.length}}
    </section>
  `
})
export class HeroListComponent {
  heroes: Hero[];
  totalPowers: number;
}
```

There's just a simple division in there for `Average Power` but this really isn't the place for it. The proper way is to define a new variable in the component class that will do the division and then call that variable in the HTML. Keeping the logic out of the view and inside functions will make your code easier to test and reuse down the line.

With this example we can also touch back on structure. With you HTML it is easy to keep it in a component class but as the HTML gets more complex it becomes a bit harder to read and maintain. It's easy to extract the HTML template into it's own file (remembering your naming conventions) in order to clean up your component class.

```typescript
@Component({
  selector: 'toh-hero-list',
  templateUrl: './hero-list.component.html'
})
export class HeroListComponent {
  heroes: Hero[];
  totalPowers: number;

  get avgPower() {
    return this.totalPowers / this.heroes.length;
  }
}
```

Wrapping Things Up

In the grand scope why does all this really matter and what can you do to write good code? As developers, we should each strive to deliver quality code that we can hang our hats on. It is hard to anticipate where the lifecycle of your code will go, and avoiding anti-patterns will make it easier to work with your project for years to come. Code that works for demonstration purposes might not be the best as your application grows. Keeping the scale of the project in mind from the start will save you hours of trouble down the line. If you are looking for more guidance on proper Angular design patterns, the Angular Style Guide has plenty of more information.

No comments :

Post a Comment