DZone
Web Dev Zone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
  • Refcardz
  • Trend Reports
  • Webinars
  • Zones
  • |
    • Agile
    • AI
    • Big Data
    • Cloud
    • Database
    • DevOps
    • Integration
    • IoT
    • Java
    • Microservices
    • Open Source
    • Performance
    • Security
    • Web Dev
DZone > Web Dev Zone > Angular: Advanced Tips and Best Practices for Experts and Beginners

Angular: Advanced Tips and Best Practices for Experts and Beginners

Avraam Piperidis user avatar by
Avraam Piperidis
CORE ·
May. 26, 20 · Web Dev Zone · Presentation
Like (14)
Save
Tweet
34.73K Views

Join the DZone community and get the full member experience.

Join For Free

I wrote this article and included some best practices, pitfalls, info, and ' what to look out for' when building an Angular application. A lot of these had been personal experiences and lessons learned the hard way. Also, it was really difficult to create this list, keep it short and simple since I wanted to add stuff for beginners and experts. 

Try to follow what Angular recommends: don't do things your way

Since you're using Angular, you should follow what the creator suggests and not do things your way. This goes for most opinionated frameworks, not just Angular. 

A MUST DO starting point is to read and follow the angular styleguide. 

If you're new to Angular, start with tour of heroes

Its one of the best tutorials with very good documentation out there that helped me a lot back when I started with angular. The tour of heroes

Read the Documentation

The angular.io documentation is mature and almost complete. Besides that, you should check and be comfortable with Angular's Glossary.

Also, other things to check.

  • Cheat Sheet.
  • Features.
  • Resources.

Consider using a UI Component library

You can choose between Angular Material, primeng, and many others that will make your life much easier

Use update.angular.io when upgrading version

Need to upgrade from older version to a newer and don’t know what will break, or what to change? No worries. Visit update.angular.io, and choose the version you currently have and the version you want to go, and follow the instructions. It also includes a guide for angular material.

Involve

Search for Angular meetups near your area.

Do you know Angular has its own Blog?

Check out top Angular events.

Also, don’t hesitate to open/report an issue when finding a problem regarding Angular itself or third party libraries. It will only make things better

Versioning/package.json, remove all caret ‘^’ and tilde ‘~’

If you want your app/library to be stable and not fail after a few months, you should highly consider removing all caret and tilde symbols from package.json.

Do not blindly accept automatic dependencies. You don’t want people knocking on your door when things go bad because of auto dependency update.

The web is evolving dramatically year after year. Try to run npm install on a 3-to-6-month-old project, and you get a warning plus deprecation messages not to mention there is a chance for a build failure.

If you want to have more control of your versions you can set this

npm config set save-prefix=''

Invest time staying up to date

It's recommended to invest some time once every 3 to 6 months and keep your dependencies up to date. Remember, a little warning or deprecation notice today will become 10 or 20 notices in the future and will bring higher problems.

If you are in the position where you running Angular 4, the latest version is 9, and you need to upgrade. Well bad news for you, it’s going to be a tough and unpleasant job. Because it’s not just the Angular you have to upgrade, it’s also all the dependencies that rely on.

Use types instead of Any

Avoid using any and prefer types. Use any if there is no other option or for special cases. That’s why you’re using Angular and TypeScript in the first place.

Know your package-lock.json

Remember your package-lock.json is the source of truth

package-lock.json lists the correct dependency versions your app is using. Refer to package-lock to know the exact version of a dependency your using.

For example, you may have this in your package.json ‘^1.1.1', but in runtime, you are using this ‘1.2.2’

Run npm ls module-name to get the exact module version.

Package-lock is generated automatically from any operation that modifies package.json or node_modules tree, assuming you have node > 5.x.x.

Also, run npm ci for clean install from package-lock

Watch out for dev, prod differences

Don’t take for granted that everything will be working just like development with prod build.

For example in development mode change detection runs twice. The second time it runs (after the first completes) if the bound values are different it produces the famous error: ExpressionChangedAfterItHasBeenCheckedError.

Build-optimizer and vendor-chunk

Setting vendorChunk to true in prod build the libraries will be placed in a separate vendor chunk.

Remember, if buildOptimizer is true, set vendorChunk to false. Using a build optimizer includes the library code in the bundle, resulting in a smaller size.

Use vendor chunk when:

  • There is a lot of client updates without affecting much of the third-party dependencies/libraries

Do not use vendor chunk when:

  • The bundle size difference is significantly larger.
  • There are no frequent client updates.

Service in subModule Providers

Services are singleton in the module provided. Adding them to other module providers will create a new instance.

Consider using in Injectable providedIn root | any | platform

