AngularJS é um framework JavaScript criado pelo time de desenvolvedores da Google, open-source e estende o HTML, possibilitando a criação de aplicações dinâmicas de forma rápida e objetiva. Ele usa o padrão MV*, ou seja, o que for alterado na sua camada de visão é refletido no seu model e vice-versa, por conta do padrão JavaScript que detém a camada model e o HTML a camada view.
O HTML é ótimo para declarar documentos estáticos, mas deixa a desejar quando tentamos usá-lo para declarar visualizações dinâmicas em aplicações web. O AngularJS permite estender o vocabulário HTML para a sua aplicação.
O Blog
Para esse artigo desenvolveremos um Blog utilizando duas das tecnologias mais atuais da Google no momento, AngularJS e Firebase, que é um BaaS (backend as a service), ou seja, um banco de dados NoSQL real time que armazena os seus dados na nuvem. O banco de dados utiliza a manipulação API REST para seus serviços, facilitando o desenvolvimento de aplicações no lado do servidor, com integrações a várias plataformas como o próprio AngularJS, JavaScript, NodeJS, Android e iOS, deixando o desenvolvedor preocupado apenas com o desenvolvimento front-end de sua aplicação.
A primeira coisa a fazer é definir a estrutura de pasta do nosso blog, conforme vemos na Listagem 1.
|- css
|- blog.css
|- js
|- app.js
|- controllers.js
|- directives.js
|- routes.js
|- services.js
|- partials
|- mast-about.html
|- mast-footer.html
|- mast-head.html
|- index.html
|- home.html
|- novo.html
Para o post foi pensando em uma estrutura simples e com melhor didática. Contudo, se quiser ir mais a fundo com AngularJS é recomendado utilizar a estrutura de pastas que o próprio pessoal da AngularJS disponibiliza em angular-seed (vide seção Links).
Para facilitar o desenvolvimento visual do nosso projeto Blog será utilizado o framework front-end Bootstrap, pois possui uma grande diversidade de temas, é responsivo e fácil de usar. No seu site (vide seção Links) baixe o CSS pronto para projetos blog e coloquei na pasta css do projeto.
Na pasta js teremos os arquivos JavaScript que criaremos em Angular e na pasta partials teremos os componentes que faremos em HTML. A index.html será nossa página principal, novo.html e home.html serão nossos templates.
Página Inicial
Assim como qualquer aplicação web, precisamos criar uma página inicial (index.html) e nela devemos inserir os arquivos js e css que utilizaremos, conforme mostra a Listagem 2.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="favicon.ico">
<title>DevMedia AngularJS</title>
<!-- Bootstrap core CSS -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css"
rel="stylesheet">
<!-- Custom styles for this template -->
<link href="css/blog.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<link href='http://fonts.googleapis.com/css?family=Maven+Pro' rel='stylesheet'
type='text/css'>
</head>
<body ng-app="app" >
<mast-head></mast-head>
<div class="container">
<div ng-view >
</div>
</div>
<mast-footer></mast-footer>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js">
</script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js">
</script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-route.min.js">
</script>
<script src="https://cdn.firebase.com/js/client/2.2.1/firebase.js"></script>
<script src="https://cdn.firebase.com/libs/angularfire/1.0.0/angularfire.min.js">
</script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<script src="js/app.js"></script>
<script src="js/routes.js"></script>
<script src="js/services.js"></script>
<script src="js/controllers.js"></script>
<script src="js/directives.js"></script>
</body>
</html>
Observa-se no código que algumas tags novas foram inseridas no HTML, como ng-app e ng-view.
O atributo ng-app na tag body do HTML informa para o DOM que é um documento AngularJS. Para o AngularJS o atributo ng-app é considerado como a raiz da aplicação, por esse motivo que geralmente colocamos o atributo no elemento body ou html, dizendo que todos os filhos fazem parte da nossa aplicação. Porém, esse atributo pode ser inserido em qualquer tag do HTML, dependendo o escopo que sua aplicação atingirá.
Já o atributo ng-view diz para o AngularJS que é ali que deve ser inserido seus templates html, conforme a Listagem 3.
|- home.html
|- novo.html
Módulos (app.js)
Após criarmos a página inicial (index.html) da nossa aplicação, devemos dizer quais módulos ela dependerá para funcionar. Para o blog, os módulos são Firebase, que é responsável pela comunicação com o banco de dados, e ngRoute, que é responsável por criar o roteamento de nossa aplicação, conforme descrito na Listagem 4.
angular.module("app", [
'firebase',
'ngRoute'
]);
Alguns autores separam a aplicação em vários módulos, deixando a aplicação bem modularizada. Cada módulo criado na sua aplicação deve entrar nas dependências da Listagem 4, para pode ser reutilizado em outras aplicações codificadas com AngularJS.
Rotas (routes.js)
É com o ngRoute que dizemos para o AngularJS injetar determinada página na ng-view e também qual é o controller dessa página. Toda vez que a rota é alterada, a URL é alterada em nosso navegador.
Na Listagem 5, quando estamos na página principal a rota é http://localhost/devmedia-blog/#/, mas quando alteramos para a página novo, o final da rota muda de http://localhost/devmedia-blog/#/ para http://localhost/devmedia-blog/#/novo. Cada vez que uma rota é alterada, novos templates e controllers são alterados: esse processo com AngularJS se torna muito simples de ser feito, diferente de quando não utilizamos esse framework.
angular.module("app").config(function($routeProvider) {
$routeProvider.when('/', {
templateUrl : 'home.html',
controller : 'HomeController',
resolve: {
"syncObject" : function(FirebaseService) {
return FirebaseService.getPosts();
}
}
}).when('/novo', {
templateUrl : 'novo.html',
controller : 'NovoController'
});
});
A função when do ngRoute diz quando será injetada essa página na ng-view, por exemplo, quando a aplicação iniciar, será injetado o HTML home.html e será chamado o controller HomeController.
Repare também que temos no código o Resolve, que é uma propriedade na configuração de roteamento, que significa que pode existir uma dependência de serviço. No blog do nosso exemplo, ao ser injetada a página home.html, o AngularJS fará a solicitação para os nossos serviços (services.js), buscando os posts do blog no Firebase.
Serviços (services.js)
O serviço fornece acesso aos dados do nosso banco de dados Firebase, como mostra a Listagem 6.
angular.module("app").service("FirebaseService", function($firebaseArray, $firebaseObject) {
var vm = this;
vm.ref = new Firebase("https://devmediablog.firebaseio.com/");
vm.posts = vm.ref.child('posts');
vm.syncObject = $firebaseObject(vm.ref);
this.add = function(post) {
vm.posts.push(post);
};
this.getPosts = function() {
return vm.syncObject;
};
});
Quando o home.html for chamado pelo ngRoute (routes.js), a propriedade resolve irá chamar a função do FirebaseService.getPosts(). Observe que para buscar os posts (getPosts) e inserir um novo post (add) não foram necessárias muitas linhas de código, pois as bibliotecas do Firebase nos ajudam neste ponto.
Controladora (controllers.js)
Agora precisamos dizer para nosso Controller, que cuidará dos dados retornados do FirebaseService.getPosts() (service.js) e adicionar um novo post FirebaseService.add(post) (service.js), conforme vemos na Listagem 7.
angular.module("app").controller("HomeController", ["$scope", "syncObject",
function($scope, syncObject) {
syncObject.$bindTo($scope, "data");
}]);
angular.module("app").controller("NovoController", ["$scope", "FirebaseService", "$timeout",
function($scope, FirebaseService, $timeout) {
var d = new Date();
var dtF = (d.getDate() < 10 ? "0" : "") + d.getDate() + "/" + (d.getMonth() +
1 < 10 ? "0" : "") + (d.getMonth() + 1) + "/" + d.getFullYear() + " " + d.getHours() +
":" + d.getMinutes() + ":" + d.getSeconds();
$scope.msg = "";
$scope.title = "";
$scope.body = "";
$scope.author = "";
$scope.dt = dtF;
$scope.addMessage = function() {
if ($scope.title && $scope.body) {
FirebaseService.add({ title: $scope.title, body: $scope.body, author:
$scope.author, date: $scope.dt });
$scope.msg = "Salvo com Sucesso!";
$timeout(function() {
$scope.msg = "";
}, 3000);
$scope.title = "";
$scope.body = "";
$scope.author = "";
$scope.dt = dtF;
}
};
}
]);
Para a controladora HomeControler, quando o home.html for chamado pelo ngRoute (routes.js), a propriedade resolve (que possui o método syncObject) chama a função do FirebaseService.getPosts(). O método syncObject é o mesmo que se encontra no HomeController da Listagem 7, fazendo a ligação, assim dizendo que nossa função syncObject vincula o nosso $scope (home.html) com o retorno de dados do Firebase.
Para a controladora NovoController, quando o novo.html for chamado pelo ngRoute (routes.js), a mesma chama a controladora NovoController da Listagem 7, fazendo a ligação: quando o usuário clicar em enviar os dados do formulário para salvar o post do blog, a função addMessage da Listagem 7 é chamada, passando as informações para FirebaseService.add(post) e salvando as informações na base de dados do Firebase.
Templates (home.html e novo.html)
Agora devemos apresentar os nossos dados na view que foram passados para nossa controladora, de acordo com o código da Listagem 8. Além disso, vamos criar um formulário para enviar os dados do novo post, conforme a Listagem 9.
<div class="row" >
<div class="col-sm-8 blog-main">
<div class="blog-post" ng-repeat="post in data.posts" >
<h2 class="blog-post-title">{{ post.title }}</h2>
{{ post.body }}
<p class="blog-post-meta">{{ post.date }} por
<a href="#">{{ post.author }}</a></p>
</div>
</div>
<div class="col-sm-3 col-sm-offset-1 blog-sidebar">
<div class="sidebar-module sidebar-module-inset">
<mast-about></mast-about>
</div>
<div class="sidebar-module">
<h4>Quer Saber Mais?</h4>
<ol class="list-unstyled">
<li><a href="//www.devmedia.com.br/space/eduardo-malherbi-martins">
DevMedia</a></li>
</ol>
</div>
</div>
</div>
<div class="row" >
<div class="col-sm-8">
<div>
<form>
<div class="form-group">
<label for="title">Título</label>
<input type="text" class="form-control" id="title" name="title"
ng-model="title" placeholder="Título do post.">
</div>
<div class="form-group">
<label for="body">Descrição</label>
<textarea id="body" name="body" ng-model="body" class="form-control"
rows="7" placeholder="Descrição do post."></textarea>
</div>
<div class="form-group">
<label for="author">Autor</label>
<input type="text" class="form-control" id="author" name="author"
ng-model="author" placeholder="Autor do post.">
</div>
<div class="form-group">
<label for="dt">Data</label>
<input id="dt" name="dt" ng-model="dt" class="form-control"
placeholder="Data do post." disabled>
</div>
<button ng-click="addMessage()" type="button"
class="btn btn-primary">Enviar</button>
</form>
</div>
<div style="margin-top: 10px" >
<div ng-if="msg">
<p class="bg-primary" style="padding: 10px" >{{ msg }}</p>
</div>
</div>
</div>
<div class="col-sm-3 col-sm-offset-1 blog-sidebar">
<div class="sidebar-module sidebar-module-inset">
<mast-about></mast-about>
</div>
<div class="sidebar-module">
<h4>Quer Saber Mais?</h4>
<ol class="list-unstyled">
<li><a href="//www.devmedia.com.br/space/eduardo-malherbi-martins">
DevMedia</a></li>
</ol>
</div>
</div>
</div>
Na Listagem 8 a diretiva ngRepeat representa o for das linguagens de programação (JavaScript, PHP, Java). É no laço do loop que conseguimos pegar o objeto post e apresentar o título e corpo do texto.
Na Listagem 9, as tags ng-model e ng-click nos auxiliam no envio dos dados para nossa controladora. As variáveis criadas dentro da tag ng-model refletem em nossa controladora o mesmo para tag ng-click.
Com isso já temos o nosso blog funcionando, e buscando os posts no banco de dados Firebase e passando para nossa controladora, a mesma ligada com o nosso HTML e apresentando os dados de forma dinâmica. Também já temos a possibilidade de inserir um novo post e enviar para o banco de dados do Firebase.
Diretivas (directives.js)
Uma das coisas mais interessantes do AngularJS é que podemos criar nossos próprios componentes, sem precisar rescrever códigos, tornando o desenvolvimento rápido.
Já reparou que no index.html temos as tags html “” e “”? Quando rodamos nossa aplicação, um código HTML é injetado nesses elementos. Veja como isso funciona no arquivo directives.js da Listagem 10.
angular.module("app").directive("mastHead", function() {
return {
templateUrl: 'partials/mast-head.html'
};
});
angular.module("app").directive("mastFooter", function() {
return {
templateUrl: 'partials/mast-footer.html'
};
});
angular.module("app").directive("mastAbout", function() {
return {
templateUrl: 'partials/mast-about.html'
};
});
Segundo o AngularJS, as diretivas são marcadores em um elemento DOM que informam ao compilador HTML da AngularJS para anexar um comportamento especificado para esse elemento. Observe como ficou nosso elemento HTML mast-head.html na Listagem 11.
<div class="blog-masthead">
<div class="container">
<nav class="blog-nav">
<a class="blog-nav-item" ng-href="#/">Home</a>
<a class="blog-nav-item" ng-href="#/novo">Novo Post</a>
</nav>
</div>
</div>
<div class="container">
<div class="blog-header">
<h1 class="blog-title">DevMedia</h1>
<p class="lead blog-description">Meu Blog.</p>
</div>
</div>
Para o nosso blog, as nossas diretivas injetam o código HTML (mast-head.html) no elemento criado, que encontra-se no index.html. Para fazer essa ligação precisamos de um código em JavaScript, ligando a tag mast-head com o HTML mast-head.html. Para as demais diretivas “” e “” o mesmo processo foi utilizado.
As Figuras 1 e 2 mostram o resultado final do nosso blog.