Mura & Angular Decoupled - Mura Digital Experience Platform
In Flow: The Mura Blog

Mura & Angular Decoupled

January 23, 2019 by Grant Shepert

If you're a developer, you doubtlessly have been exposed to the complexities of creating solutions that can function across a wide range of platforms and consumers.

Like us, you've probably turned to client-side technologies like Angular, React, Vue and Node to help solve these problems. Managing content in such a diverse environment can be a challenge, but one that a Headless CMS can thrive in.

This blog post will be the first in an ongoing series exploring Mura's Headless CMS functionality, and its deep set of Content-as-a-Service (CaaS) tools and services. We have developed a series of teach-by-example toolkits that will help you understand the fundamentals of distributing content via our JSON API and remote connectors, and how you can leverage this rich set of functionality using whatever framework you are most comfortable with.

We will begin by looking at Mura distributed via Angular, the popular and potent JavaScript framework, walking through the process of setting up a test environment, as well as explaining the various points of integration, authentication, and access to content.th
 

MuraAngularDecoupled Project

To get started, head over to the Mura Angular project on GitHub: 
https://github.com/blueriver/MuraAngularDecoupled

Follow the instructions in the README section (as seen below) to get things set up with Git, Docker, and Angular, then review the listed requirements and key points for integration with Mura. 

Link to Mura Angular Decoupled project on Github

Mura Modules Mapped to Angular Components

Once you have things set up, there will be an Angular application ready for you to explore, located at:

/angular/src/app

Note that the Mura service now creates a Mura.UI.Angular class that can be used for all other Mura modules that will wrap Angular components:

https://github.com/blueriver/MuraAngularDecoupled/blob/master/angular/src/app/mura.service.ts#L28-L35

You import this component to use as a Mura display object:

https://github.com/blueriver/MuraAngularDecoupled/blob/master/angular/src/app/mura.service.ts#L3
 

Create & Register Angular Components

Creating and registering Angular components is very straight-forward. If you examine the app.module.ts, you will find a couple of example registrations:

You can then follow the pattern for the actual Angular components as shown in these two registered components:

In your own project, it might be wise to create a base class that you then extend to simplify your code.
 

Tell Mura what to Expect

The last step is to tell Mura about your external modules by adding them the to mura.config.json:

https://github.com/blueriver/MuraAngularDecoupled/blob/master/angular/mura.config.json#L10-L18

Notice that the NgCollectionLayout module has an empty contenttypes attribute is empty:

"NgCollectionLayout":{

        "name":"Collection Layout",

        "contenttypes":"",

        "configurator":""

}

This is because it is not meant to be available on the main left sidebar, to drag onto the stage. It's meant to be used with the collection module and you tell Mura this by adding it to the new collectionLayoutArray property:

"rendererProperties":{

        "templateArray":["Default"],

        "collectionLayoutArray":["NgCollectionLayout"],

        "hashurls":false,

        "primaryContentTypes":"Page,Link,File"

}

And set it's location as a environment variable in the Mura container:

https://github.com/blueriver/MuraAngularDecoupled/blob/master/mura/docker-compose.yml#L22

You then reload Mura (?AppReload). If everything is wired up correctly, you should see the example module in your sidebar to drag onto the stage and the NgCollectionLayout will be available in the collection display object configurator.

You can also mount the mura.config.json into the Docker container and set the MURA_EXTERNALCONFIG as the local file path to where it lives.
 

Mura Collection Example

A more involved example is the component used as a Mura Collection layout:

https://github.com/blueriver/MuraAngularDecoupled/blob/master/angular/src/app/modules/collectionlayout/collectionlayout.component.ts

This component requests an instance of the Mura.UI.Collection and retrieve an actual collection instance from it:

new this.Mura.UI.Collection(this.context).getCollection().then((collection)=>{

        this.collection=collection;

        this.changeDetectorRef.detectChanges();

});

The associated template (at the top of the file) simply outputs the retrieved collection. You can also see that Mura collections returned from the API contain self-describing links following the Hypermedia as the Engine of State pattern.

This makes its super-easy to transition to other states or in this specific example pages:

goToPage(link){

        link=link || 'missing'

        if(this.collection && this.collection.has(link)){

                    this.collection.get(link).then((collection)=>{

                                this.collection=collection;

                                this.detectChanges()

                    })

        }

}

We also have documentation that shows an example of what you can get by default from a content node:

https://docs.getmura.com/v7-1/mura-developers/mura-rendering/json-api/findquery/

… which allows you do do things like:

<script>

Mura.getEntity('content').loadBy('filename','about').then(content=>{

       content.getKids().then(kids=>{

           console.log(kids.getAll().items)

           kids.forEach(function(kid){

              /*

                 The kids object can be used to manage content.  You can do things like:

                  kids.set('title','My New Title').set('approved',1).save().then(kid=>{

                        if(kid.hasErrors()){

                          console.log('ERRORS:')

                          console.log(kid.getErrors())

                        } else {

                           console.log('Success')

                        }

                  })

              */

              console.log(kid.getAll());

          });

       });

  })

</script>

Also, note that entities created with the Mura ORM Assembler and Scaffolder work the exact same with seamless transition to related API states (via Mura documentation):

https://docs.getmura.com/v7-1/mura-developers/mura-rendering/murajs/mura-orm-with-mura-js/
 

Important Note

One thing I'd like to specifically point out is that in the above examples, we're basically just talking about how to map Angular component to Mura's layout manager.  I view that as a specific use case and you are in no way required to use the layout manager. We actually refer to this type of use case as 'decoupled' and not 'headless'.  This is because we view the Mura Layout Manager as lightweight 'head', and try to make it as transparent as possible. You can, of course, use MuraJS to talk the Mura API directly, and use that data within you Angular app outside of any of those steps.  Angular Decoupled includes an example of that:

You can also create specialized Mura modules that use MuraJS to find and iterate through Mura entities that have nothing to do with the Mura.UI.Collection module.  That's the default way to display standard Mura content with the default layout manager modules, but's it's not the only way by any means.
 

Next Steps

If you are not an Angular developer, fear not. In forthcoming blog entries, we are going to look at examples using Vue and NextJS/React. We'll also be taking a bigger look at Node implementations, and the role Docker plays in developing these strategies.

Need a look at the big picture and why headless and Content as a Service are so important? Check out this article.

 

 

About the Author

Grant Shepert has been a developer for nearly 20 years, and started using Mura the year it was released. Since then he has written dozens of plugins, spoken about Mura in conferences around the world, contributed code to the core project in numerous ways (his favorite being the FormBuilder), acted as Mura instructor, mentor and evangelist and, when time has permitted, written a blog post or two.