Desenvolvimento

19 mar, 2018

Desenvolvendo uma aplicação de autenticação JWT com AngularJs (v.1)

100 visualizações
Publicidade

Olá, pessoal! Tudo bem? Faz um tempinho que não faço um artigo aqui e claro que eu não iria abandonar vocês!

Bom, no último artigo eu trouxe um passo a passo ensinando como desenvolver uma aplicação com autenticação em JWT com Node.Js. Vocês puderam aprender pelo lado do back-end como realizar essa autenticação. Porém, ficou faltando o lado do front-end. Foi pensando nisso que eu resolvi fazer esse artigo, que será dividido em duas partes.

Querem saber mais? Então continuem a leitura!

Introdução

Como já mencionado, dividirei esse artigo em duas partes, sendo elas:

  • Autenticação JWT com AngularJs (versão 1.x)
  • Autenticação JWT com Angular (versão 2/4)

Talvez vocês estejam se perguntando: “Mas, Glaucia! Por que perder tempo ensinando a versão 1.x do AngularJs, uma vez que houve mudança para a versão 2/4?”. Simples. Existe muito sistema legado e tem muita empresa que ainda faz uso do AngularJs (1.x). Então para que fique claro como realizar uma aplicação de Autenticação JWT, tanto com o AngularJs 1 quanto com o Angular 2/4, resolvi fazer para ambas as versões. E espero que vocês gostem do material proposto aqui!

O que vamos desenvolver?

Uma aplicação simples, visando ensinar os conceitos básicos de autenticação usando o jwt.io. É assim que a nossa aplicação Angular irá enviar o usuário codificado em base64 e a senha prefixada como ‘Basic’. E consequentemente o JWT enviará um token JSON codificado para a base64 (JWT). Caso queiram entender mais sobre o assunto, entrem no site do jwt.io.

Como podem perceber na demo acima, a aplicação usa um back-end falso, com ajuda do service do angular: $httpBackEnd, o qual faz parte do módulo ngMockE2E. Isso é feito para que o exemplo possa funcionar sem um backend real. Caso você queira fazer uso do seu backend, basta remover o script mock-backend.js do arquivo principal: index.html.

Quando estiver bem explicado para todos o conceito do back-end e front-end aqui no site, farei um tutorial que será zero to hero, ou seja, um tutorial que explicará o desenvolvimento de uma aplicação MEAN (Mongo, Express, Angular, Node) que tratará desde o back até o front-end! Quando?

Vamos precisar do que? Assim como nos outros tutoriais:

Disponibilizarei o código fonte do projeto neste link. Sintam-se à vontade em fazer um fork, compartilhar o código ou dar um star no repositório!

Estrutura do projeto

Peço que abram a IDE desejada – no meu caso será o Visual Studio Code – criem um diretório para o projeto que desenvolveremos e criem pastas e arquivos como na imagem abaixo:

Caso vocês não saibam, existe um padrão na organização da estrutura de um projeto Angular, visando não perder o conceito MV*. Recomendo a todos, caso usem ou pretendem usar o AngularJs em projeto de grande escala, a leitura do artigo do Cliff Meyers, que explica como deve ser feito e organizado uma estrutura de um projeto AngularJs:

Conforme passamos aqui no tutorial, explicarei o que é cada pasta e arquivo nesse projeto para fique claro para todos as suas funções e objetivos na aplicação!

1. Pasta: Content

Como vocês podem perceber no arquivo contido nessa pasta, ela é responsável por conter arquivos estáticos como, por exemplo: CSS, imagens, fontes, dentre outros arquivos.

1.1 Arquivo: app.css (content/app.css):

Já o arquivo app.css contido na pasta content abrigará todo estilo personalizado da nossa aplicação Angular. No código abaixo estou personalizando o tamanho da fonte das mensagem de validação no formulário de login que estaremos criando.

/*
    Arquivo: app.css
    Data: 06/05/2017
    Descrição: Arquivo responsável por personalizar as mensagens de validação do formulário de login.
    Author: Glaucia Lemos
*/

body {
	background: #EEE;
	margin: 0 auto;
	text-align: center;
	font-family: 'Merriweather', 'Helvetica', 'Arial';
	font-size: 16px;
	padding-top: 70px;
	padding-bottom: 30px;
}

