ng new angularSeo --style css --routing false cd angularSeo
<app-root>
in the src/index.html
file. Replace the existing code with the following:<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>AngularSeo</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> </head> <body> <app-root class="content"></app-root> <footer>Built with <a href="https://github.com/maciejtreder/ng-toolkit">ng-toolkit</a> by <a href="https://www.maciejtreder.com">maciejtreder.com</a></footer> </body> </html>
src/app/app.component.html
with:<h1>Hello World!</h1>
src/styles.css
:body { margin: 0 auto; max-width: 1000px; background: url('assets/img/sandbox.png') no-repeat center; display: flex; flex-direction: column; height: 100%; font-family: 'Source Sans Pro', calibri, Arial, sans-serif !important; min-height: 550px; } .content { flex: 1 0 auto; } footer { padding: 10px 0; text-align: center; }
src/assets/img
catalog.ng serve ** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ ** Date: 2018-10-29T08:58:37.685Z Hash: cb54e4608cfb1115882b Time: 7682ms chunk {main} main.js, main.js.map (main) 10.7 kB [initial] [rendered] chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 227 kB [initial] [rendered] chunk {runtime} runtime.js, runtime.js.map (runtime) 5.22 kB [entry] [rendered] chunk {styles} styles.js, styles.js.map (styles) 15.9 kB [initial] [rendered] chunk {vendor} vendor.js, vendor.js.map (vendor) 3.29 MB [initial] [rendered]
git clone -b tutorial1_step1 https://github.com/maciejtreder/angular-seo.git angularSeo cd angularSeo/ npm install ng serve
ng g c first ng g c second ng g c menu ng g s echo
src/app/first/first.component.ts
; it will be really straightforward. Replace the default contents with the following:import { Component } from '@angular/core'; @Component({ template: '<h1>Hello World!</h1>' }) export class FirstComponent { }
src/app/second/second.component.ts
, which is a little bit more complex:import { Component, OnInit } from '@angular/core'; import { EchoService } from '../echo.service'; import { Observable } from 'rxjs'; @Component({ templateUrl: `second.component.html`, styleUrls: ['./second.component.css'] }) export class SecondComponent implements OnInit { public response: Observable<any>; constructor(private echoService: EchoService) {} public ngOnInit(): void { this.response = this.echoService.makeCall(); } }
makeCall()
. Don’t worry about this: we’ll resolve it below when we create the service.templateUrl
and stylesUrls
). Angular gives us the ability to create HTML templates and CSS stylesheets outside of the component class.src/app/second/second.component.html
, should look:<h1>Second component</h1> <h2>This component injects EchoService</h2> Response is: <span>{{response | async | json}}</span>
src/app/second/second.component.css
, should look like this:span { color: purple; display: block; background: #ccc; padding: 5px; }
second.component.ts
you will see a parameter of type EchoService
. Angular will try to initialize and pass an object of the EchoService
type to our SecondComponent
class when it is initialized. Dependency injection is a technique used to implement an architectural paradigm known as inversion of control (IoC).Observable
type and an async
pipe in the template. Are you familiar with Promises? An Observable is one step further. This asynchronous type emits values pushed to it by other functions. You can reuse it as many times as you want, subscribe multiple listeners, and more (map, filter, pass to another observable, etc.) You can read more about it at the RxJS GitHub page.second.component.html
template is a special Angular mechanism to display our variable in the view template only when it is evaluated. In other words, the value pushed to the HTML at runtime is sent by the EchoService
observable. OnInit
interface and the ngOnInit
lifecycle hook. Interfaces in TypeScript work the same way as interfaces in other languages; if you implement it, you must implement all the methods declared in it. In this particular case, we need to implement the ngOnInit()
method. This method is one of the “Angular lifecycle hooks”, methods called automatically by the Angular engine at different stages of view initialization, destruction and other events. According to the Angular documentation:
ngOnChanges().src/app/echo.service.ts
with the following:https://jsonplaceholder.typicode.com/posts/1
URL.NgModule
, the entry point of our app. Time to take a look at what is going on inside of it and import one more module which is necessary for EchoService
: HttpClientModule
(src/app/app.module.ts
):import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { FirstComponent } from './first/first.component'; import { SecondComponent } from './second/second.component'; import { MenuComponent } from './menu/menu.component'; import { AppRoutingModule } from './app-routing.module'; import { HttpClientModule } from '@angular/common/http'; @NgModule({ declarations: [ AppComponent, FirstComponent, SecondComponent, MenuComponent ], imports: [ BrowserModule, AppRoutingModule, HttpClientModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
ng generate module app-routing --flat --module=app
src/app/app-routing.module.ts
, export RouterModule
from it, and remove redundant code (the declarations
array and CommonModule
import):import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { FirstComponent } from './first/first.component'; import { SecondComponent } from './second/second.component'; @NgModule({ imports: [ RouterModule.forRoot([ { path: '', redirectTo: '/firstComponent', pathMatch: 'full' }, { path: 'firstComponent', component: FirstComponent }, { path: 'secondComponent', component: SecondComponent } ]) ], exports: [ RouterModule ] }) export class AppRoutingModule { }
MenuComponent
file, src/app/menu/menu.component.ts
:import { Component } from '@angular/core'; @Component({ selector: 'app-menu', template: ` <ul> <li><a routerLink="firstComponent">First component</a></li> <li><a routerLink="secondComponent">Second component</a></li> </ul> `, styles: [` :host {margin: 0; padding: 0} ul {list-style-type: none; padding: 0;} li {display: inline-block;} a { border: 1px solid #666666; background: #aaaaaa; border-radius: 5px; box-shadow: 1px 1px 5px black; color: white; font-weight: bold; padding: 5px; text-decoration: none; } li + li a {margin-left: 20px;} `] }) export class MenuComponent { }
routerLink
and routes declared in AppModule? Great! This is how we are linking stuff in Angular: routerLink
is an Angular built-in directive which takes path
as a parameter and matches it with path
declared in RouterModule.forRoot()
. When there is a match, it loads the given component into a <router-outlet>
component, which we are going to add into src/app/app.component.html
right now. Replace the code from this file with:<app-menu></app-menu><router-outlet></router-outlet>
ng serve -o
git clone -b tutorial1_step2 https://github.com/maciejtreder/angular-seo.git angularSeo cd angularSeo/ npm install ng serve -o
ng serve
curl localhost:4200 <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>AngularSeo</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> </head> <body> <app-root class="content"></app-root> <footer>Built with <a href="https://github.com/maciejtreder/ng-toolkit">ng-toolkit</a> by <a href="https://www.maciejtreder.com">maciejtreder.com</a></footer> <script type="text/javascript" src="runtime.js"></script><script type="text/javascript" src="polyfills.js"></script><script type="text/javascript" src="styles.js"></script><script type="text/javascript" src="vendor.js"></script><script type="text/javascript" src="main.js"></script></body> </html>
ng add @ng-toolkit/universal
src/app/app.module.ts
file, which was the entry point of our app. What @ng-toolkit did is remove the bootstrap
attribute from the @NgModule annotation, removed BrowserModule
from imports array and add a couple new there, NgtUniversalModule
and CommonModule
.src/app/app.browser.module.ts
, this is where it resides:import { AppComponent } from './app.component'; import { AppModule } from './app.module'; import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; @NgModule({ bootstrap: [AppComponent], imports: [ BrowserModule.withServerTransition({appId: 'app-root'}), AppModule, ] }) export class AppBrowserModule {}
src/app/app.server.module.ts
. This is an entry point for the code which will be executed on the server side.angular.json
. What we will find there is a new builder added to our project....
”) in a code block indicates a section redacted for brevity.)... "server": { "builder": "@angular-devkit/build-angular:server "options": { "outputPath": "dist/server", "main": "src/main.server.ts", "tsConfig": "src/tsconfig.server.json" } } } ...
src/main.server.ts
, which has been added to our project as well. By looking at this file we can determine the entry point used by the Angular compiler to create the server side build:import { enableProdMode } from '@angular/core'; import { environment } from './environments/environment'; if (environment.production) { enableProdMode(); } export {AppServerModule} from './app/app.server.module';
src/app/app.server.module.ts
, which is the server-side rendering equivalent of src/app/app.browser.module.ts
, the module that bootstraps the app for browser rendering.@ng-toolkit
also made changes in the package.json
file. We have a couple new scripts there:... "build:server:prod": "ng run angularSeo:server && webpack --config webpack.server.config.js --progress --colors", "build:browser:prod": "ng build --prod", "build:prod": "npm run build:server:prod && npm run build:browser:prod", "server": "node local.js" ...
build:prod
, which runs the Angular compiler against the browser and server builds and then creates a server.js
file, using the Webpack configuration added by @ng-toolkit.server
, which is used to start Node.js with the compiled application.npm run build:prod npm run server
curl localhost:8080
... <app-root class="content" _nghost-sc0="" ng-version="6.1.10"><menu _ngcontent-sc0="" _nghost-sc1=""> <ul _ngcontent-sc1=""> <li _ngcontent-sc1=""><a _ngcontent-sc1="" routerlink="firstComponent" href="/firstComponent">First component</a></li> <li _ngcontent-sc1=""><a _ngcontent-sc1="" routerlink="secondComponent" href="/secondComponent">Second component</a></li> </ul></menu> <router-outlet _ngcontent-sc0=""></router-outlet><ng-component><h1>Hello World!</h1></ng-component></app-root> ...
git clone -b tutorial1_step3 https://github.com/maciejtreder/angular-seo.git angularSeo cd angularSeo/ npm install npm run build:prod npm run server
dist
folder to some hosting service (for example, Amazon S3). The “problem” is that we introduced the server-side rendering mechanism, which needs Node.js running on the backend machine. Do we need a cost-consuming EC2 instance running for 24 hours per day?ng add @ng-toolkit/serverless
serverless.yml
file, which provides configuration for the Serverless Framework, and the lambda.js
file, which provides the entry point for the AWS Lambda function. It also makes some minor changes in the framework.serverless.yml
file and replace the value of region:
with the name of your default AWS region. For example, replace ‘eu-central-1 with ‘us-east-2
.package.json
. Make use of one of those:npm run build:serverless:deploy ... endpoints: ANY - https://5k42947te1.execute-api.eu-central-1.amazonaws.com/production/{proxy+} ANY - https://5k42947te1.execute-api.eu-central-1.amazonaws.com/production functions: api: angularseo-production-api
serverless.yml
.curl https://5k42947te1.execute-api.eu-central-1.amazonaws.com/production ... <app-root class="content" _nghost-sc0="" ng-version="6.1.10"><menu _ngcontent-sc0="" _nghost-sc1=""> <ul _ngcontent-sc1=""> <li _ngcontent-sc1=""><a _ngcontent-sc1="" routerlink="firstComponent" href="/production/firstComponent">First component</a></li> <li _ngcontent-sc1=""><a _ngcontent-sc1="" routerlink="secondComponent" href="/production/secondComponent">Second component</a></li> </ul> </menu><router-outlet _ngcontent-sc0=""></router-outlet> <ng-component><h1>Hello World!</h1></ng-component></app-root> ...
git clone -b tutorial1_step4 https://github.com/maciejtreder/angular-seo.git angularSeo cd angularSeo/ npm install npm run build:serverless:deploy