Prior to Angular 6 and later, we can use providedIn:'root' in Injectable instead of importing the service in a module.

Also, Angular 9 introduced us with extra providedIn options, any, and platform

By declaring a providedIn in Injectable service, we don’t have to import in any module. Instead, if we add the service in providers, we also have to import it, which will be included in the final bundle even if we don’t use it anywhere.

By using providedIn, which is the official, preferred way, it enables Tree Shaking. With Tree Shaking, if the service isn’t used anywhere it won’t be bundled with the rest of the code, which leads to smaller bundle size and faster loading times

What about providedIn root | any | platform options?

The time of writing this article, the official angular documentation of providedIn only covers root. So I will try to explain any and platform as simple as possible.

  • root: One instance of the service for the whole application
  • any: One instance for the eagerly loaded modules, different instance for every lazy module
  • platform: One instance for all angular applications running on the same page (useful for shared service modules)

Lazy loading

Please lazy load your modules and don’t eager load them all at once. It makes a tremendous difference in loading time even for small applications. Consider eager loading for core modules and feature modules that required to start the app and do some initial interception.

I’ve seen big applications with all modules been eager load, and it some cases it exceeds 30 seconds to start.

You can check angular lazy-loading doc

Lazy loading loadChildren from a secure place

If the resources and time are available you may consider store loadChildren path in a secure place and fetch them after authorization.

Choose Angular i18n (even if it’s not mature yet) instead of ngx-translate

Yes, I know, you probably think that angular implementation of i18n sucks!. You have to create different builds for every language and to reload for every language change.

And you are right! It was a living hell for me and my team when working with these.

But…. keep in mind, Angular i18n is going through a lot of development effort and it is very complex. Angular is pushing it and eventually will become a lot better. Also, the main developer of ngx-translate is hired by Angular and working in i18n (source here). His statement is that ngx-translate is just a replacement for Angular until Angular i18n is completed, and at some point, ngx-translate will be deprecated.

Use trackBy when you can

You rendered a collection with *ngFor, and now you have to get new data from an HTTP request and reassign them to the same collection. This will cause Angular to remove all DOM elements associated with the data and re-render all DOM elements again. Having a large collection could cause a performance problem since DOM manipulations are expensive.

trackBy comes to the rescue. By using trackBy, rendering will happen only for the collection elements that have been modified(also new/removed).

Consider the following

TypeScript
 




xxxxxxxxxx
1
19


 
1
export class AppComponent {
2
  //initial array
3
   cars = [
4
    {id:1,name:'Honda'},
5
    {id:2,name:'Toyota'},
6
    {id:3,name:'Hyundai'}];
7
  
8
  setNewCars() {
9
    this.cars = [
10
      {id:1,name:'Honda'},
11
      {id:2,name:'Mazda'},
12
      {id:3,name:'Hyundai'},
13
      {id:4,name:'BMW'}];
14
  }
15
 
          
16
  trackByFn(index,car){
17
    return index;
18
  }
19
}


HTML
 




xxxxxxxxxx
1


 
1
<ul>
2
   <li *ngFor="let car of cars">{{car.name}}</li>
3
</ul>
4
<button (click)="setNewCars()">update cars</button>



By pressing the button, the whole ul HTML block will be re-render.

Let’s add trackBy by including trackByFn and update the *ngFor

HTML
 




xxxxxxxxxx
1


 
1
<ul>
2
   <li *ngFor="let car of cars; trackBy:trackByFn">{{car.name}}</li>
3
</ul>



Now, by pressing the button, the elements with a difference will be re-render. Which in our case are the updated item with id:2 and the new item with id:4.

Angular webworker has been marked as deprecated and may be removed at angular 10

Change detection strategy: OnPush

By default, Angular runs change detection on all components for every event that occurred. It’s the default ChangeDetectionStrategy.Default. Having many components and a large number of elements could result in performance issues. To deal with that we can set ChangeDetectionStrategy.OnPush on the components we want. For example.

TypeScript
 




xxxxxxxxxx
1


 
1
@Component({  
2
...  
3
    changeDetection:ChangeDetectionStrategy.OnPush
4
...
5
})
6
export class ChildComponent {    
7
    @Input
8
    data;
9
}



By settings ChangeDetectionStrategy.OnPush the ChildComponent it only depends on the input values and it will only run checks if the input values change.

If the input is a value type it will run a check for the new value.

For a reference type value, a check will run only if is a difference between the old and new input reference.

Keep note to a case when we have a different input reference and change detection is not running.

TypeScript
 




xxxxxxxxxx
1
17


 
1
@Component({  
2
...  
3
    changeDetection:ChangeDetectionStrategy.OnPush
4
...
5
})
6
export class ChildComponent {    
7
    @Input
8
    data;
9
 
          
10
    setNewData(){
11
        //Async get new data from http request
12
        getData().then(d=>{
13
            this.data = d;
14
            //No UI update at this point  
15
        }) 
16
    }
17
} 



Having a similar code with above, you notice even if the input reference is changing, the component is not updating. That’s because there is not an event trigger to run the change detection in the first place.

The solution is to use inject ChangeDetectorRef and programmatically trigger change detection.

TypeScript
 




xxxxxxxxxx
1
12


 
1
export class ChildComponent {    
2
    @Input
3
    data;
4
    constructor(private cd : ChangeDetectorRef){}
5
    setNewData(){
6
        //Async get new data from http request
7
        getData().then(d=>{
8
            this.data = d
9
            this.cd.detectChanges()
10
        }) 
11
    }
12
} 



Be careful with spamming events

As said above, every event triggers change detection. Be careful with global and window events that may cause unintended checks running on the whole app all the time

The notorious ExpressionChangedAfterItHasBeenCheckedError error

If you see this error it means there is something wrong with your code.

In development mode, Angular runs two digests circles. The first time it evaluates the values and updates the DOM. If Angular is running on development mode, the circle runs a second time. In the second circle, Angular performs verification and compares the values against the first circle. If a difference is found, it produces the error. 

That’s why this error means you have something very seriously wrong with your code. If the error wasn’t thrown, two things could happen. First, for the values to be the same between oldValues and newValues, it could lead to an infinite circle loop. And second, it could lead to an inconsistent model – view state.

A simple scenario that may cause the error

TypeScript
 




xxxxxxxxxx
1


 
1
AppComponent HTML
2
<childComponent [data]="originalData"</childComponent>
3
 
          
4
ChildComponent
5
ngOnInit(){
6
  this.appComponent.originalData = "updatedValueData"
7
}



Tip: avoid manipulating a bound array directly in loop.

ViewChild/ContentChild availability

Both ViewChild and ContentChild are available from ngAfterViewInit and after. ngAfterViewInit is when the component view has been initialized.

ViewChildren and ContentChildren will return a QueryList, while ViewChild and ContentChild will return the first element they matched.

Pure and impure Pipes

Pupe pipe is when angular executes the pipe only when input reference changes. By default, pipes are pure. Consider the following pure pipe.

TypeScript
 




xxxxxxxxxx
1


 
1
@Pipe({ 
2
  name: 'jdmCars' 
3
})
4
export class JdmCarsPipe implements PipeTransform {
5
  transform(allCars: Car[]) {
6
    return allCars.filter(car => car.isJdm);
7
  }
8
}



Now if we add a new car somewhere in our component code…

TypeScript
 




xxxxxxxxxx
1


 
1
addNewJdmCar(){
2
  this.cars.push({name:'Honda',isJdm:true});
3
}



Nothing happens. The view is not updating. This is because the car array reference is the same. If we want to see the changes, we have to make our pipe impure or re-reference(not recommended) the car array.

TypeScript
 




xxxxxxxxxx
1


 
1
@Pipe({
2
  name: 'jdmCars',
3
  pure: false
4
})



Base URL and Paths in tsconfig.json

Instead of heaving imports like this…

TypeScript
 




xxxxxxxxxx
1


 
1
import { MyService }   from '../../services/MyService';
2
import { MyComponent } from '../../../../app/core/MyComponent';



Define the base paths you want in tsconfig.json …

JavaScript
 




x
15


 
1
{
2
...
3
  "compilerOptions":{
4
    "baseUrl":"src",
5
    "paths": {
6
      "@app/*": ["src/core/*"],
7
      "@assets/*": ["src/assets/*"],
8
      "@service/*": ["src/services/*"]
9
    }
10
  }
11
...
12
}
13
//And use them
14
import { MyService }   from '@service/MyService';
15
import { MyComponent } from '@app/MyComponent';



Will save you a lot of time in refactoring

Why not check out Redux

Depending on the situation and if state management of type undo/redo is needed, you could consider using redux for your next angular app. You could check out rxjs for Angular.

AngularJS

Published at DZone with permission of Avraam Piperidis, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Exploring a Paradigm Shift for Relational Database Schema Changes
  • Externalize Microservice Configuration With Spring Cloud Config
  • What Is Cloud Storage: Definition, Types, Pros, and Cons
  • Decorator Pattern to Solve Integration Scenarios in Existing Systems

Comments

Web Dev Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • MVB Program
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends:

DZone.com is powered by 

AnswerHub logo