We use cookies to make your viewing experience better. By accepting you consent, you agree to our Cookie policy

Accept
Improve your Craft CMS skills

How To Build A Craft CMS Site Using Angular

10 min read
Shape April 2022 HR 197

Integrating a Craft CMS backend with an Angular frontend enables building full-stack web applications efficiently. However, connecting these technologies can be challenging for developers new to either platform. This guide provides targeted insights on architecting a Craft + Angular stack from authentication to deployment, equipping you with the exact steps needed to avoid pitfalls and accelerate development. Read on to master melding these technologies into a high-performing site.

Integrating Angular frontends with Craft CMS backends enables building full-stack web applications efficiently. Follow best practices like encapsulating API logic in services, implementing route guards for security, optimizing performance, and planning robust deployment to seamlessly connect these technologies into high-performing sites.

Getting Started with Angular

Angular Overview and Key Concepts

Angular is a popular open-source JavaScript framework for building web applications. At its core, Angular focuses on making the development of complex, data-driven apps easier through its powerful features and design patterns.

Some of the key concepts in Angular include:

  • Components - The building blocks of an Angular app. Components control sections of the UI and are easily reusable.

  • Templates - HTML views that work with components to render UI. Templates display data and handle user events.

  • Services - Provide reusable business logic that components can leverage. Services encapsulate backend access and help manage state.

  • Dependency Injection - A design pattern where dependencies like services are injected into components via a injector service. This promotes loose coupling.

  • Modules - Logical groupings of components, services etc. Apps are divided into modules managing different functionality.

The Angular CLI tool generates boilerplate code and performs tasks like building, testing and deployment. Overall, Angular apps are well-organized with separate folders for components, services, styles etc. The strong architecture enables large scale app development.

Learning these fundamental concepts will provide you with a solid base before diving deeper into Angular. Things like components, templates and modules appear frequently when developing so understanding them early is key.

Setting Up an Angular Development Environment

Before starting Angular development, you need to set up an environment with the required tools. Here are the steps:

First, install the latest LTS version of Node.js which includes the npm package manager. Angular itself is written in TypeScript so Node is necessary for compiling to plain JavaScript that browsers understand.

After installing Node + npm, use npm to globally install the Angular CLI tool. The CLI handles initializing projects, generating boilerplate code like components and services, building and testing. To scaffold a new app, run ng new my-app and the CLI will set up a starter project.

The generated app contains the src/ folder with your TypeScript application code. It also includes the angular.json config file, package.json with dependencies and more. You can tweak these configs to customise build options, add plugins etc.

Angular uses TypeScript which is a typed superset of JavaScript. It compiles to vanilla JS but has advantages like type checking and improved autocompletion. TypeScript configuration like strictness levels goes in tsconfig.json.

Optionally use an editor like VS Code for coding. You'll want extensions for code linting, IntelliSense and debugging. Also install browsers like Chrome for testing during development.

With Node, CLI, a project set up and editor installed you have an Angular environment ready! Familiarise yourself with the folder structure and core config files generated.

Building Your First Angular App

Let's build a simple Angular application to see core concepts in action. We'll create a book tracking app displaying a list of books and their details. Follow along to get hands-on Angular experience.

First generate a book component using the CLI which provides the TypeScript class and other boilerplate:

ng generate component book

This creates the book folder with files like book.component.ts. In app.module.ts it declares the BookComponent.

Next in book.component.ts define a books array and instantiate some book objects with properties like title and author. This sample data will drive our UI.

In the template book.component.html use *ngFor to loop through the books array and display each book's title and author. The template accesses the component's books data through its class.

<div *ngFor="let book of books">

<h3>{{book.title}}</h3>

<p>Author: {{book.author}}</p>

</div>

The expressions in {{}} brackets display property values. This demonstrates Angular's powerful data binding between the TypeScript code and template.

To allow viewing details for each book, route to a BookDetailsComponent. Use the router-outlet in AppComponent to render this component on that path.

Pass the selected book as a route parameter and show its details in the BookDetailsComponent template.

