Angular Best Practices — August 2017 Edition

Best Practice 1: Use the CLI

Today, the Angular CLI is the best way to build Angular Applications. Many developers got started with Angular before the CLI was ready for primetime, but the CLI is awesome for the vast majority of developers, startups, and large enterprise teams.

Starting a new project? Create it with the CLI.

Working on an existing project? Create a new project with the CLI and then move your existing code into /src/app/.

npm install -g @angular/cli
ng new my-project

The CLI has scaffolding (aka schematics) tools for creating new projects, and generating new code for you, but these aren’t the main benefit. The main benefit of the CLI is the way that it automates the build pipeline for both live development with ng serve as well as for production code that you would ship down to browsers with ng build -prod

ng build -prod is always going to take advantage of as many best practices as the Angular team is able to turn on automatically. This means that this command will get more powerful over time with features like Service Workers or The Angular Optimizer.

Best Practice 2: Install John Papa’s Vs Code Extension

Visual Studio Code is an awesome IDE for building Angular apps. One of the biggest favors you can do for yourself is to install John Papa’s Essential Angular Extension Pack.

This makes working with Angular in Visual Studio Code better than ever.

This pack includes the following awesome tools:

  1. The Angular Language Service — Provides template-aware and Angular-aware completion and error checking withing your application
  2. EditorConfig — Connects VSCode’s configuration to your .editorconfigthat we generate automatically for you as part of a new CLI project
  3. Bracket Pair Colorizer — Instead of standard syntax highlighting, this extension colors brackets, parentheses, and curly braces based on their nested layer. Visual indications of nesting is a huge help when working with complicated code.

Best Practice 3: Don’t Subscribe to Your Observables In Components

Many developers who are working with Observables for the first time want to subscribe and save the data locally somewhere.

This is usually an antipattern because it forces you the developer to manage the lifecycle of components and subscriptions, rather than letting the framework do this for you.

The better way to do this is to use the async pipe within your templates, and let Angular manage the whole thing for you. Let's take a look at some sample code.

Don’t do this:

...
template: `
{{localData | json}}
`)
export class MyComponent {
localData;
constructor(http: HttpClient) {
http.get('path/to/my/api.json')
.subscribe(data => {
this.localData = data;
});
}
}

Instead do this

...
template: `
{{data | async | json}}
`)
export class MyComponent {
data;
constructor(http: HttpClient) {
this.data = http.get('path/to/my/api.json');
}
}

By keeping the Observable and subscribing via the template, you are avoiding memory leaks because Angular will automatically unsubscribe from the Observable when the component is torn down. This may not seem as important for HTTP, but this is nice for several reasons.

The Async pipe will cancel HTTP requests if the data is no longer needed, instead of completing, processing, and then doing nothing with it.

The Async pipe finally means that we can build more performant applications by switching your ChangeDetectionStrategy to OnPush. When you switch to OnPush, many new and improved strategies can trigger the need for change detection, which your manual subscription won't automatically trigger.

Here are a few examples of more advanced Observable strategies:

The Async pipe means that later if you swap out a simple HTTP call with something more complex like a realtime dataset like Firebase, your template code doesn’t need to change.

Many of the features / powers of Observables are lost when you manually subscribe. Observables can be enriched with behavior like retry, startWith (for things like offline caching), or timer-based refreshing.

Best Practice 4: Don’t Forget about SEO and Analytics

Websites and applications are powerful because of the way search engines like Google can index them and share your content with the world.

Analytics products can help you understand your users needs and behaviors.

To set both of these up, let’s include the Google Analytics snippet in our index.html and replace the tracking code an removing the initial pageviewthat they include by default.

<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
  ga('create', 'UA-00000000-0', 'auto');
</script>

Now let’s go ahead and add events to our app.module.ts.

We want to listen to all of the router events, figure out when a navigation event has completed successfully (NavigationEnd), and update the page title and send an analytics event.

We’re going to use a subscription (oh no!), but it’s okay in this context because we’re going to have exactly one subscription, and we want it to live and run for the entire lifespan of our application in the browser.

For SEO, we’re going to read the page title from some additional data we’re including in our route config.

app.module.ts

declare var ga;
...
RouterModule.forRoot([
...
{ path: 'bio', component: Bio, data: { title: 'About' } },
{ path: 'projects', component: Projects, data: { title: 'Projects' } },
])
...
constructor(router: Router, activatedRoute: ActivatedRoute, title: Title) {
router.events.filter(e => e instanceof NavigationEnd).subscribe((event) => {
let pageTitle = router.routerState.snapshot.root.children[0].data['title'];
if (pageTitle) {
title.setTitle(pageTitle);
} else if (pageTitle !== false) {
title.setTitle('My Default Title');
}
ga('send', 'pageview', event.urlAfterRedirects);
});
}