Using Angular with a “headless” Wagtail CMS

Wagtail is a great Django-based content management system. Angular is a full-featured JavaScript framework. I wanted to use them together, so I made some helper libraries. Below, I explain how I did it.

Goals:

  • Enable Wagtail features like preview and redirects.
  • Allow routing to be defined (mostly) in Wagtail
  • Maintain great performance through
    • Lazy loading JS modules
    • Compatible with Angular Universal for server side rendering

wagtail and angular

Setting up Wagtail

Install from pypi wagtail_spa_integration using the instructions here. Since there is nothing angular specific about it – you could also use this with other front-end solutions. This package provides an extended Wagtail V2 Pages Endpoint.

Setting up Angular

Install angular-wagtail. Follow the instructions. At a high level, instead of setting routes to components, you will set Wagtail page types to either components or modules. For example the Wagtail Page “foo.MyPage” might map to MyPageComponent in Angular. I will call this dynamic routing, as opposed to Angular router’s fixed routes. This is all that’s needed for simple websites. However, angular-wagtail works by having the Angular project request page data for every page. This is a problem if your site has thousands of blog pages. Your Angular app may not need to know every blog URL up front. It just needs to know that they follow a schema like “blog/blog-post-slug”. You can make a lazy loaded module for blog and set the route like

{
  type:"cool_blog.BlogIndexPage",
  loadChildren:"./blog/blog.module#BlogModule"
}

There are some limitations. loadChildren won’t work with nested routes. If you have two components in BlogModule, then you can’t both lazy load the module and use the dynamic Wagtail driven routes. There are two workarounds. Ensure the lazy loaded modules only have one route or keep them in Angular’s routing instead of WagtailModule’s page type mapping. In the blog example, you may have a blog index page and a blog post page nested under. As long as you assume the route is always /blog and /blog/post-slug you don’t really need the dynamic routing that WagtailModule provides.

Finally you need to gather page detail data in our components. In the ngOnInit function for add something like

constructor(private wagtail: WagtailService) {}
...
this.cmsData$=this.wagtail.getPageForCurrentUrl<IMyCoolPage>();

IMyCoolPage is the interface for the data you expect to receive from wagtail for this page. This works with both fixed routes in Angular router and dynamic routes in WagtailModule.

These functions will also automatically check for redirects if a page is not found.

Closing thoughts

I really enjoy having all view logic in Angular instead of attempting to mix Django templates with a JS framework’s view layer (JSX, Angular’s templates, etc). Previously, this meant giving up a lot of features that “just work” in Wagtail. With using these packages, I can quickly bootstrap a headless Wagtail server with a separate Node/JavaScript front-end and keep all the features I’m used to. Please consider contributing some code, unit tests, or make a JS integration with your favorite framework.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s