Skip to content

Instantly share code, notes, and snippets.

@fabriciofmsilva
Last active July 30, 2019 21:12
Show Gist options
  • Save fabriciofmsilva/fcb7c0df05e62eb2358b7a6887af5485 to your computer and use it in GitHub Desktop.
Save fabriciofmsilva/fcb7c0df05e62eb2358b7a6887af5485 to your computer and use it in GitHub Desktop.
Angular

How to Write Better Code in Angular

Caching/Memoization

Optimize template expressions

  • Binary expressions
  • Function calls
  • Method calls
  • Primary expressions
@Component({
    // ...
    template: `
        <div>
            {{longMethod()}}
        </div>    
    `
})
export class App {
    longMethod() {
        // takes 3 mins to exec
        return 67
    }
}
  1. Finish quickly
  2. Use web workers
  3. Cache the template expressions
    • pure function/method
  4. Use pipe

App Shell

Shareable/Reusable Angular components

Container and compositional components

  1. Divide components into Smart and Dumb
  2. Keep components as Dumb as possible
  3. Decide when a component should be Smart instead of Dumb

No logic in templates

@Component({
    ...
    template: `
        <div>
            <h3>My List</h3>
            <div class="alert alert-danger" *ngIf="logged == true">You are not allowed here</div>
            ...
            <div class="panel">
                <div class="panel-header">{{name == 'undefined' ? "John Doe" : name}}</div>
                ...
            </div>
        </div>    
    `
})
export class App {
    logged: boolean = false
    name: string = null
    //...
}
@Component({
    ...
    template: `
        <div>
            <h3>My List</h3>
            <div class="alert alert-danger" *ngIf="logged">You are not allowed here</div>
            ...
            <div class="panel">
                <div class="panel-header">{{name}}</div>
                ...
            </div>
        </div>    
    `
})
export class App {
    logged: boolean = false
    name: string = "John Doe"
    //...
}

Unsubscribe in ngOnDestroy

@Component({
    // ...
    template: `
        <div *ngFor="let list of lists">
            //...
        </div>
    `
})
export class App implements OnInit {
    private listObservable: Observable
    ngOnInit() {
     listObservable = this.listService.getAllLists().subscribe(res=> this.lists = res)   
    }
}
@Component({
    // ...
    template: `
        <div *ngFor="let list of lists">
            //...
        </div>
    `
})
export class App implements OnDestroy, OnInit {
    private listObservable: Observable
    ngOnInit() {
     listObservable = this.listService.getAllLists().subscribe(res=> this.lists = res)   
    }
    ngOnDestroy() {
        this.listObservable.unsubscribe()
    }
}

Use trackBy function for *ngFor

@Component({
    // ...
    template: `
        <div *ngFor="let movie of movies">
            ...
        </div>
    `
})
export class App {
    private movies: Movies[]
    constructor(){
        this.movies = [
            {
                id: 0,
                name: 'nnamdi'
            },
            {
                id: 1,
                name: 'feynman'
            },
            // ... 1000 items
        ]
    }
}
this.movies = this.movies.concat([
    {
        id: Date.now(),
        name
    }
])
@Component({
    // ...
    template: `
        <div *ngFor="let movie of movies; trackBy: trackByFn">
            ...
        </div>
    `
})
export class App {
    private movies: Movies[]
    constructor(){
        this.movies = [
            {
                id: 0,
                name: 'nnamdi'
            },
            {
                id: 1,
                name: 'feynman'
            },
            // ... 1000 items
        ]
    }
    trackByFn(inde, item) {
        return item.id
    }
}

Use pipes

@Component({
    template: `
        {{dataFromTemplate | myPipe}}    
    `
})
{
    name: string,
    pure: boolean
}

use async pipe in template

@Component({
    // ...
    template:`
        <div>
            {{movies}}
        </div>    
    `
})
export class App implements OnInit {
    private movies: Movies[]
    constructor(private store: Store) {}
    ngOnInit() {
        this.store.select(state=> state.movies).subscribe(data= this.movies = data)
    }
}
@Component({
    // ...
    template:`
        <div>
            {{movies$ | async}}
        </div>    
    `
})
export class App implements OnInit {
    private movies$: Observable<Movies[]>
    constructor(private store: Store<Movies[]>) {}
    ngOnInit() {
        this.movies$ = this.store.select(state=> state.movies)
    }
}

Change Detection

  • XMLHttpRequest
  • DOM events like mouse movements, click events
  • setTimeout, setInterval, clearInterval, setImmediate etc

Detach component from component tree

@Component({
    // ...
    template: `
        <div>
            {{data}}
        </div>
    `
})
export class Comp {
    private data
    constructor(private dataService: DataService) {}
    getData() {
        this.data = this.dataService.loadData()
    }
}
@Component({
    // ...
    template: `
        <div>
            {{data}}
        </div>
    `
})
export class Comp {
    private data
    constructor(private dataService: DataService, private changeDetectorRef: ChangeDetectorRef) {
        this.changeDetectorRef.detach()
        setInterval(()=> {
            this.changeDetectorRef.detectChanges()
        }, 5000)
    }
    getData() {
        this.data = this.dataService.loadData()
    }
}

Run outside Angular zone

constructor(private ngZone: NgZone) {}
    this.ngZone.runOutsideAngular(()=> {
        // run outside Angular zone
        setTimeout(()=> {
            this.ngZone.run(()=> {
                // run inside ngZone
            })
        }, 5000)
    })

ChangeDetectionStrategy.OnPush

@Component({
    // ...
    template: `
        <div *ngFor="let movie of movies">
            ...
        </div>    
    `
})
export class MoviesList {
    @Input() movies
}
@Component({
    // ...
    template: `
        <div *ngFor="let movie of movies">
            ...
        </div>    
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class MoviesList {
    @Input() movies
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment