Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for PWAs with Angular: Being Fast
Michael Solati
Michael Solati

Posted on • Edited on • Originally published atmichaelsolati.com

     

PWAs with Angular: Being Fast

Earlier in the week we looked at starting to turn a basic Angular application into a Progressive Web App (you can catch up here). Now that we have an application that isreliable and will load content from cache even when there's no network, let's make our application fast!


git clone--branch v1.0 https://github.com/MichaelSolati/ng-popular-movies-pwa.gitcdng-popular-movies-pwanpminstall
Enter fullscreen modeExit fullscreen mode

This app depends onThe MovieDB's APIs. Get an API key (check this out) and put it as the moviedb environment variable in yoursrc/environments/environment.tsandsrc/environments/environment.prod.ts.

Lets run our applicationnpm run start:pwa, and then disable JavaScript in our browser. All our user would get is a black screen:

No JavaScript

This is definitely not PWA behavior, and actually ties back to our last topic of having areliable application. So lets fix that with one of the tools in ourng-pwa-tools package we added to our application last time. Specifically we will be using thengu-app-shell tool.

First, we're going to go intosrc/app/app.module.ts file and change ourBrowserModule import on line 22 toBrowserModule.withServerTransition({ appId: 'ng-popular-movies-pwa' }). (ThewithServerTransition() function configures our browser based application to transition from a pre-rendered page, details to come) Now lets run ourngu-app-shell.

./node_modules/.bin/ngu-app-shell--module src/app/app.module.ts
Enter fullscreen modeExit fullscreen mode

You should have seen logged into your terminal our entire home route rendered out! We have all of our HTML, CSS, and even data grabbed from The MovieDB. What ourngu-app-shell did was prerender out our index route much in the same way thatAngular Universal does.

With a prerendered home route, we don't need to worry if our user has JavaScript disabled, or if it takes a while for our JS bundles to download and execute. We have content already rendered into HTML. So we can use thengu-app-shell to replace our emptydist/index.html with a rendered out page.

./node_modules/.bin/ngu-app-shell--module src/app/app.module.ts\--out dist/index.html
Enter fullscreen modeExit fullscreen mode

While we're here, let's update ournpm scripts to the following.

{    "ng": "ng",    "start": "ng serve",    "start:pwa": "npm run build && cd dist && http-server",    "build": "ng build --prod && npm run ngu-app-shell && npm run ngu-sw-manifest",    "test": "ng test",    "lint": "ng lint",    "e2e": "ng e2e",    "ngu-app-shell": "./node_modules/.bin/ngu-app-shell --module src/app/app.module.ts --out dist/index.html",    "ngu-sw-manifest": "./node_modules/.bin/ngu-sw-manifest --module src/app/app.module.ts --out dist/ngsw-manifest.json"}
Enter fullscreen modeExit fullscreen mode

App rendered with no JavaScript

Not only is this a better experience for when our user has JavaScript disabled, this is an inherently faster process. When we pass an already rendered page to the user, we do not need to wait for our code to run. Instead we give the user something as soon as the HTML loads, then we let ourBrowserModule transition in our Angular app to replace the rendered content.


Another way we can speed up our application is "lazy loading" parts of our application. In Angular we can lazy load modules, which essentially means we can group related pieces of code together and load those pieces on demand. Lazy loading modules decreases the startup time because it doesn't need to load everything at once, only what the userneeds to see when the app first loads.

In our current structure we only have two routes, one module, and essentially two components (I'm excluding theAppComponent because all it does is provide our navigation bar). So let's create a new module for ourHomeComponent and ourMovieComponent and put the components into those modules.

ng g m homeng g m movie
Enter fullscreen modeExit fullscreen mode

Next let's change oursrc/app/home/home.module.ts to look like this.

import{NgModule}from'@angular/core';import{CommonModule}from'@angular/common';import{MaterialModule}from'@angular/material';import{RouterModule}from'@angular/router';import{HomeComponent}from'./home.component';@NgModule({declarations:[HomeComponent],imports:[CommonModule,MaterialModule,RouterModule.forChild([{path:'',pathMatch:'full',component:HomeComponent}])]})exportclassMovieModule{}
Enter fullscreen modeExit fullscreen mode

Now we'll change oursrc/app/movie/movie.module.ts, making it similar to ourHomeModule.

import{NgModule}from'@angular/core';import{CommonModule}from'@angular/common';import{MaterialModule}from'@angular/material';import{RouterModule}from'@angular/router';import{MovieComponent}from'./movie.component';@NgModule({declarations:[MovieComponent],imports:[CommonModule,MaterialModule,RouterModule.forChild([{path:'',pathMatch:'full',component:MovieComponent}])]})exportclassMovieModule{}
Enter fullscreen modeExit fullscreen mode

We should also updatesrc/app/app-routing.module.ts to reflect that we will be lazily loading our routes from our modules.

import{Routes,RouterModule}from'@angular/router';constroutes:Routes=[{path:'',loadChildren:'app/home/home.module#HomeModule'},{path:'movie/:id',loadChildren:'app/movie/movie.module#MovieModule'},{path:'movie',redirectTo:'/',pathMatch:'full'},{path:'**',redirectTo:'/'}];exportconstrouting=RouterModule.forRoot(routes);
Enter fullscreen modeExit fullscreen mode

Finally we'll update oursrc/app/app.module.ts to reflect our newrouting, as well as remove any reference of our components.

import{BrowserModule}from'@angular/platform-browser';import{BrowserAnimationsModule}from'@angular/platform-browser/animations';import{NgModule}from'@angular/core';import{HttpModule}from'@angular/http';import{MaterialModule}from'@angular/material';import{MoviesService}from'./services/movies.service';import{NavbarService}from'./services/navbar.service';import{routing}from'./app-routing.module';import{AppComponent}from'./app.component';@NgModule({declarations:[AppComponent],imports:[BrowserModule.withServerTransition({appId:'ng-popular-movies-pwa'}),HttpModule,BrowserAnimationsModule,MaterialModule,routing],providers:[MoviesService,NavbarService],bootstrap:[AppComponent]})exportclassAppModule{constructor(private_moviesService:MoviesService,private_navbarService:NavbarService){}}
Enter fullscreen modeExit fullscreen mode

By prerendering our home route as well as lazy loading all of our routes we were able to not only make our applicationfaster but also morereliable! While this application may not have many routes that lazy loading can shave off seconds from our initial load time, for your bigger applications it definitely will.

By running the Lighthouse tests on our current app, we can see our PWA and Performance scores nudge up from 36 each (taking the score from the previous article which was not using a deployed app), to 45 and 61 respectively.

Better Lighthouse scores


You can look at the changes we made in our code byclicking here. Additionally, if you run Lighthouse on a deployed version of our application you'll start to get results looking like this:

Deployed Lighthouse test


Final part of the series, titled "PWAs with Angular: Being Engaging," isavailable here.

Top comments(2)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
playground profile image
playground
  • Joined

Hi Michael,

When I try to apply app-shell as such

ngu-app-shell.js --module src/app/app.module.ts --out dist/index.html

(function (exports, require, module, __filename, __dirname) { import { Inject, Injectable, NgModule } from '@angular/core';
^
SyntaxError: Unexpected token import
at createScript (vm.js:53:10)
at Object.runInThisContext (vm.js:95:10)
at Module._compile (module.js:543:28)
at Object.Module._extensions..js (module.js:580:10)
at Module.load (module.js:488:32)
at tryModuleLoad (module.js:447:12)
at Function.Module._load (module.js:439:3)
at Module.require (module.js:498:17)
at require (internal/module.js:20:19)
at Object. (/Users/sandbox/src/app/app.module.ts:6:1)

app.module.ts:6:1 where it is referencing my private npm module

CollapseExpand
 
michaelsolati profile image
Michael Solati
  • Email
  • Location
    Oakland, CA
  • Education
    Southern Connecticut State University
  • Joined

What machine are you running your code on? There have been a slew of issues and difficulties faced by windows users. Also if you have your code up on github or something like that I can take a peak and see whats going on... There can be a slew of moving parts here that can throw off your results.

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

  • Location
    Oakland, CA
  • Education
    Southern Connecticut State University
  • Joined

More fromMichael Solati

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp