GithubHelp home page GithubHelp logo

angular-movies-perf's People

Contributors

biophoton avatar hoebbelsb avatar karnaukhov-kh avatar medeirosjoaquim avatar vmasek avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

angular-movies-perf's Issues

Categories Add Modal

Styling

The categories component includes empty styles.scss file. This causes extra overhead in the build time and increases the bundle size.

  • Solution:

Remove the styles file and import in the component

Optimize rendering, style recalculation and paint v1

To optimize style recalculation layout and paint we can apply the contain styling rule

TL;DR

  • Content section: from 513ms to 251 ms (262ms improvement)
  • Sidebar section: from 53ms to 32 ms (21ms improvement)
  • Toolbar section: marginal animation improvement

Total improvement 283ms

Movies section

When navigating to a movie list the whole document is rendered
angular-movies-_movi-list-before

<div _ngcontent-hub-movies-c24="" class="content">
 <!-- movie list -->
</div>

the stylings for the content section are:

.content[_ngcontent-hub-movies-c24] {
    min-height: calc(100vh - 72px);
    width: 100%;
}

Measures Before:
angular-movies-_movi-list-detail-before

  • 0 ms Scripting
  • 415 ms Rendering
  • 56 ms Painting
  • 40 ms System
  • 1 ms Idle
  • 513 ms Total

To improve the render cost we can apply some styling rules.

.content[_ngcontent-hub-movies-c24] {
  ...
  contain: strict;
}

This scopes the area that gets rerendered to the div.
angular-movies-_movi-list-after

Measures After:
angular-movies-_movi-list-detail-after

  • 1 ms Scripting
  • 232 ms Rendering
  • 11 ms Painting
  • 8 ms System
  • 0 ms Idle
  • 251 ms Total

The overall improvement is minus 262ms ๐Ÿ˜Ž

Side drawer

The side drawer gets an update of fetched menu items. These causes rerender of the whole page.

Again we apply contain: strict;.

<div class="side-drawer">
 <!-- menu items -->
</div>

Measures Before:
angular-movies-_drawer-detail-before
angular-movies-_drawer-before

  • 0 ms Scripting
  • 3 ms System (self)
  • 44 ms Rendering
  • 6 ms Painting
  • 0 ms Idle
  • 53 ms Total

Measures After:
angular-movies-_drawer-detail-after
angular-movies-_drawer-after

  • 0 ms Scripting
  • 25 ms Rendering
  • 4 ms Painting
  • 2 ms System
  • 0 ms Idle
  • 32 ms Total

The overall improvement is minus 31ms ๐Ÿ™‚

Toolbar and input animation

This is a small one but the approach is the same.

Here we apply contain: strict; to the app-toolbar component:

<div _ngcontent-hub-movies-c24="" class="app-toolbar">
 <!-- header toolbar -->
</div>

Measures Before:
angular-movies-_toolbar-before

Measures After:

angular-movies-_toolbar-after

The impact is marginal but still fewer items processed.

Fix inconsistencies and bugs through rewrite

  • login logout state is not reflected in the menu
  • Movie hover styling misses gray background in the description area
  • login success shows infinite loading spinner
  • click on movie does not navigate to details page

Image lazy code is slow

The popular page gets loaded and HTTP request updates the list.

angular_popular-http

The longest time is spent in the list rendering:

angular_popular-old

to be more specific in the initialisation time of the lazy image directive.
angular_popular-lazy-img-dir-init

One obvious reason is the forces reflow visible at the end of the flames
angular_popular-lazy-img-dir

Stats:
List rendering 412ms

Categories Delete Modal

Styling

The categories component includes empty styles.scss file. This causes extra overhead in the build time and increases the bundle size.

  • Solution:

Remove the styles file and import in the component

Organize for better bundling

To get smaller bundle sizes the Angular Module and lib structure need to get refactored.

Refactorings:

  • Refactored to SCAMs
  • Get rid of SharedMaterialModule and organize in SCAMs
  • Introduce app shell which only provides the minimum and the rest can be preloaded

Account Page

Images

The avatar image has no dimensions set. This causes CLS and can be avoided by providing an aspect ratio to the IMG tag.
Furthermore, we can use native lazy loading to give the Browser time to process more important tasks.

  <img *ngIf="photoURL" src="{{photoURL}}" alt="profile">

Expected Impact:
The Browser can process more important tasks first. When the image gets rendered no CLS will occur.

  • Solution:
  <img *ngIf="photoURL" src="{{photoURL}}" alt="profile" loading="lazy" width="50" height="50">