a:focus {
	outline: none;
}

h1, h2, h3, h4, h5, h6 {
	font-family: 'Lato', 'Merriweather', 'Helvetica', 'Arial';
}

h2 {
	font-weight: 700;
	font-size: 18px;
}

h3 {
	color: rgba(0,0,0,0.5);
}

ul {
	margin: 0;
	padding: 0;
}

li {
	list-style: none;
}

.container {
	width: 100%;
	max-width: 1300px;
	margin: 0 auto;
	text-align: left;
}

.jumbotron {
	padding-top: 32px;
	padding-bottom: 32px;
	text-align: center;
}

.jumbotron h1 {
    font-weight: 700;
}

.jumbotron h1 span {
    color: rgba(0,0,0,0.5);
    font-size: 40px;
}

/**
 * Dropdown
 */

.theme-dropdown .dropdown-menu {
	position: static;
	display: block;
	margin-bottom: 20px;
}

.theme-showcase > p > .btn {
	margin: 5px 0;
}

.theme-showcase .navbar .container {
	width: auto;
}

/**
 * Navigation
 */

.navbar .container {
	font-family: 'Lato', 'Merriweather', 'Helvetica', 'Arial';
	line-height: 1;
	text-align: center;
}

.navbar-nav {
	float: right;
}

.navbar > .container .navbar-brand {
    color: #FFF;
    font-weight: 700;
    text-shadow: 0 -1px 0 rgba(0,0,0,.25);
}

.navbar-inverse .navbar-nav > li > a {
	color: #DDD;
}

.navbar-inverse .navbar-nav > .active > a,
.navbar-inverse .navbar-nav > .active > a:focus {
    color: #fff;
    background-color: #080808;
}

.navbar-inverse .navbar-nav > .active > a:hover {
    color: #fff;
    background-color: #222;
}

.navbar > .container .navbar-brand span {
    color: rgba(255,255,255,0.2);
    font-size: 13px;
}

.navbar > .container .navbar-brand:hover {
    color: rgba(255,255,255,0.8);
}


/** 
 * Footer
 */

footer {
	border-top: 1px solid #DDD;
	color: #858585;
	font-size: 13px;
	margin: 48px 0 0 0;
	padding: 40px 0 0 0;
}

footer a,
footer a:link,
footer a:focus,
footer a:visited {
	color: #858585;
}

footer a:hover {
	color: #333;
}

footer span.glyphicon {
	color: #e00000;
}

.validacao-msg {
    font-size: 12px;
}

Peço que incluam os arquivos package.json e o bower.json na raiz principal do projeto e com o seguintes pacotes a serem instalados:

  • Package.json

{
  "name": "angular-seed",
  "private": true,
  "version": "0.0.0",
  "description": "A starter project for AngularJS",
  "repository": "https://github.com/angular/angular-seed",
  "license": "MIT",
  "devDependencies": {
    "bower": "^1.7.7",
    "http-server": "^0.9.0",
    "jasmine-core": "^2.4.1",
    "karma": "^0.13.22",
    "karma-chrome-launcher": "^0.2.3",
    "karma-firefox-launcher": "^0.1.7",
    "karma-jasmine": "^0.3.8",
    "karma-junit-reporter": "^0.4.1",
    "protractor": "^4.0.9"
  },
  "scripts": {
    "postinstall": "bower install",
    "update-deps": "npm update",
    "postupdate-deps": "bower update",
    "prestart": "npm install",
    "start": "http-server -a localhost -p 8000 -c-1 ./app",
    "pretest": "npm install",
    "test": "karma start karma.conf.js",
    "test-single-run": "karma start karma.conf.js --single-run",
    "preupdate-webdriver": "npm install",
    "update-webdriver": "webdriver-manager update",
    "preprotractor": "npm run update-webdriver",
    "protractor": "protractor e2e-tests/protractor.conf.js",
    "update-index-async": "node -e \"var fs=require('fs'),indexFile='app/index-async.html',loaderFile='app/bower_components/angular-loader/angular-loader.min.js',loaderText=fs.readFileSync(loaderFile,'utf-8').split(/sourceMappingURL=angular-loader.min.js.map/).join('sourceMappingURL=bower_components/angular-loader/angular-loader.min.js.map'),indexText=fs.readFileSync(indexFile,'utf-8').split\\/\\/@@NG_LOADER_START@@[\\s\\S]*\\/\\/@@NG_LOADER_END@@/).join('//@@NG_LOADER_START@@\\n'+loaderText+'    //@@NG_LOADER_END@@');fs.writeFileSync(indexFile,indexText);\""
  }
}
  • bower.json