With create, read and routing set up, you now have a basic Angular application! Using components, data binding and routing you can expand this into a more complex app.

This hands-on tutorial should provide practical knowledge for starting your own Angular project. Use the CLI to generate components and services and wire them together via routing, event handling and dependencies.

Connecting Angular with Craft CMS

Consuming the Craft CMS API in Angular

To connect an Angular frontend to a Craft CMS backend, you need to consume the Craft API in your Angular code. The API exposes CRUD endpoints for interacting with content, assets, users and more.

Start by installing the HTTPClient module which provides services for making HTTP requests. Import this into your root app module. You can then inject the HTTPClient service into any component or service.

To authenticate API requests, pass an Authorization header with a valid Craft CMS bearer token. Generate a token for your user account under Settings -> Users in the CMS.

Here is sample code for getting entries from the API using HTTPClient:

constructor(private http: HTTPClient) {}


getEntries() {

return this.http.get('/api/entries', {

headers: {

Authorization: `Bearer ${token}`

}

});

}


The HTTPClient methods return Observables which represent asynchronous streams of data. Subscribe to the Observable to process the response when it arrives.

For error handling, pipe the Observable through catchError() to gracefully handle any HTTP errors. Also display loaders and alerts while waiting for responses.

To integrate with services, encapsulate the API logic into injectable Angular services. Components can then consume these services to get CMS data without knowing the implementation details.

Overall, rely on HTTPClient for requests, Observables for streaming responses, and services for clean architecture. Follow Craft CMS API documentation for working with entries, assets, categories and more.

Displaying Craft CMS Content in Angular

Retrieving content from Craft CMS via the API is only the first step. Next, you need to display that content within your Angular application.

For example, get a list of blog entries from the API. Loop through the entries in your component class, storing them in a variable.

Then in the template, use *ngFor on that variable to output the entry titles, dates, excerpts, images etc. Bind values through expressions like {{entry.title}}.

To link between pages, use routerLink in the template to navigate between routes defined in your Angular app. For example:

<a [routerLink]="['/blog', entry.slug]">{{entry.title}}</a>

For navigation, make an API call to get all entries with a "navigation" tag. Output these into a <nav> component rendering the navigation markup.

When new CMS content gets published, use services to refresh data in your components so users immediately see the updated content.

Follow Craft CMS naming conventions for taxonomies, fields etc. to easily integrate the API responses with your Angular code.

With techniques like displaying entry loops, binding values and programmatic navigation you can build a seamless Angular frontend for your headless Craft CMS backend.

Client-Side Routing with Angular

Since Angular handles templating and routing on the client-side, you need to sync its router with your Craft CMS structure.

Define Angular routes corresponding to your CMS templates and entries. For example, create a post route for the blog post template. Use :slug for dynamic post URLs.

Link between static routes like /about and dynamic entry routes. To navigate on button click or event, inject the Router service and call its navigate() method.

Implement route guards such as CanActivate to check user authentication before accessing restricted routes. For example:

canActivate(): Observable<boolean> {


return this.authService.getUser()

.pipe(

map(user => {

if (user) { return true }


this.router.navigate(['/login']);

return false;

})

);

}


This checks if a user exists, redirecting to /login if not. Return an Observable<boolean> from canActivate().

When linking between pages, use relative links without the base URL. This prevents broken links when the app URL changes.

Lazy load non-critical routes via loadChildren to boost performance. Also prefetch links likely to be clicked.

Follow routing best practices like maintaining linear page flow and keeping UI consistent between routes. With clean routing, you can build a seamless SPA powered by Craft's flexible content.

Developing Angular Components

Angular Components Overview

Components are the main building blocks in Angular applications. They control portions of the screen (known as views) and enable you to split the UI into reusable pieces.

An Angular component consists of:

  • A TypeScript class with the application logic

  • An HTML template defining the view

  • Component-specific styles

  • Metadata like selector and inputs

The class and template are linked together via databinding. For example, loop through an array in the class and output it using *ngFor in the template.

