Cómo hacer un blog con Contentful y AngularJS
Contentful es un CMS con API como base, listo para ser usado con AngularJS
Como les contaba en el post anterior, me cambié a Contentful porque decidí trabajar con AngularJS y utilizar una API.
Había pensado en armar mi propia API, de hecho lo hice; pero hacer un sistema y todo lo que conlleva me dió una tremenda flojera.
Sin embargo, volví a la carga cuando conocí a Contentful, un CMS basado en API. Era justo lo que necesitaba. Así que en este post les contaré cómo sacarle provecho a esta innovadora forma de hacer blogs.
Ingredientes
- 1 Diseño o Template en HTML
- Una pizca de conocimientos de AngularJS
- Tener una cuenta en Contentful
Preparación
1. El diseño del sitio
Comienza por el diseño. Puedes usar tus habilidades de Frontend para crear uno o simplemente buscar uno y usarlo como base para tu sitio. En mi caso, utilicé uno que tenía guardado hace casi más de un año.
En este diseño es importante mencionar que debes tener al menos dos vistas:
- El home
- La vista del post
Para quienes usan WordPress o conocen los themes de WordPress, es similar a tener el diseño de index.php, y single.php
Para nuestro ejemplo, usaremos la primera búsqueda que encontré al buscar "bootstrap blog template", es decir, http://startbootstrap.com/template-overviews/clean-blog/.
Lo bueno de este template es que tiene la fuente en Github, así que podemos clonarlo en nuestro computador y comenzar a hacerle los cambios necesarios para nuestro siguiente paso.
Así que partan por ir a su directorio de trabajo preferido y clonen el template, si no usan consola, no se sientan mal, siempre pueden usar la App de Github. Para quienes si usamos consola deben escribir:
git clone [email protected]:IronSummitMedia/startbootstrap-clean-blog.git miblog
Este comando clonará el template en un directorio llamado "miblog" y nos dejará todo listo para comenzar a trabajar.
2. Agregando AngularJS
El siguiente paso es agregar AngularJS a nuestro template base. Para realizar esto podemos utilizar bower como herramienta de instalación o simplemente ir a angularjs.org y descargar la última versión estable.
Si no estás relacionado con AngularJS te recomiendo la lectura del siguiente tutorial
En mi caso, utilicé bower e instalé AngularJS con el siguiente comando: bower install angularjs
Este comando instaló la versión 1.4.8
que es la última estable, si quieres usar otra versión como la 1.5 o la 2, puedes especificarlo en la instalación usando angularjs#version
.
También instalaremos angular-route
para definir las rutas de nuestra single-page-app, también angular-marked
para interpretar los contenidos que vienen desde contentful y por supuesto, angular-contenful
para poder obtener los contenidos desde la API.
El comando para hacer lo anterior es: bower install angular-route angular-marked angular-contentful
Ahora que tenemos todo instalado, crearemos la estructura en un directorio llamado src
, en la raíz del proyecto y vamos a crear el archivo app.js
. Lo que nos deja la siguiente estructura:
Con esta estructura armada comenzaremos el desarrolo de una aplicación simple, en nuestro archivo app.js.
2.1 Modificando el home
Lo primero es agregar nuestros scripts en el siguiente orden:
angular
angular-route
marked
angular-marked
angular-contentful
app.js
Lo que da algo como esto:
<script src="bower_components/angular/angular.min.js"></script>
<script src="bower_components/angular-route/angular-route.min.js"></script>
<script src="bower_components/marked/lib/marked.js"></script>
<script src="bower_components/angular-marked/dist/angular-marked.min.js"></script>
<script src="bower_components/angular-contentful/dist/angular-contentful.min.js"></script>
<script src="src/app.js"></script>
Y luego, configuraremos nuestra app en el tag html
del archivo index.html
, del siguiente modo:
<html lang="en" ng-app="blogApp">
Acá se define el nombre de nuestra aplicación, que debe ser la misma que definamos en el archivo app.js
:
// Blog App
var blogApp = angular.module('blogApp', []);
2.2 Configurando las rutas
El segundo paso es configurar las rutas correspondientes al "index" y a la vista de un post. Para esto usaremos angular-route
, un módulo que permite mostrar templates en base a la ruta.
Para configurar las rutas lo primero que debemos hacer es agregar el módulo en nuestra definición de la BlogApp:
var blogApp = angular.module('blogApp', ['ngRoute']);
El módulo se llama, ‘ngRoute` y se configura del siguiente modo:
blogApp
.config(['$routeProvider',
function($routeProvider) {
$routeProvider
.when('/post/:slug', {
templateUrl: 'post.html',
controller: 'PostController'
})
.when('/', {
templateUrl: 'home.html',
controller: 'HomeController'
})
.otherwise({
redirectTo: '/'
});
}
])
;
Acá le estamos diciendo a nuestra aplicación que si encuentra la ruta /post/slug
entregue el contenido de post.html y que utilice el controlador PostController. Y que en caso contrario, nos lleve a la raíz.
2.3 Creando los controladores
Luego que creamos las rutas, creamos los controladores encargados de la interacción entre la vista y la API.
Para eso, agregamos posterior a la configuración (.config()
), dos métodos más:
.controller('HomeController', ['$scope',
function($scope) {
}
])
.controller('PostController', ['$scope',
function($scope) {
}
])
El primer controlador será el que controle el home de la app, por eso le pusimos HomeController, y el segundo será el que controle la vista del post, por eso lleva el nombre de PostController.
2.4 Directiva ng-view
Antes de declarar los controladores en la vista, debemos definir dónde estará nuestra vista, la cual será reemplazada por el contenido del Home.
Acá el análisis es simple, llevaremos el <!-- Page Header -->
y el <!-- Main Content -->
a un archivo llamado home.html, y en ese espacio pondremos la directiva <div id="view" ng-view></div>
El resultado es el esperado, el sitio queda tal cual venía y el archivo home.html tiene todo menos <!-- Navigation -->
y <!-- Footer -->
, además de las definiciones fuera de estos elementos.
Para probar que todo funciona bien abre el archivo index.html y veras el sitio principal, luego revisa #/post/test y verás la interfaz del post
¿Qué quiere decir esto? Quiere decir que debemos dejar limpio el archivo post.html
, dejándolo solamente los elmentos <header>
y <article>
.
3. Creando la cuenta en Contentful
El último paso es la integración de Contentful, para esto debemos crear una cuenta gratuita en Contentful donde podemos registrarnos normalmente o usando nuestra cuenta de Google o de Github.
Luego, al ingresar te preguntarán tu especialdad, seguramente para darte mejores herramientas. En mi caso, utilicé la opción "I write code".
El siguiente paso es crear el espacio, para esto el wizard cuenta con 4 tipos, yo elegí el template "Blog", que es lo que buscamos realizar.
Después te preguntarán los datos de tu espacio. Yo escribí lo siguiente:
Y comenzará el proceso de creación de API keys, contenidos por defecto y otras configuraciones más.
Al terminar, estarás dentro de contentful. En el menú superior veremos las funcionalidades principales de la App.
Acá, iremos al menú APIs, y seleccionaremos "Content Delivery API", para obtener las credenciales necesarias para obtener el contenido.
Dentro de esta opción existen 3, usaremos la API para la WEB. Al ingresar veremos la sección API ACCESS TOKENS, que es la información que usaremos en nuestro blog para acceder a los contenidos.
4. Integrando Contentful
Con la información anterior estaremos listos para integrar Contentful en nuestra BlogApp.
Para esto agregaremos los módulos restantes: angular-contentful
y angular-marked
en el archivo app.js
:
var blogApp = angular.module('blogApp', ['ngRoute', 'contentful', 'hc.marked']);
Luego, debemos configurar nuestro contentful con los datos del espacio creado, sobre la configuración de $routeProvider
:
blogApp
.config(['$routeProvider', 'contentfulProvider',
function($routeProvider, contentfulProvider) {
contentfulProvider.setOptions({
space: '',
accessToken: ''
});
Los valores que van en space están la api de Website, el primero es el SPACE ID y el segundo es el API ACCESS TOKEN → Production
Con este módulo integrado, ya podemos hacer la consulta de los posts a nuestra API. Usando la documentación del siguiente link (https://github.com/jvandemo/angular-contentful) podemos comenzar a armar nuestro sitio.
4.1 Agregando posts al home
Para agregar los posts al home partiremos definiendo HomeController
dentro de nuestra interfaz. Para esto, en el archivo home.html
agregaremos un <div ng-controller="HomeController"></div>
como contenedor global.
<div ng-controller="HomeController">
<!-- Page Header -->
<!-- Set your background image for this header on the line below. -->
<header class="intro-header" style="background-image: url('img/home-bg.jpg')">
<div class="container">
<div class="row">
...
...
...
...
<ul class="pager">
<li class="next">
<a href="#">Older Posts →</a>
</li>
</ul>
</div>
</div>
</div>
</div>
Primero vamos a obtener los posts usando el módulo contenful
y definiremos la variable entries
dentro del controlador.
.controller('HomeController', ['$scope', 'contentful',
function($scope, contentful) {
contentful
.entries()
.then(
function(response){
$scope.entries = response.data;
},
function(response){
console.log('Algo pasó que no se trajo las entradas :(');
}
)
}
])
Si todo funciona bien, veremos algo como esto en consola:
Ahora, el tema es que la API está trayendo todos los contenidos independientes del tipo. Esto es porque el CMS no cuenta con una estructura definida y varía según el tipo de contenido:
Por lo tanto, debemos filtrar por el tipo de contenido Post. Para ésto usaremos la API de Referencia que entrega Contenful.
Lo que hacemos, es agregar el filtro en el método entries()
el cual tiene como parámetro opcional un queryString
.
El parámetro a utilizar es el ID alfanumérico del tipo de contenido, que podemos obtener al editar el tipo de contenido en la URL.
En la URL, el ID corresponde a
2wKn6yEnZewu2SCCkus4as
, en el caso de ustedes será otro ID alfanumérico. Esto es algo que contentful debería mejorar, sin duda.
Ya que tenemos nuestro ID, filtraremos los contenidos en el controlador, del siguiente modo:
contentful
.entries('content_type=2wKn6yEnZewu2SCCkus4as')
.then(
El siguiente paso es agregar los siguientes posts en la lista inferior. Para esto usaremos las directivas ng-repeat
que hará la repetición. Eliminamos todos los elementos repetidos y dejamos solo un <div class="post-preview">
incluyendo el <hr>
. El elemento Pager Lo dejaremos comentado por ahora:
<!-- Main Content -->
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
<div class="post-preview" ng-repeat="entry in entries.items">
<a href="#/post/{{ entry.fields.slug }}>
<h2 class="post-title">
{{ entry.fields.title }}
</h2>
<h3 class="post-subtitle">
</h3>
</a>
<p class="post-meta">Posted by <a href="#">{{entry.fields.author[0].fields.name}}</a> on {{entry.fields.date|date:'medium'}}</p>
</div>
<hr>
<!-- Pager -->
<!--ul class="pager">
<li class="next">
<a href="#">Older Posts →</a>
</li>
</ul-->
</div>
</div>
</div>
Si revisan bien, usamos el atributo fields
definido en la variable entry
, para obtener los datos del post. También aplicamos el filtro date para mostrar la fecha más entendible.
Otro punto importante es el link, donde usamos el slug
como llave para encontrar el contenido en la vista de Post.
Por el momento ignoraremos el elemento post-subtitle
pero podemos crear un atributo "excerpt" en nuestro content-type o usar una función que nos recorte parte del contenido.
4.2 Visualizando un Post
Para visualizar un post haremos click en una de las entradas, esto nos llevará a la url #/post/<slug del post>
.
Aquí, vamos a modificar el controlador PostController
inyectando contentful
y $routeParams
, para obtener el slug.
.controller('PostController', ['$scope', '$routeParams', 'contentful',
function($scope, $routeParams, contentful) {
contentful
.entries('content_type=2wKn6yEnZewu2SCCkus4as&fields.slug='+$routeParams.slug+'&limit=1')
.then(
function(response){
$scope.entry = response.data.items[0];
},
function(response){
console.log('Algo pasó que no se trajo las entradas :(');
}
)
}
])
Acá repetimos la búsqueda por tipo de contenido, agreamos el atributo fields.slug
y usamos $routeParams.slug
para el valor. Además, solo obtenemos un valor usando el atributo limit=1
.
Al igual que en home.html
, en post.html
también debemos agregar el elemento <div ng-controller="PostController"></div>
como contenedor del post.
Luego, modificaremos el intro-header
del siguiente modo:
<header class="intro-header" style="background-image: url('img/post-bg.jpg')">
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
<div class="post-heading">
<h1>{{ entry.fields.title }}</h1>
<h2 class="subheading"></h2>
<span class="meta">Posted by <a href="#">{{entry.fields.author[0].fields.name}}</a> on {{entry.fields.date|date:'medium'}}</span>
</div>
</div>
</div>
</div>
</header>
Y para el contenido, usaremos marked
, el plugin que convierte los contenidos desde markdown a html.
<!-- Post Content -->
<article>
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
<div marked="entry.fields.body"></div>
</div>
</div>
</div>
</article>
Resultado y detalles finales
El resultado de todo lo anterior se ve del siguiente modo:
Si lo quieren ver funcionando pueden visitar el siguiente link: http://labs.alvaroveliz.cl/angular-contentful-blog/
Si quieren implementar este método, pueden agregar detalles finales, utilizando otros modulos como por ejemplo:
- Disqus para comentarios : https://github.com/alvaroveliz/angular-disqus
- Google Analytics : https://github.com/revolunet/angular-google-analytics
- Meta tags : https://github.com/AvraamMavridis/angular-metatags
- Social Share: https://github.com/720kb/angular-socialshare
Si tienen comentarios sobre el post o si encontraron errores, pueden dejarlos acá abajo.
¡Gracias por leer!