{
  "name": "angular-seed",
  "description": "A starter project for AngularJS",
  "version": "0.0.0",
  "homepage": "https://github.com/angular/angular-seed",
  "license": "MIT",
  "private": true,
  "dependencies": {
    "angular": "~1.5.0",
    "angular-route": "~1.5.0",
    "angular-loader": "~1.5.0",
    "angular-mocks": "~1.5.0",
    "html5-boilerplate": "^5.3.0"
  }
}

Esse package.json é o modelo do angular-seed, localizado no repositório do GitHub para ajudar os desenvolvedores a iniciar um determinado projeto AngularJs, acesse. Como ficaremos testando para saber se a nossa aplicação está funcionando, precisaremos somente do bower e do script para iniciar o serviço!

Como de praste, depois que vocês incluírem esse arquivo no projeto, peço que executem npm install para que sejam instaladas as dependências necessárias do projeto.

Vamos dar continuidade porque ainda tem muita coisa para desenvolvermos!

2) Pasta: Helpers

É na pasta helpers que estaremos mockando e é nela que estarão os nossos dados falsos que simularão um back-end na aplicação.

2.1) Arquivo: mock-backend.js

Como já explicado, esse back-end falso permitirá que o exemplo seja executado sem que haja o desenvolvimento de uma api de um back-end real. Também, como já explicado acima, usaremos o service do Angular: $httpBackEnd.

Caso queiram saber mais sobre esse service do Angular, basta clicar neste link. Esse service é provido do módulo ngMockE2E, segue o link:

Mas o que esse módulo faz? Simples. Ele irá interceptar as solicitações via HTTP feitas pela aplicação Angular e enviará respostas falsas para o servidor, simulando uma API no back-end.

Peço que abram o arquivo mock-backend.js e desenvolvam o seguinte código abaixo:

/**
 * Arquivo: mock-backend.js
 * Data: 06/05/2017
 * Descrição: arquivo responsável por simular um Back-End falso.
 * Author: Glaucia Lemos
 * 
 */

(function() {
  'use strict';

  angular
    .module('app')
    .run(setupBackEndMock);

  //Aqui estamos criando um método que será responsável por simular uma api no lado do Back-End
  function setupBackEndMock($httpBackend) {
    var testUsuario = {
      username: 'teste',
      password: 'teste',
      nome: 'Teste',
      sobrenome: 'Usuario'
    };

    //Aqui estamos criando uma api (end point) de autenticação falsa:
    $httpBackend.whenPOST('/api/authenticate').respond(function(method, url, data) {

      //Irá pegar os parâmetros inseridos e enviar logo em seguida via post
      var params = angular.fromJson(data);

      // Aqui irá verificar se as credenciais do usuário estão corretas e retornará um jwt token falso se os dados forem válidos:
      if (params.username === testUsuario.username && params.password === testUsuario.password) {
        return [200, {
          token: 'jwt-token-mockeado'
        }, {}];
      } else {
        return [200, {}, {}];
      }
    });

    $httpBackend.whenGET(/^\w+.*/).passThrough();

  }
})();

Vamos entender agora o que esse arquivo está fazendo. Aqui fizemos uma implementação que irá simular uma api/authenticate que apontará um nome de usuário e senha válidos, e consequentemente enviará de retorno um token jwt falso se for bem sucedido. Todas as outras solicitações serão passadas para o servidor de forma que os arquivos estáticos, como por exemplo: .js, .html e .css, sejam exibidos de maneira correta para aplicação Angular.

Novamente, caso não tenham entendido o que foi feito acima, peço que leiam sobre o uso do service: $httpBackend. Este link explica detalhadamente o seu uso e possui exemplos práticos também!

3) Pasta: Services

A pasta services é usada para manter arquivos de services e factories do Angular. Todas as APIs de acesso e lógica de negócios devem ser desenvolvidos dentro desta pasta, para que ela possa manter os controladores do Angular e claro, manter a organização e separação do código para que fique de fácil entendimento para quem estiver desenvolvendo ou até mesmo para quem for realizar, por exemplo, uma manutenção ou sustentação da aplicação criada.

3.1) Arquivo: auth.service.js

O arquivo auth.service.js será utilizado para realizar o login e o logout da nossa aplicação. Para efetuar o login, a sessão irá registrar as credenciais dos usuários, e logo em seguida, a API verificará se existe um token jwt via response.

Se o login for bem sucedido, os detalhes do usuário serão armazenados localmente via local storage e o token será adicionado ao header de autorização do HTTP para todas as solicitações feitas via service do Angular: $http.

Os detalhes do usuário registrado serão armazenados em um localStorage para que o usuário fique conectado. Então o usuário manterá logado na aplicação se ele atualizar o navegador e entre as sessões do navegador, até que faça o logout da aplicação.

Vejam na prática, no código abaixo:

/**
 * Arquivo: auth.service.js
 * Data: 06/05/2017
 * Descrição: arquivo responsável por toda a lógica da aplicação.
 * Author: Glaucia Lemos
 * 
 */

(function () {
  'use strict';

  angular
    .module('app')
    .factory('AuthenticationService', Service);

  function Service($http, $localStorage) {
    var service = {};

    service.Login = Login;
    service.Logout = Logout;

    return service;

    function Login(username, password, callback) {
      $http.post('/api/authenticate', {
          username: username,
          password: password
        })
        .success(function(response) {
          //Caso o login efetuado pelo usuário seja satisfatório então enviará um token via response:
          if (response.token) {
            //Armazenará localmente o usuário e o token para que mantenha o usuário logado em caso de dar um refresh nas páginas:
            $localStorage.currentUser = {
              username: username,
              token: response.token
            };

            //Aqui estamos adicionando um token jwt no header de todos os requests das solicitações feitas via $http
            $http.defaults.headers.common.Authorization = 'Coders ' + response.token;

            //Aqui iremos executar um callback como true para indicar que o login foi bem sucedido:
            callback(true);
          } else {
            //e callback como false para indicar que o login não foi bem sucedido:
            callback(false);
          }
        });
    }

    function Logout() {
      //Aqui estaremos removendo todos os usuários guardados via localStorage e limparemos auth header do http:
      delete $localStorage.currentUser;
      $http.defaults.headers.common.Authorization = '';
    }
  }
})();

Bom, agora que a lógica está feita, vamos focar nos Controllers!

4) Pasta: Home

A pasta home contém todos os controllers e as views responsáveis pela seção home da aplicação.

4.1) Arquivo: home/index.controller.js

O arquivo index.controller na pasta home será responsável por lidar com toda a iteração e dados para a exibição do arquivo index.view.html.

/**
 * Arquivo: index.controller.js
 * Data: 06/05/2017
 * Descrição: arqivo responsável por lidar com toda a iteração e dados para a exibição no arquivo: home/index.view.html
 * Author: Glaucia Lemos
 * 
 */

(function () {
    'use strict';

    angular
        .module('app')
        .controller('Home.IndexController', Controller);

    function Controller() {
        var viewModel = this;

        initController();

        function initController() {
        }
    }

})();

4.2) Arquivo: home/index.view.html

Esse arquivo é justamente o html da página que, quando o usuário for efetuar o seu login e for bem sucedido, entrará na Página Principal.

<div class="col-md-6 col-md-offset-3">
    <h1>Página Principal</h1>
    <p>Seja Bem-Vindo!</p>
    <p><a href="#/login">Sair</a></p>
</div>

Acho que nem preciso dizer o que está acontecendo aqui, né? Vamos continuar!

5) Pasta: Login

A pasta login terá todos os controllers e as views para a seção do login da aplicação.

5.1) Arquivo: login/index.controller.js

O arquivo index.controller.js da pasta login irá lidar com toda a iteração e dados para a exibição de login. Quando for carregado pela primeira vez, irá garantir que o usuário esteja desconectado (através da função: initController() ), para que, quando o usuário for clicar no link ‘Sair’, ele seja redirecionado para página de Login.