Components use encapsulation so their styles only apply to that component, not globally. Lifecycle hooks like ngOnInit allow running initialization logic.

Components also receive data through @Input() decorators and communicate events outwards using @Output(). This makes them reusable across the application.

Understanding these key characteristics of Angular components like databinding, encapsulation and lifecycle hooks will help you develop robust UI components.

Creating Reusable Components

For an application with many screens, it's best to break the UI down into reusable component pieces. Here are some tips for building reusable Angular components:

  • Make components small and focused on a single task like a button, modal or grid.

  • Use @Input() and @Output() to receive data and emit events. Make components configurable and decoupled.

  • Encapsulate any services or HTTP logic in providers and inject them as dependencies.

  • Emit events and delegate complex logic upwards rather than handling it internally.

  • Avoid component coupling by only interacting via @Inputs/@Outputs, services or message passing.

  • Implement OnPush change detection and avoid direct DOM access for optimal performance.

  • Add CSS encapsulation so styles don't clash when reusing components.

Following best practices like single responsibility and loose coupling will maximize component reusability.

Component Communication Patterns

For components to interact, you need to pass data between them. Some common patterns include:

  • Use @Input() and @Output() for parent-child communication. @Input() receives data from parent, @Output() emits events upward.

  • Share data via services. For unrelated components, inject a shared service that manages state.

  • Leverage RxJS Observables for state change events. An RxJS Subject broadcasts updates.

  • Pass immutable data copies using OnPush change detection rather than mutating directly.

  • For sibling components, listen for events from a shared parent component acting as bus.

  • Follow Container and Presentational component patterns, keeping child components reusable.

Angular offers flexibility in component communication. Use a combination of approaches like inputs/outputs, services and RxJS for clear data flow between components. Follow best practices to build a maintainable, extensible application.

Advanced Angular Concepts

State Management with RxJS

Managing state is crucial as applications scale in complexity. Angular provides RxJS Observables as a robust state management solution. Observables represent asynchronous streams of data that can dispatch multiple values over time. This differs from promises which return only a single value.

An important aspect is that Observables do not begin emitting values until you subscribe to them. Inside the subscribe() method you can define logic to handle each emission from the stream. This allows you to observe a flow of data and react accordingly. To transform and compose Observable streams, RxJS offers numerous operators like map(), filter(), concat(), merge() and many more. These operators enable complex asynchronous programming by manipulating, combining and filtering streams.

Some common use cases for Observables include using a Subject or BehaviorSubject to act as an event bus broadcasting updates across components, caching HTTP responses and invalidating the cache at intervals to fetch fresh data, streaming user interaction events like clicks to trigger UI updates, and composing multiple Observable chains together using the RxJS pipe() method.

Overall, RxJS and Observables empower decoupled, reactive Angular applications by enabling communication via observable streams rather than direct function calls. This pub/sub pattern reduces coupling and mitigates difficult state management bugs.

Angular Directives

Angular directives provide a way to declaratively manipulate the DOM. Some commonly used built-in directives include *ngIf for conditionally creating or destroying elements based on a boolean expression, *ngFor for repeating elements by looping through a collection, [ngClass] for dynamically setting CSS classes on an element, and [ngStyle] for programmatically setting CSS styles. These core directives apply logic directly in the template rather than imperatively in code. For example, you can loop through a list of items with *ngFor syntax like: <div *ngFor="let item of list"> {{ item }} </div>. This declarative approach helps keep your presentation logic in the template.

In addition to built-in directives, you can also create custom directives using the @Directive decorator. This allows encapsulating reusable DOM manipulation behaviour that can be applied declaratively. For instance, you could make a highlighting directive like: @Directive({selector: '[highlight]'}).

Inside it sets the element background color upon instantiation. Now the highlight behavior can be added to any element through the HTML attribute rather than procedural code. Directives are a powerful way to extend HTML with custom functionality.

Angular Pipes