Styling

In the account page the username is used to fill the image if no avatar is given. This value is used in the template.
The drawback here is Angular will need to reevaluate the template to apply the character to the DOM which is expensive.

I suggest moving the logic which adds a character to the DOM to JS only. In addition, I added a small enrichment for the case no avatar is given. I use the first letter of the username to derive a random color for the background of the avatar image to make it look a little nicer.

Expected Impact:
Both things are done by using CSS variables. In this way, we can avoid change detection.

  • Solution:
@Component({
  template: `
    <!-- Template not needed to accomplish style adoption -->
  `,
  styles: `
    :root {
      --avatarColor: #cccccc;
    }
   .avatar {
      background-color: var(--avatarColor);
   }
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class Component {
  change() {
    document.documentElement.style.setProperty('--avatarColor', '#cccccc');
  }
}

Third party libs

As dayjs is used only once to get the proper date format we can remove it and use vanilla JS.

Usage:

this.creationTime = dayjs(authData.metadata.creationTime).format('YYYY-MM-D');

Expected Impact:
Checking bundlephobia dayjs has a total of:

  • 6.3kB MINIFIED

  • 2.8kB MINIFIED + GZIPPED

  • Solution

Where I would question the format, in particular, the last single "D" as correct the value is never used anywhere so I would drop it completely.

State Management

At the moment the component maintains a subscription to the user data stream. This leads to cluttery code and usage of a lot of workarounds like the need for markForCheck.

A refactor to template-based subscription handling will reduce the code base and enable future performance optimizations with @rx-angular/template

Expected Impact:
We can reduce LOC, the number of injected services which will reduce bootstrap time and prepare for further render improvements.

  • Solution:
    use the ngIf hack as the first quick win before introducing rx-angular.

Remove zone.js

By removing zone.js we can save significant bundle size and bootstrap time.

Things I had to change:

  • routing and change detection (introduced helper service)
  • refactor async pipe to rxLet (or push pipe)
  • helper to check if we run zoneless or not

Before:

Bootstrap Time Before
angular_popular-zone-full

Bundlesize Before
angular_popular-zone-full-bundle

Bootstrap Time Before 425ms
Polyfills Before es5 132.32 KB, es2015 36.12 KB

After:

Bootstrap Time After
angular_popular-zone-less

_Bundlesize After
angular_popular-zone-less-bundle
_

Bootstrap Time After 66ms
Polyfills After es5 126 B

Implement global state to cache results

Introduce global state to cache results between routes.
Furthermore, it introduces a new layer so subscriptions are not owned by the components, but rather in a central place.

  • Intro state service
  • reduce subscriptions handling code by using tools that subscribe in the template (async, push, let)

Setup Linting Rules

OnPush:

  • setup linting
  • adopt components
  • config schematics

trackBy:

  • setup linting
  • adopt components

unused variables:

  • setup linting
  • adopt components

Initial linting report

  • src/app/about/about.component.ts:3:1

    • ERROR: 3:1 prefer-on-push-component-change-detection The changeDetection value of a component should be set to ChangeDetectionStrategy.OnPush
  • src/app/account/account-delete-modal/account-delete-modal.component.ts:4:1

    • - ERROR: 4:1 prefer-on-push-component-change-detection The changeDetection value of a component should be set to ChangeDetectionStrategy.OnPush
  • src/app/account/account.component.ts:11:1

    • - ERROR: 11:1 prefer-on-push-component-change-detection The changeDetection value of a component should be set to ChangeDetectionStrategy.OnPush
  • src/app/app.component.ts:9:1

    • - ERROR: 9:1 prefer-on-push-component-change-detection The changeDetection value of a component should be set to ChangeDetectionStrategy.OnPush
    • - ERROR: 31:61 no-explicit-change-detection-apis Use with caution. detectChanges is synchronous and will trigger change detection in this component and all its children.
    • - ERROR: 49:31 prefer-no-layout-sensitive-apis Use with caution. scrollY may trigger style recalculation
    • - ERROR: 55:16 prefer-no-layout-sensitive-apis Use with caution. scrollTo may trigger style recalculation
  • src/app/categories/categories-add-modal/categories-add-modal.component.ts:5:1

    • - ERROR: 5:1 prefer-on-push-component-change-detection The changeDetection value of a component should be set to ChangeDetectionStrategy.OnPush
  • src/app/categories/categories-delete-modal/categories-delete-modal.component.ts:4:1

    • - ERROR: 4:1 prefer-on-push-component-change-detection The changeDetection value of a component should be set to ChangeDetectionStrategy.OnPush
  • src/app/categories/categories.component.html:29:22

    • - ERROR: 29:22 template-use-track-by-function Missing trackBy function in ngFor directive
    • - ERROR: 87:13 template-use-track-by-function Missing trackBy function in ngFor directive
    • - ERROR: 89:22 template-use-track-by-function Missing trackBy function in ngFor directive
  • src/app/categories/categories.component.ts:13:1

    • - ERROR: 13:1 prefer-on-push-component-change-detection The changeDetection value of a component should be set to ChangeDetectionStrategy.OnPush
  • src/app/movies/movie-list/movie-list.component.ts:8:1

    • - ERROR: 8:1 prefer-on-push-component-change-detection The changeDetection value of a component should be set to ChangeDetectionStrategy.OnPush
  • src/app/movies/movie/movie-trailer/movie-trailer.component.ts:4:1**

    • - ERROR: 4:1 prefer-on-push-component-change-detection The changeDetection value of a component should be set to ChangeDetectionStrategy.OnPush
  • src/app/movies/movie/movie.component.html:46:14

    • - ERROR: 46:14 template-use-track-by-function Missing trackBy function in ngFor directive
    • - ERROR: 83:14 template-use-track-by-function Missing trackBy function in ngFor directive
    • - ERROR: 103:26 template-use-track-by-function Missing trackBy function in ngFor directive
    • - ERROR: 131:30 template-use-track-by-function Missing trackBy function in ngFor directive
  • src/app/movies/movie/movie.component.ts:20:1

    • - ERROR: 20:1 prefer-on-push-component-change-detection The changeDetection value of a component should be set to ChangeDetectionStrategy.OnPush
    • - ERROR: 141:11 no-unused-variable 'dialogRef' is declared but its value is never read.
  • src/app/movies/movies.component.ts:11:1

    • - ERROR: 11:1 prefer-on-push-component-change-detection The changeDetection value of a component should be set to ChangeDetectionStrategy.OnPush
  • src/app/playlist/playlist.component.html:9:22**

    • - ERROR: 9:22 template-use-track-by-function Missing trackBy function in ngFor directive
    • - ERROR: 70:22 template-use-track-by-function Missing trackBy function in ngFor directive
  • src/app/playlist/playlist.component.ts:10:1

    • - ERROR: 10:1 prefer-on-push-component-change-detection The changeDetection value of a component should be set to ChangeDetectionStrategy.OnPush
  • src/app/settings/settings.component.html:7:22

    • - ERROR: 7:22 template-use-track-by-function Missing trackBy function in ngFor directive
  • src/app/settings/settings.component.ts:7:1

    • - ERROR: 7:1 prefer-on-push-component-change-detection The changeDetection value of a component should be set to ChangeDetectionStrategy.OnPush
  • src/app/shared/component/pagination/pagination.component.html:14:10

    • - ERROR: 14:10 template-use-track-by-function Missing trackBy function in ngFor directive
  • src/app/shared/component/pagination/pagination.component.ts:3:1

    • - ERROR: 3:1 prefer-on-push-component-change-detection The changeDetection value of a component should be set to ChangeDetectionStrategy.OnPush
  • src/app/shared/component/share-modal/share-modal.component.ts:6:1

    • - ERROR: 6:1 prefer-on-push-component-change-detection The changeDetection value of a component should be set to ChangeDetectionStrategy.OnPush
  • src/app/shared/service/tmdb/tmdb.service.ts:23:49

    • - ERROR: 23:49 no-unused-variable Property 'storageService' is declared but its value is never read.
  • src/app/sign-in/sign-in.component.ts:7:1

    • - ERROR: 7:1 prefer-on-push-component-change-detection The changeDetection value of a component should be set to ChangeDetectionStrategy.OnPush
  • src/app/star/star.component.html:18:26

    • - ERROR: 18:26 template-use-track-by-function Missing trackBy function in ngFor directive
    • - ERROR: 31:26 template-use-track-by-function Missing trackBy function in ngFor directive
  • src/app/star/star.component.ts:13:1

    • - ERROR: 13:1 prefer-on-push-component-change-detection The changeDetection value of a component should be set to ChangeDetectionStrategy.OnPush

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.