A função ‘Login’ exposta pelo viewModel (viewModel.login) usará o método: ‘AuthenticationService’ para validar as credenciais dos usuários e redirecionar para a Página Principal da aplicação (se as credenciais forem bem sucedidas) ou exibir uma mensagem de erro (no caso das credenciais não serem bem sucedidas).

Vejam o código abaixo:

/**
 * Arquivo: index.controller.js
 * Data: 06/05/2017
 * Descrição: arqivo responsável por lidar com toda a iteração e dados para a exibição no arquivo: login/index.view.html
 * Author: Glaucia Lemos
 * 
 */

(function() {
  'use strict';

  angular
    .module('app')
    .controller('Login.IndexController', Controller);

  function Controller($location, AuthenticationService) {
    var viewModel = this;

    viewModel.login = login;

    initController();

    function initController() {
      //Aqui iremos resetar o status do login:
      AuthenticationService.Logout();
    };

    function login() {
      viewModel.loading = true;
      AuthenticationService.Login(viewModel.username, viewModel.password, function(result) {
        if (result === true) {
          $location.path('/');
        } else {
          viewModel.error = 'Usuário e senha estão incorretos!';
          viewModel.loading = false;
        }
      });
    };
  }

})();

5.2) Arquivo: login/index.view.html

Já o arquivo ‘index.view.html’ da pasta login terá um formulário de login bem padrão, onde terá campos de usuário e senha. Para ambos os campos, usaremos a diretiva: ngMessages para exibirmos mensagens de validação.

Vejam na prática o código abaixo:

<div class="col-md-6 col-md-offset-3">
  <div class="alert alert-info">
    Usuário: teste
    <br /> Senha: teste
  </div>
  <h2>Entrar</h2>
  <form name="form" ng-submit="form.$valid && viewModel.login()" novalidate>
    <div class="form-group" ng-class="{ 'has-error': form.$submitted && form.username.$invalid }">
      <label for="username">Usuário</label>
      <input type="text" name="username" class="form-control" ng-model="viewModel.username" required />
      <div ng-messages="form.$submitted && form.username.$error" class="validacao-msg">
        <div ng-message="required">Campo 'Usuário' é obrigatório</div>
      </div>
    </div>
    <div class="form-group" ng-class="{ 'has-error': form.$submitted && form.password.$invalid }">
      <label for="password">Senha</label>
      <input type="password" name="password" class="form-control" ng-model="viewModel.password" required />
      <div ng-messages="form.$submitted && form.password.$error" class="validacao-msg">
        <div ng-message="required">Campo 'Senha' é obrigatório</div>
      </div>
    </div>
    <div class="form-group">
      <button ng-disabled="viewModel.loading" class="btn btn-primary">Login</button>
      <img ng-if="viewModel.loading" src="https://goo.gl/xob0vd" />
    </div>
    <div ng-if="viewModel.error" class="alert alert-danger">{{viewModel.error}}</div>
  </form>
</div>

6) Arquivo: app.js

O arquivo app.js é o ponto de partida para a aplicação Angular, pois é aí que o módulo é declarado juntamente com as dependências, e contém a lógica de configuração e inicialização quando a aplicação é carregada pela primeira vez.

A função config() é usada para definir as rotas da aplicação usando o Angular UI Router. A função run() contém a lógica de inicialização da aplicação, incluindo o código que verifica o armazenamento local para manter o usuário conectado entre as atualizações das páginas e sessões do navegador. E adiciona um evento handler para o $locationChangeStart, que irá redirecionar os usuários não autenticados para a página de Login.

Vejam o código abaixo:

/**
 * Arquivo: app.js
 * Data: 11/05/2017
 * Descrição: arqivo responsável por lidar com toda a lógica da aplicação
 * Author: Glaucia Lemos
 * 
 */


(function() {
  'use strict';

  angular
    .module('app', ['ui.router', 'ngMessages', 'ngStorage', 'ngMockE2E'])
    .config(config)
    .run(run);

  function config($stateProvider, $urlRouterProvider) {
    //Rota default:
    $urlRouterProvider.otherwise("/");

    //Rotas da Aplicação:
    $stateProvider
      .state('home', {
        url: '/',
        templateUrl: 'home/index.view.html',
        controller: 'Home.IndexController',
        controllerAs: 'viewModel'
      })
      .state('login', {
        url: '/login',
        templateUrl: 'login/index.view.html',
        controller: 'Login.IndexController',
        controllerAs: 'viewModel'
      });
  }

  function run($rootScope, $http, $location, $localStorage) {
    // aqui é para manter o usuário logado mesmo se for atualizar a página:
    if ($localStorage.currentUser) {
      $http.defaults.headers.common.Authorization = 'Coders ' + $localStorage.currentUser.token;
    }

    //Aqui iremos redirecionar para a página de Login, caso o usuário não esteja logado:
    $rootScope.$on('$locationChangeStart', function(event, next, current) {
      var paginasPublicas = ['/login'];
      var paginaRestrita = paginasPublicas.indexOf($location.path()) === -1;

      if (paginaRestrita && !$localStorage.currentUser) {
        $location.path('/login');
      }
    });
  }
})();

6.2) Arquivo: index.html

E finalmente chegamos no arquivo final, o index.html. É o arquivo html principal da aplicação. Ele possui um template modelo e todos os CSS e scripts necessários para a aplicação.

<!DOCTYPE html>
<html lang="pt-br" ng-app="app">

<head>

  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>Tutorial - Autenticação JWT com AngularJs</title>

  <!-- Bootstrap theme -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">

  <!-- Google Fonts -->
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lato:400,300,700" />
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Merriweather:400,700,400italic" />

  <!-- application css -->
  <link href="content/app.css" rel="stylesheet" />

  <!-- Latest compiled and minified JavaScript -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>


</head>

<body>
  <div class="container theme-showcase" role="main">

    <!-- main app container -->
    <div class="jumbotron">
      <h1 class="text-center"><span class="glyphicon glyphicon-log-in" aria-hidden="true"></span> Coder4Coders</h1>
      <h2 class="text-center">Tutorial - Autenticação JWT com AngularJs</h2>
      <h3 class="text-center">Glaucia Lemos</h3>

    </div>
    <ui-view></ui-view>
  </div>

  <footer>
    <p class="text-center">Desenvolvido com muito <span class="glyphicon glyphicon-heart" aria-hidden="true"></span> Rio de Janeiro <a href="http://www.code4coders.wordpress.com" target="_blank"> - 2017</a></p>
  </footer>
  <!--/ footer -->

  <!-- angular scripts -->
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js"></script>
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular-messages.min.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.18/angular-ui-router.min.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/ngStorage/0.3.6/ngStorage.min.js"></script>

  <!-- application scripts -->
  <script src="app.js"></script>
  <script src="services/auth.service.js"></script>
  <script src="home/index.controller.js"></script>
  <script src="login/index.controller.js"></script>

  <!-- scripts para criar um back end falso -->
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular-mocks.js"></script>
  <script src="helpers/mock-backend.js"></script>
</body>

</html>

O resultado final deverá ser o seguinte:

Caso queiram realizar a demo, basta clicar neste link.

Considerações finais

Neste artigo eu pude mostrar um pouco do AngularJs (versão 1.x). Como há muitas empresas que ainda fazem uso da primeira versão, eu decidi escrever esse artigo.

Recomendo a vocês, caso queiram estudar o AngularJs a fazerem o seguinte curso:

Tudo sobre AngularJs – Rodrigo Branas:

Procurem não se aprofundar muito na versão 1.x, uma vez é que a tendência seja migrar para o Angular(2/4).

Caso queiram ver o código desenvolvido, segue o link:

Muito em breve desenvolverei a mesma aplicação, mas usando o Angular 2/4.

Momento Jabá: Não deixem de curtir, compartilhar e de se inscrever no meu canal do Youtube. Irei postar videos semanalmente no meu canal sobre a série de Introdução a TDD com C# & nUnit, e assim que eu terminar essa série, começarei o curso de Asp.NET MVC 5 com EF – Básico. E postarei mais artigos/tutoriais no meu site também. Fiquem ligados!

 

E até a próxima!