Similar to directives, Angular pipes provide a way to declaratively transform output in templates. Some commonly used built-in pipes include the DatePipe for formatting dates, UpperCasePipe for converting text to uppercase, CurrencyPipe for displaying currency values, and JsonPipe for turning JSON into a string. You apply pipes using the “pipe” (|) symbol like {{ book.price | currency }}, which displays the price formatted as currency. Multiple pipes can also be chained together for sequential transformations.

For unique data transformations, you can define custom pipes using the @Pipe decorator. For example, you could create a filter pipe accepting an items array and term, filtering the array to only items containing that term. Now you can use your custom filter pipe in templates just like the built-in ones. Pipes are a useful way to handle transformations in a declarative, reusable way while keeping component logic clean.

Angular Forms

Template-Driven Forms

Template-driven forms allow building forms directly in the component template using directives like ngModel.

First define the inputs with a name attribute to register them with Angular forms. For example:

<input name="email" ngModel>

Now bind the input value to the component class using the ngModel directive. This enables two-way data binding between the template and the component model.

For validation, add directives like required, minlength or pattern as attributes. Angular will validate based on these and update error states automatically.

Submit events can be listened to via (ngSubmit) on the <form>. In the handler, access the form using @ViewChild and values via form.value.

Template-driven forms are useful for simple scenarios with minimal validation. However, they lack flexibility as logic is tightly coupled to the template.

Reactive Forms

Reactive forms take a model-driven approach for more control and testability. Forms are defined programmatically in the component class.

Import ReactiveFormsModule and inject FormBuilder. Build the form using FormGroup containing FormControls.

For example:

form = new FormGroup({

name: new FormControl(''),

email: new FormControl('')

});


Access values and validate via code like form.get('email').errors.

Reactive forms are more verbose but provide greater flexibility. Define custom validators, compose complex hierarchies and manipulate forms entirely through code.

Reactive forms also integrate better with testing and patterns like Redux. Use them for complex scenarios with dynamic form generation and validation.

Submitting and Processing Forms

When submitting forms in Angular, both client and server-side processing need to be handled.

Listen for form submit events in the template using (ngSubmit). In the handler, perform any client-side validation first.

Next, make an HTTP request to your server API submitting the serialized form data. Display a loading indicator while waiting for the response.

Upon success, redirect or refresh UI based on the expected workflow. Clear form state to reset the model.

To handle errors, parse the server response to extract any validation errors. Display them back to the user bound to the relevant fields. For example:

<div *ngIf="email.errors.required">Email is required</div>

This provides user-friendly validation messages. Follow similar patterns for any client-side errors.

Overall, planning form submission flow and lifecycle handling results in a smooth user experience. Carefully validate and process submissions both client and server-side.

User Authentication and Authorization

Implementing User Authentication

To enable user authentication in Angular, provide functionality for registration, login, and using access tokens.

For registration, create a form to capture credentials like email and password. Make a POST request to your API to create the user resource.

For login, build a form to collect email and password again. Submit these to the API's login endpoint which issues a JWT or similar access token if valid.

Store this token in browser storage or a state management solution like Redux. Send the token in an Authorization header when making secure API requests. The API will validate it and allow access if valid.

Use a library like ngx-auth to handle boilerplate authentication logic. Ensure to hash passwords securely before sending to API.

Implement 'Remember Me' functionality by setting longer token expiration. Log users out on browser close for security. Provide a logout button to destroy server-side sessions.

Overall, rely on access tokens issued by the API for handling user identity and access. Refresh tokens as needed before expiration.

Securing Angular Routes

To restrict access for unauthorized users, implement route protection in Angular using guards.

First define public and secured routes. Public routes don't require authentication, secured routes protect premium or user-specific content.

Use an auth guard that checks for a valid access token before allowing navigation to protected routes. Redirect to login if unauthenticated.

For example:

@Injectable()

export class AuthGuard implements CanActivate {

constructor(private auth: AuthService, private router: Router) {}


canActivate(

route: ActivatedRouteSnapshot,

state: RouterStateSnapshot

): Observable<boolean> {

return this.auth.user$.pipe(

map(user => {

if (user) { return true }


this.router.navigate(['login']);

return false;

})

)

}

}

