Angular applications follow the Single-Page Application (SPA) architecture, where different views of the web page can be activated using the URL in the browser. Any changes to that URL can be intercepted by the Angular router and translated to routes that can activate a particular Angular component.
Scully is a popular static website generator that is based on the Jamstack architecture. It can cooperate nicely with the Angular router to prerender the content of an Angular application according to each route.
In this chapter, we are going to combine Angular and Scully to create a personal blog. The following topics are going to be covered:
In the old days of web development, client-side applications were highly coupled with the underlying server infrastructure. Much machinery was involved when we wanted to visit the page of a website using a URL.
The browser would send the requested URL to the server, and the server should respond with a matching HTML file for that URL. This was a complicated process that would result in delays and varying round-trip times.
Modern web applications eliminate these problems using the SPA architecture. A client needs to request a single HTML file only once from the server. Any subsequent changes to the URL of the browser are handled internally by the client infrastructure. In Angular, the router is responsible for intercepting in-app URL requests and handling them according to a defined route configuration.
Jamstack is a hot emerging technology that allows us to create fast and secure web applications. It can be used for any application type, ranging from an e-commerce website to a Software as a Service (SaaS) web application or even a personal blog. The architecture of Jamstack is based on the following pillars:
Scully is the first static website generator for Angular that embraces the Jamstack approach. It essentially generates pages of the Angular application during build time to be immediately available when requested.
In this project, we will build a personal blog using the Angular framework and enhance it with Jamstack characteristics using the Scully site generator. Initially, we will scaffold a new Angular application and enable it for routing. We will then create the basic layout of our application by adding some barebones components. As soon as we have a working Angular application, we will add blog support to it using Scully. We will then create some blog posts using Markdown files and display them on the home page of our application. The following diagram depicts an architectural overview of the project:
Figure 2.1 – Project architecture
Build time: 1 hour.
The following software tools are required to complete this project:
Chapter02
folder at https://github.com/PacktPublishing/Angular-Projects-Third-Edition.We will kick off our project by creating a new Angular application from scratch. Execute the following Angular CLI command in a terminal window to create a new Angular application:
ng new my-blog --routing --style=scss
We use the ng new
command to create a new Angular application, passing the following options:
my-blog
: The name of the Angular application that we want to create. The Angular CLI will create a my-blog
folder in the path where we execute the command.
Every command that we run in the terminal window should be run inside this folder.
--routing
: Enables routing in the Angular application.--style=scss
: Configures the Angular application to use the SCSS stylesheet format when working with CSS styles.When we enable routing in an Angular application, the Angular CLI imports several artifacts from the @angular/router npm package in our application:
app-routing.module.ts
file, which is the main routing module of our application:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
AppRoutingModule
into the main module of our application, app.module.ts
:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
We configured our application to use the SCSS stylesheet format. Instead of creating the styles of our application manually, we will use the Bootstrap CSS library:
npm install bootstrap
In the preceding command, we use the npm
executable to install the bootstrap
package from the npm registry.
import
statement at the top of the styles.scss
file that exists in the src
folder of our Angular application:
@import "bootstrap/scss/bootstrap";
The styles.scss
file contains CSS styles that are applied globally in our application. In the previous snippet, we import all the styles from the Bootstrap library into our application. The @import
CSS rule accepts the absolute path of the bootstrap.scss
file as an option without adding the extension.
In the following section, we will learn how to create the basic layout of our blog by creating components, such as the header and the footer.
A blog typically has a header containing all the primary website links and a footer containing copyright information and other useful links. In the world of Angular, both can be represented as separate components.
The header component is used only once since it is added when our application starts up, and it is always rendered as the main menu of the website. In Angular, we typically create a module, named core
by convention, to keep such components or services central to our application. To create the module, we use the generate
command of the Angular CLI:
ng generate module core
The preceding command will create the module in the src\app\core
folder of our application. To create the header component, we will use the same command, passing a different set of options:
ng generate component header --path=src/app/core --module=core --export
The previous command will create all necessary component files inside the src\app\core\header
folder. It will also declare HeaderComponent
in the core.module.ts
file and add it to the exports
property so that other modules can use it:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HeaderComponent } from './header/header.component';
@NgModule({
declarations: [
HeaderComponent
],
imports: [
CommonModule
],
exports: [
HeaderComponent
]
})
export class CoreModule { }
The header component should display the main links of our blog. Open the header.component.html
template file of the header component and replace its content with the following snippet:
<nav class="navbar navbar-expand navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand">Angular Projects</a>
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link">Articles</a>
</li>
<li class="nav-item">
<a class="nav-link">Contact</a>
</li>
</ul>
</div>
</nav>
The footer component can be used more than once in an Angular application. Currently, we want to display it on the main page of our application. In the future, we may want to have it also on a login page that will be available for blog visitors. In such a case, the footer component should be reusable. When we want to group components that will be reused throughout our application, we typically create a module named shared by convention. Use the Angular CLI generate
command to create the module:
ng generate module shared
The previous command will create the shared
module in the src\app\shared
folder. The footer component can now be created using the following command:
ng generate component footer --path=src/app/shared --module=shared --export
The previous command will create all necessary files of the footer component inside the src\app\shared\footer
folder. It will also add FooterComponent
in the declarations
and exports
properties in the shared.module.ts
file:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FooterComponent } from './footer/footer.component';
@NgModule({
declarations: [
FooterComponent
],
imports: [
CommonModule
],
exports: [
FooterComponent
]
})
export class SharedModule { }
The content of the footer component should contain copyright information about our blog.
Let’s see how to add this information to our component:
footer.component.ts
file, add a currentDate
property in the FooterComponent
class, and initialize it to a new Date
object:
currentDate = new Date();
footer.component.html
template file of the footer component and replace its content with the following:
<nav class="navbar fixed-bottom navbar-light bg-light">
<div class="container-fluid">
<p>Copyright @{{currentDate | date: 'y'}}. All
Rights Reserved</p>
</div>
</nav>
The preceding code uses interpolation to display the value of the currentDate
property on the screen. It also uses the built-in date
pipe to display only the year of the current date.
Pipes are a built-in feature of the Angular framework that apply transformations on the view representation of a component property. The underlying value of the property remains intact.
We have already created the essential components of our blog. Now it is time to display them on the screen:
app.module.ts
file, and add CoreModule
and SharedModule
into the imports
property of the @NgModule
decorator:
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
CoreModule,
SharedModule
],
providers: [],
bootstrap: [AppComponent]
})
import
statements at the top of the file for each module:
import { CoreModule } from './core/core.module';
import { SharedModule } from './shared/shared.module';
app.component.html
template file of the main component and replace its content with the following HTML snippet:
<app-header></app-header>
<app-footer></app-footer>
We added the header and the footer component in the preceding snippet by using their CSS selectors.
If we run the serve
command of the Angular CLI to preview the application, we should get the following:
Figure 2.2 – Basic layout
We have already completed the basic layout of our blog application, and it looks great! But the header contains two additional links that we have not covered yet. We will learn how to use routing to activate those links in the following section.
The header component that we created in the previous section contains two links:
The previous links will also become the main features of our application. So, we need to create an Angular module for each one.
When you design your website and need to decide upon the Angular modules that you will use, check out the main menu of the website. Each link of the menu should be a different feature and, thus, a different Angular module.
By convention, Angular modules that contain functionality for a specific feature are called feature modules.
Let’s begin by creating our contact feature:
ng generate module contact
contact
module:
ng generate component contact --path=src/app/contact --module=contact --export --flat
We pass the --flat
option to the generate
command so that the Angular CLI will not create a separate folder for our component, as in previous cases. The contact
component will be the only component in our module, so there is no point in having it separately.
contact.component.html
file and add the following HTML content:
<div class="card mx-auto text-center border-light" style="width: 18rem;">
<img src="assets/angular.png" class="card-img-top"
alt="Angular logo">
<div class="card-body">
<h5 class="card-title">Angular Projects</h5>
<p class="card-text">
A personal blog created with the Angular
framework and the Scully static site generator
</p>
<a href="https://angular.io/" target="_blank"
class="card-link">Angular</a>
<a href="https://scully.io/" target="_blank"
class="card-link">Scully</a>
</div>
</div>
In the preceding code, we used the angular.png
image, which you can find in the src\assets
folder of the project from the accompanying GitHub repository.
The assets
folder in an Angular CLI project is used for static content such as images, fonts, or JSON files.
We have already created our contact feature. The next step is to add it to the main page of our Angular application:
app-routing.module.ts
file and add a new route configuration object in the routes
property:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ContactComponent } from './contact/contact.component';
const routes: Routes = [
{ path: 'contact', component: ContactComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
The preceding code indicates that when the URL of the browser points to the contact
path, our application will activate and display ContactComponent
on the screen. The routes
property of a routing module contains the routing configuration of the respective feature module. It is an array of route configuration objects where each one defines the component class and the URL path that activates it.
ContactModule
in the imports
array of the @NgModule
decorator of AppModule
to be able to use it:
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
CoreModule,
SharedModule,
ContactModule
],
providers: [],
bootstrap: [AppComponent]
})
Do not forget to add the respective import
statement for ContactModule
at the top of the file.
ContactComponent
, need a place where they can be loaded. Open the app.component.html
file and add the <router-outlet>
directive:
<app-header></app-header>
<div class="container">
<router-outlet></router-outlet>
</div>
<app-footer></app-footer>
Now, we need to wire up the route configuration that we created with the actual link on the header component:
header.component.html
file and add the routerLink
directive to the respective anchor HTML element:
<li class="nav-item">
<a routerLink="/contact" routerLinkActive="active"
class="nav-link">Contact</a>
</li>
In the preceding snippet, the routerLink
directive points to the path
property of the route configuration object. We have also added the routerLinkActive
directive, which sets the active
class on the anchor element when the specific route is activated.
Notice that the value of the routerLink
directive contains a leading /
, whereas the path
property of the route configuration object that we defined does not. According to the case, omitting the /
would give a different meaning to the route.
routerLink
and routerLinkActive
directives are part of the Angular Router package. We need to import RouterModule
in the core.module.ts
file to use them:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HeaderComponent } from './header/header.component';
import { RouterModule } from '@angular/router';
@NgModule({
declarations: [
HeaderComponent
],
imports: [
CommonModule,
RouterModule
],
exports: [
HeaderComponent
]
})
export class CoreModule { }
We are now ready to preview our new contact page! If we run the application using ng serve
and click on the Contact link, we should see the following output:
Figure 2.3 – Contact page
In the following section, we will build the functionality for the Articles link of the header in our blog.
The feature that is responsible for displaying articles in our blog will be the articles
module. It will also be the module that connects the dots between Angular and Scully. We will use the generate
command of the Angular CLI to create that module:
ng generate module articles --route=articles --module=app-routing
In the previous command, we pass some additional routing options:
--route
: Defines the URL path of our feature--module
: Indicates the routing module that will define the route configuration object that activates our featureThe Angular CLI performs additional actions, instead of just creating the module, upon executing the command:
src\app\articles
folder that will be activated by default from a route navigation object. It is the landing page of our feature, and it will display a list of blog posts, as we will see in the Displaying blog data on the home page section.articles-routing.module.ts
that contains the routing configuration of our module.The articles-routing.module.ts
file contains the routing configuration for the articles
module:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ArticlesComponent } from './articles.component';
const routes: Routes = [{ path: '', component: ArticlesComponent }];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ArticlesRoutingModule { }
It imports RouterModule
using the forChild
method to pass the routing configuration to the Angular router. If we take a look at the main routing module of the application, we will see that it follows a slightly different approach:
app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ContactComponent } from './contact/contact.component';
const routes: Routes = [
{ path: 'contact', component: ContactComponent },
{ path: 'articles', loadChildren: () => import('./articles/articles.module').then(m => m.ArticlesModule) }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
The forChild
method is used in feature modules, whereas the forRoot
method should be used only in the main application module.
The route configuration of the articles
module contains only one route that activates ArticlesComponent
. The path of the route is set to an empty string to indicate that it is the default route of the routing module. It essentially means that ArticlesComponent
will be activated whenever that module is loaded. But how is the articles
module loaded in our application?
The second route of the main routing module contains a route configuration object that does not activate a component but rather a module. It uses the loadChildren
method to load ArticlesModule
dynamically when navigation triggers the articles
path.
The import
function in the loadChildren
property accepts the relative path of the TypeScript module file without the extension.
The previous approach is called lazy loading and improves the startup and the overall performance of an Angular application. It creates a separate bundle for each lazy-loaded module, which is loaded upon request, reducing the final bundle size and the memory consumption of your application. Let’s wire up the new route to our header component:
header.component.html
file and add the following routerLink
and routerLinkActive
directives to the Articles
anchor HTML element:
<li class="nav-item">
<a routerLink="/articles" routerLinkActive="active"
class="nav-link">Articles</a>
</li>
ng serve
and use your favorite browser to preview your application.Figure 2.4 – Lazy loading Angular module
Among other requests, you should see one named src_app_articles_articles_module_ts.js
. It is the bundle of the lazy-loaded articles module that was loaded when you clicked on the Articles link.
We are now ready to convert our amazing Angular application into a professional blog website.
Before we move on, let’s add some additional routes to the app-routing.module.ts
file:
const routes: Routes = [
{ path: 'contact', component: ContactComponent },
{ path: 'articles', loadChildren: () => import('./articles/articles.module').then(m => m.ArticlesModule) },
{ path: '', pathMatch: 'full', redirectTo: 'articles' },
{ path: '**', redirectTo: 'articles' }
];
We added a default route to automatically redirect our blog users to the articles
path upon visiting the blog. Additionally, we created a new route configuration object with its path set to **
that also navigates to the articles
path. The **
syntax is called the wildcard route, and it is triggered when the router cannot match a requested URL with a defined route.
Define the most specific routes first and then add any generic ones, such as the default and the wildcard routes. The Angular router parses the route configuration in the order that we define and follows a first-match-wins strategy to select one.
We have already enabled and configured routing in our Angular application. In the following section, we will establish the infrastructure needed to add blogging capabilities to our application.
Our application currently does not have any specific logic regarding blog posts. It is a typical Angular application that uses routing. However, by adding a routing configuration, we have established the foundation for adding blog support using Scully.
Scully needs at least one route defined in an Angular application to work correctly.
First, we need to install Scully in our application.
We will use the install
command of the npm CLI to install Scully in our Angular application:
npm install @scullyio/init @scullyio/ng-lib @scullyio/scully @scullyio/scully-plugin-puppeteer --force
The preceding command downloads and installs all the necessary npm packages for Scully to work correctly in our Angular application.
The Scully library is not fully compatible with Angular 16, as of this writing. In the preceding command we use the --force
option to ignore any warnings that come from the Angular version incompatibility.
Open the app.module.ts
file and import ScullyLibModule
:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ContactModule } from './contact/contact.module';
import { CoreModule } from './core/core.module';
import { SharedModule } from './shared/shared.module';
import { ScullyLibModule } from '@scullyio/ng-lib';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
CoreModule,
SharedModule,
ContactModule,
ScullyLibModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
ScullyLibModule
is the main module of the Scully library; it contains various Angular services and directives that Scully will need.
Create a configuration file for the Scully library in the root folder of the Angular CLI workspace with the following contents:
scully.my-blog.config.ts
import { ScullyConfig } from '@scullyio/scully';
export const config: ScullyConfig = {
projectRoot: "./src",
projectName: "my-blog",
outDir: './dist/static',
routes: {
}
};
The configuration file contains information about our Angular application that Scully will need along the way:
projectRoot
: The path containing the source code of the Angular applicationprojectName
: The name of the Angular applicationoutDir
: The output path of the Scully-generated files
The Scully output path must be different from the path that the Angular CLI outputs for the bundle of your Angular application. The latter can be configured in the angular.json
file.
routes
: It contains the route configuration that will be used for accessing our blog posts. Scully will populate it automatically, as we will see in the following section.Since we have installed Scully successfully in our Angular application, we can now configure it to initialize our blog.
Scully provides a specific Angular CLI schematic for initializing an Angular application, such as a blog, by using Markdown (.md) files:
ng generate @scullyio/init:markdown --project my-blog
The previous command will start the configuration process of our blog by going through a list of questions (default values are shown inside parentheses):
posts
as the name of the blog module:
What name do you want to use for the module? (blog)
This will create a new Angular module named posts
.
What slug do you want for the markdown file? (id)
The slug is a unique identifier for each post, and it is defined in the route configuration object of the module.
mdfiles
as the path that Scully will use to store our actual blog post files:
Where do you want to store your markdown files?
This will create an mdfiles
folder inside the root path of our Angular CLI project. By default, it will also create a blog post for our convenience. We will learn how to create our own in the Displaying blog data on the home page section.
posts
as the name of the route for accessing our blog posts:
Under which route do you want your files to be requested?
The name of the route is the path
property of the route configuration object that will be created.
Scully performs various actions upon executing the preceding commands, including the creation of the routing configuration of the posts
module:
posts-routing.module.ts
import {NgModule} from '@angular/core';
import {Routes, RouterModule} from '@angular/router';
import {PostsComponent} from './posts.component';
const routes: Routes = [
{
path: ':id',
component: PostsComponent,
},
{
path: '**',
component: PostsComponent,
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class PostsRoutingModule {}
The path
property for the first route is set to :id
and activates PostsComponent
. The colon character indicates that id
is a route parameter. The id
parameter is related to the slug
property defined earlier in the Scully configuration. Scully works by creating one route for each blog post that we create. It uses the route configuration of the posts
module and the main application module to build the routes
property in the Scully configuration file:
routes: {
'/posts/:id': {
type: 'contentFolder',
id: {
folder: "./mdfiles"
}
},
}
PostsComponent
is the Angular component that is used to render the details of each blog post. The template file of the component can be further customized according to your needs:
posts.component.html
<h3>ScullyIo content</h3>
<hr>
<!-- This is where Scully will inject the static HTML -->
<scully-content></scully-content>
<hr>
<h4>End of content</h4>
You can customize all content in the previous template file except the <scully-content></scully-content>
line, which is used internally by Scully.
At this point, we have completed the installation and configuration of Scully in our Angular application. It is now time for the final part of the project! In the next section, we will get Angular and Scully to cooperate and display blog posts in our Angular application.
We would like our users to see the list of available blog posts as soon as they land on our blog website. According to the default route path that we have defined, ArticlesComponent
is the landing page of our blog. Scully provides ScullyRoutesService
, an Angular service that we can use in our components to get information about the routes that it will create according to the blog posts. Let’s put this service into action on our landing page:
articles.component.ts
file and modify the import
statements as follows:
import { Component, OnInit } from '@angular/core';
import { ScullyRoute, ScullyRoutesService } from '@scullyio/ng-lib';
import { Observable, map } from 'rxjs';
OnInit
interface to the list of implemented interfaces of the ArticlesComponent
class:
export class ArticlesComponent implements OnInit {
}
ScullyRoutesService
in the constructor
of the ArticlesComponent
class:
constructor(private scullyService: ScullyRoutesService) { }
posts$: Observable<ScullyRoute[]> | undefined;
ngOnInit
method:
ngOnInit(): void {
this.posts$ = this.scullyService.available$.pipe(
map(posts => posts.filter(post => post.title))
);
}
articles.component.html
file and add the following HTML code:
<div class="list-group mt-3">
<a *ngFor="let post of posts$ | async"
[routerLink]="post.route" class="list-group-item
list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{post.title}}</h5>
</div>
<p class="mb-1">{{post['description']}}</p>
</a>
</div>
There are many Angular techniques involved in the previous steps, so let’s break them down piece by piece.
When we want to use an Angular service in a component, we just need to ask for it from the Angular framework. How? By adding it as a property in the constructor
of the component. The component does not need to know anything about how the service is implemented.
The ngOnInit
method is part of the OnInit
interface, which is implemented by our component. It is called by the Angular framework when a component is initialized and provides us with a hook to add custom logic to be executed.
Angular services that provide initialization logic to a component should be called inside the ngOnInit
method and not in the constructor
because it is easier to provide mocks about those services when unit testing the component.
The available$
property of ScullyRoutesService
is called an observable and returns all the available routes that were generated from Scully when we subscribe to it. To avoid displaying routes other than those related to blog posts, such as the contact
route, we filter out the results from the available$
property.
In the component template, we use the *ngFor
Angular built-in directive and the async
pipe to subscribe to the posts$
observable inside HTML. We can then access each item using the post
template reference variable and use interpolation to display title
and description
.
Finally, we add a routerLink
directive to each anchor element to navigate to the respective blog post when clicked. Notice that routerLink
is surrounded by []
. The []
syntax is called property binding, and we use it when we want to bind the property of an HTML element to a variable. In our case, we bind the routerLink
directive to the route
property of the post
variable.
Now that we have finally completed all the pieces of the puzzle, we can see our blog website in action:
build
command of the Angular CLI to build our Angular application:
ng build
npx scully --project my-blog
The preceding command will create a scully-routes.json
file inside the src\assets
folder. It contains the routes of our Angular application and is needed by the Scully runtime.
Running the Scully executable for the first time will prompt you to collect anonymous errors to improve its services.
npx scully serve --project my-blog
The preceding command will start two web servers: one that contains the static prerendered version of our website built using Scully and another that is the Angular live version of our application:
Figure 2.5 – Serving our application
If we open our browser and navigate to http://localhost:1668
, we will not see any blog posts. A blog post created with Scully is not returned in the available$
property of ScullyRoutesService
unless we publish it. To publish a blog post, we do the following:
mdfiles
folder that Scully created and open the only .md
file that you will find. The name and contents may vary for your file because it is based on the date Scully created it:
---
title: 2023-06-22-posts
description: 'blog description'
published: false
slugs:
- ___UNPUBLISHED___lj738su6_7mqWyfNdmNCwovaCCi2tZItsDKMPJGcG
---
# 2023-06-22-posts
Scully has defined a set of properties between the closing and ending ---
lines at the top of the file representing metadata about the blog post. You can also add your own as key-value pairs.
slugs
property and set the published
property to true
:
---
title: 2023-06-22-posts
description: 'blog description'
published: true
---
# 2023-06-22-posts
npx scully --project my-blog
We need to execute the previous command every time we make a change in our blog-related files.
npx scully serve --project my-blog
command and navigate to preview the generated website.We can now see one blog post, the default one that was created when we installed Scully. Let’s create another one:
generate
command of the Angular CLI:
ng generate @scullyio/init:post --name="Angular and Scully"
In the preceding command, we use the @scullyio/init:post
schematic, passing the name of the post that we want to create as an option.
mdfiles
:
What's the target folder for this post? (blog)
angular-and-scully.md
inside the specified folder. Open that file and update its content to be the same as the following:
---
title: 'Angular and Scully'
description: 'How to build a blog with Angular and Scully'
published: true
---
# Angular and Scully
Angular is a robust JavaScript framework that we can use to build excellent and performant web applications.
Scully is a popular static website generator that empowers the Angular framework with Jamstack characteristics.
You can find more about them in the following links:
- https://angular.io
- https://scully.io
- https://www.jamstack.org
npx scully --project my-blog
to create a route for the newly created blog post. Scully will also update the scully-routes.json
file with the new route.If we preview our application now, it should look like the following:
Figure 2.6 – List of blog posts
If we click on one of the blog items, we will navigate to the selected blog post. The content that is currently shown on the screen is a prerendered version of the blog post route:
Figure 2.7 – Blog post details
To verify that, navigate to the dist
folder of your Angular project, where you will find two folders:
my-blog
: This contains the Angular live version of our application. When we execute the ng build
Angular CLI command, it builds our application and outputs bundle files in this folder.static
: This contains a prerendered version of our Angular application generated from Scully when we run the npx scully --project my-blog
command.If we navigate to the static
folder, we will see that Scully has created one folder for each route of our Angular application. Each folder contains an index.html
file, which represents the component that is activated from that route.
The contents of the index.html
file are auto-generated by Scully, and behave as if we run our application live and navigate to that component.
Now you can take your Angular application, upload it to the CDN or web server of your choice, and you will have your blog ready in no time! All you will have to do then will be to exercise your writing skills to create excellent blog content.
In this chapter, we learned how to combine the Angular framework with the Scully library to create a personal blog.
We saw how Angular uses the built-in router package to enhance web applications with in-app navigation. We also learned how to organize an Angular application into modules and how to navigate through these.
We introduced Jamstack to our Angular application using the Scully library and saw how easy it is to convert our application into a prerendered blog. We used the Scully interface to create some blog posts and display them on the screen.
In the following chapter, we will investigate another exciting feature of the Angular framework, forms. We are going to learn how to use them and build an issue-tracking system.
Let’s take a look at a few practice questions:
assets
folder in an Angular CLI application?Here are some links to build upon what we learned in this chapter:
Where there is an eBook version of a title available, you can buy it from the book details for that title. Add either the standalone eBook or the eBook and print book bundle to your shopping cart. Your eBook will show in your cart as a product on its own. After completing checkout and payment in the normal way, you will receive your receipt on the screen containing a link to a personalised PDF download file. This link will remain active for 30 days. You can download backup copies of the file by logging in to your account at any time.
If you already have Adobe reader installed, then clicking on the link will download and open the PDF file directly. If you don't, then save the PDF file on your machine and download the Reader to view it.
Please Note: Packt eBooks are non-returnable and non-refundable.
Packt eBook and Licensing When you buy an eBook from Packt Publishing, completing your purchase means you accept the terms of our licence agreement. Please read the full text of the agreement. In it we have tried to balance the need for the ebook to be usable for you the reader with our needs to protect the rights of us as Publishers and of our authors. In summary, the agreement says:
If you want to purchase a video course, eBook or Bundle (Print+eBook) please follow below steps:
Our eBooks are currently available in a variety of formats such as PDF and ePubs. In the future, this may well change with trends and development in technology, but please note that our PDFs are not Adobe eBook Reader format, which has greater restrictions on security.
You will need to use Adobe Reader v9 or later in order to read Packt's PDF eBooks.
Packt eBooks are a complete electronic version of the print edition, available in PDF and ePub formats. Every piece of content down to the page numbering is the same. Because we save the costs of printing and shipping the book to you, we are able to offer eBooks at a lower cost than print editions.
When you have purchased an eBook, simply login to your account and click on the link in Your Download Area. We recommend you saving the file to your hard drive before opening it.
For optimal viewing of our eBooks, we recommend you download and install the free Adobe Reader version 9.