Register AuthGuard as a provider and add to secured route definitions.

You can also create role-based guards, only allowing admins to access admin views for example. Follow similar patterns with granular access policies.

Managing User Sessions

When users log in, you need to verify their identity and manage that session state.

On login, the API returns a JWT token encoding the user ID. Your application stores this token to maintain an authenticated session.

Before making secured requests, check if a valid token exists and hasn't expired. If so, fetch a fresh token from the API before proceeding.

Manage this global auth state using RxJS Subjects, Redux or services. Check auth status before accessing restricted data to avoid errors.

For auto logouts, store token expiry time on login. Poll to periodically check if the token is nearing expiration, logging the user out to force reauthentication if needed.

Handle cases like connectivity loss to avoid blindly using an expired token. Revalidate on reconnect before allowing sensitive actions.

Careful session management provides a seamless user experience without constant reauthentication. Verify identity using access tokens and handle expiration and cases like connectivity loss gracefully.

Optimizing and Deploying Angular Apps

Improving Performance

As Angular applications increase in complexity, performance optimization becomes crucial to deliver a smooth user experience. Rather than upfront bundle loading, lazy loading can be used to load non-critical modules only when needed, reducing the initial payload. Relatedly, prefetching likely clicked links using a service worker accelerates subsequent navigation.

Caching and cache invalidation techniques avoid duplicate API requests. Compiling during the build process instead of runtime via Ahead-of-Time compilation speeds up boot times.

Minification, uglification, and tree shaking all reduce file size for faster loading. Enabling gzip compression on server requests also minimizes network payloads. For efficient change detection, the OnPush strategy detects changes in applicable components more precisely.

Following general performance best practices like avoiding slow DOM manipulation and keeping components lightweight also improves speed. Tools like Lighthouse and the Chrome profiler analyze metrics like load times and bundle sizes to identify and address bottlenecks.

Server-side rendering delivers the initial HTML from the server for faster first load. Carefully applying these web performance techniques results in smooth, optimized Angular apps ready to scale.

Angular Security Best Practices

With prevalent web security threats, following best practices is key for secure Angular apps. Sanitizers and DOM security libraries prevent cross-site scripting by sanitizing untrusted inputs before display. Rigorously validating, sanitizing, and encoding all API data protects against injection attacks.

Effective use of Cross-Origin Resource Sharing (CORS) prevents unauthorized cross-origin access. Transmitting authentication tokens and sensitive data over HTTPS connections enhances security. Access controls like role-based access and principle of least privilege carefully restrict permissions. Encryption and stringent key management keep sensitive data secure in transit and at rest.

Authentication techniques like token expiry, rate limiting, and jailing workflows harden the auth flow. Conducting audits and penetration testing uncovers vulnerabilities proactively before exploits occur. Staying updated with the latest Angular, Node.js, and dependency versions ensures timely security patches are applied. These techniques combine to create robust, hardened Angular applications resistant to real-world attacks.

Deploying Angular Apps

Once an Angular application is complete, careful planning is required for production deployment. Creating an optimized production build enables compilation, bundling, and minification for lean assets. The hosting environment, whether a managed cloud provider or custom servers, should ensure robustness, security, and reliability at scale. Automating builds, tests, and releases through continuous integration improves efficiency and stability.

Implementing fallbacks and catch-all routes allows the app to degrade gracefully across environments. Server-side rendering augments the client-side app for better initial load performance and SEO. Logging, monitoring, and alerting quickly surfaces issues in production. Infrastructure scaling and capacity planning accommodate growing traffic volumes.

Finally, establishing update strategies facilitates long-term maintenance and improvement. With thoughtful deployment planning, Angular applications can launch smoothly and sustainably.

Shape April 2022 HR 202
Andy Golpys
- Author

Andy has scaled multiple businesses and is a big believer in Craft CMS as a tool that benefits both Designer, Developer and Client. 

Share
Feedback
Show us some love
Email Us
We usually reply within 72 hours
Agency Directory
Submit your agency
Affiliate Partners
Let's chat