Um tutorial passo a passo para seu primeiro aplicativo AngularJS

Publicados: 2022-03-11
O AngularJS evoluiu e ficou ainda melhor. Agora chamado de Angular, foi totalmente reescrito para um novo fluxo de trabalho de desenvolvimento. Confira nosso novo tutorial de Angular 5 e ainda mais novo tutorial de pilha completa de Angular 6, com Material e Firebase.

O que é AngularJS?

AngularJS é uma estrutura JavaScript MVC desenvolvida pelo Google que permite criar aplicativos front-end bem estruturados, facilmente testáveis ​​e de fácil manutenção.

E por que devo usar?

Se você ainda não experimentou o AngularJS, está perdendo. A estrutura consiste em um conjunto de ferramentas totalmente integrado que o ajudará a criar aplicativos do lado do cliente bem estruturados e ricos de maneira modular — com menos código e mais flexibilidade.

O AngularJS estende o HTML fornecendo diretivas que adicionam funcionalidade à sua marcação e permitem que você crie modelos dinâmicos poderosos. Você também pode criar suas próprias diretivas, criando componentes reutilizáveis ​​que atendam às suas necessidades e abstraindo toda a lógica de manipulação do DOM.

Ele também implementa a vinculação de dados bidirecional, conectando seu HTML (visualizações) aos seus objetos JavaScript (modelos) perfeitamente. Em termos simples, isso significa que qualquer atualização em seu modelo será imediatamente refletida em sua visualização sem a necessidade de qualquer manipulação de DOM ou manipulação de eventos (por exemplo, com jQuery).

Angular fornece serviços em cima do XHR que simplificam drasticamente seu código e permitem que você abstraia chamadas de API em serviços reutilizáveis. Com isso, você pode mover seu modelo e lógica de negócios para o front-end e criar aplicativos Web agnósticos de back-end.

Por fim, adoro o Angular por causa de sua flexibilidade em relação à comunicação do servidor. Como a maioria das estruturas JavaScript MVC, ele permite que você trabalhe com qualquer tecnologia do lado do servidor, desde que possa servir seu aplicativo por meio de uma API Web RESTful. Mas o Angular também fornece serviços em cima do XHR que simplificam drasticamente seu código e permitem que você abstraia chamadas de API em serviços reutilizáveis. Como resultado, você pode mover seu modelo e lógica de negócios para o front-end e criar aplicativos Web independentes de back-end. Neste post, faremos exatamente isso, um passo de cada vez.

Então, por onde eu começo?

Primeiro, vamos decidir a natureza do aplicativo que queremos construir. Neste guia, preferimos não gastar muito tempo no back-end, então escreveremos algo baseado em dados facilmente acessíveis na Internet, como um aplicativo de feed esportivo!

Como sou um grande fã de automobilismo e Fórmula 1, usarei um serviço de API de autosport para atuar como nosso back-end. Felizmente, os caras da Ergast são gentis o suficiente para fornecer uma API de automobilismo gratuita que será perfeita para nós.

Para uma prévia do que vamos construir, dê uma olhada na demonstração ao vivo. Para embelezar a demonstração e mostrar alguns modelos Angular, apliquei um tema Bootstrap do WrapBootstrap, mas como este artigo não é sobre CSS, vou abstraí-lo dos exemplos e deixá-lo de fora.

Tutorial de primeiros passos

Vamos iniciar nosso aplicativo de exemplo com algum clichê. Eu recomendo o projeto de semente angular, pois ele não apenas fornece um ótimo esqueleto para bootstrapping, mas também prepara o terreno para testes de unidade com Karma e Jasmine (não faremos nenhum teste nesta demonstração, então apenas deixe essas coisas de lado por enquanto; veja a Parte 2 deste tutorial para obter mais informações sobre como configurar seu projeto para testes unitários e de ponta a ponta).

EDIT (maio de 2014): Desde que escrevi este tutorial, o projeto angular-seed passou por algumas mudanças pesadas (incluindo a adição de Bower como gerenciador de pacotes). Se você tiver alguma dúvida sobre como implantar o projeto, dê uma olhada rápida na primeira seção do guia de referência. Na Parte 2 deste tutorial, Bower, entre outras ferramentas, é abordado com mais detalhes.

OK, agora que clonamos o repositório e instalamos as dependências, o esqueleto do nosso aplicativo ficará assim:

tutorial angularjs - comece com o esqueleto

Agora podemos começar a codificar. Como estamos tentando construir um feed esportivo para um campeonato de corrida, vamos começar com a visão mais relevante: a tabela do campeonato .

a mesa do campeonato

Dado que já temos uma lista de drivers definida em nosso escopo (aguarde comigo – chegaremos lá), e ignorando qualquer CSS (para facilitar a leitura), nosso HTML pode ficar assim:

 <body ng-app="F1FeederApp" ng-controller="driversController"> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> {{driver.Driver.givenName}}&nbsp;{{driver.Driver.familyName}} </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table> </body>

A primeira coisa que você notará neste modelo é o uso de expressões (“{{“ e “}}”) para retornar valores de variáveis. No desenvolvimento do AngularJS, as expressões permitem que você execute alguns cálculos para retornar um valor desejado. Algumas expressões válidas seriam:

  • {{ 1 + 1 }}
  • {{ 946757880 | date }}
  • {{ user.name }}

Efetivamente, as expressões são snippets semelhantes a JavaScript. Mas apesar de ser muito poderoso, você não deve usar expressões para implementar nenhuma lógica de nível superior. Para isso, usamos diretivas.

Entendendo as Diretivas Básicas

A segunda coisa que você notará é a presença de ng-attributes , que você não veria em uma marcação típica. Essas são diretivas.

Em um nível alto, as diretivas são marcadores (como atributos, tags e nomes de classe) que dizem ao AngularJS para anexar um determinado comportamento a um elemento DOM (ou transformá-lo, substituí-lo etc.). Vamos dar uma olhada nos que já vimos:

  • A diretiva ng-app é responsável por inicializar seu aplicativo definindo seu escopo. No AngularJS, você pode ter vários aplicativos na mesma página, portanto, essa diretiva define onde cada aplicativo distinto começa e termina.

  • A diretiva ng-controller define qual controller será responsável pela sua view. Nesse caso, denotamos o driversController , que fornecerá nossa lista de drivers ( driversList ).

  • A diretiva ng-repeat é uma das mais usadas e serve para definir o escopo do seu template ao fazer um loop pelas coleções. No exemplo acima, ele replica uma linha na tabela para cada driver em driversList .

Adicionando controladores

Claro, não há uso para nossa visão sem um controlador. Vamos adicionar driversController ao nosso controllers.js:

 angular.module('F1FeederApp.controllers', []). controller('driversController', function($scope) { $scope.driversList = [ { Driver: { givenName: 'Sebastian', familyName: 'Vettel' }, points: 322, nationality: "German", Constructors: [ {name: "Red Bull"} ] }, { Driver: { givenName: 'Fernando', familyName: 'Alonso' }, points: 207, nationality: "Spanish", Constructors: [ {name: "Ferrari"} ] } ]; });

Você deve ter notado a variável $scope que estamos passando como parâmetro para o controlador. A variável $scope deve vincular seu controlador e visualizações. Em particular, ele contém todos os dados que serão usados ​​em seu modelo. Qualquer coisa que você adicionar a ele (como o driversList no exemplo acima) estará diretamente acessível em suas visualizações. Por enquanto, vamos trabalhar apenas com um array de dados fictício (estático), que substituiremos posteriormente com nosso serviço de API.

Agora, adicione isso ao app.js:

 angular.module('F1FeederApp', [ 'F1FeederApp.controllers' ]);

Com essa linha de código, na verdade inicializamos nosso aplicativo e registramos os módulos dos quais ele depende. Voltaremos a esse arquivo ( app.js ) mais tarde.

Agora, vamos juntar tudo em index.html :

 <!DOCTYPE html> <html> <head> <title>F-1 Feeder</title> </head> <body ng-app="F1FeederApp" ng-controller="driversController"> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> {{driver.Driver.givenName}}&nbsp;{{driver.Driver.familyName}} </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table> <script src="bower_components/angular/angular.js"></script> <script src="bower_components/angular-route/angular-route.js"></script> <script src="js/app.js"></script> <script src="js/services.js"></script> <script src="js/controllers.js"></script> </body> </html>

Erros menores do módulo, agora você pode inicializar seu aplicativo e verificar sua lista (estática) de drivers.

Nota: Se você precisar de ajuda para depurar seu aplicativo e visualizar seus modelos e escopo no navegador, recomendo dar uma olhada no incrível plugin Batarang para Chrome.

Carregando dados do servidor

Como já sabemos como exibir os dados do nosso controller em nossa view, é hora de realmente buscar dados ao vivo de um servidor RESTful.

Para facilitar a comunicação com servidores HTTP, o AngularJS fornece os serviços $http e $resource . O primeiro é apenas uma camada sobre XMLHttpRequest ou JSONP, enquanto o último fornece um nível mais alto de abstração. Usaremos $http .

Para abstrair nossas chamadas de API do servidor do controlador, vamos criar nosso próprio serviço personalizado que buscará nossos dados e atuará como um wrapper em torno de $http adicionando isso ao nosso services.js :

 angular.module('F1FeederApp.services', []). factory('ergastAPIservice', function($http) { var ergastAPI = {}; ergastAPI.getDrivers = function() { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK' }); } return ergastAPI; });

Com as duas primeiras linhas, criamos um novo módulo ( F1FeederApp.services ) e registramos um serviço dentro desse módulo ( ergastAPIservice ). Observe que passamos $http como parâmetro para esse serviço. Isso informa ao mecanismo de injeção de dependência do Angular que nosso novo serviço requer (ou depende ) do serviço $http .

De maneira semelhante, precisamos dizer ao Angular para incluir nosso novo módulo em nosso aplicativo. Vamos registrá-lo com app.js , substituindo nosso código existente por:

 angular.module('F1FeederApp', [ 'F1FeederApp.controllers', 'F1FeederApp.services' ]);

Agora, tudo o que precisamos fazer é ajustar um pouco nosso controller.js , incluir ergastAPIservice como uma dependência, e estaremos prontos:

 angular.module('F1FeederApp.controllers', []). controller('driversController', function($scope, ergastAPIservice) { $scope.nameFilter = null; $scope.driversList = []; ergastAPIservice.getDrivers().success(function (response) { //Dig into the responde to get the relevant data $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings; }); });

Agora recarregue o aplicativo e confira o resultado. Observe que não fizemos nenhuma alteração em nosso modelo, mas adicionamos uma variável nameFilter ao nosso escopo. Vamos colocar essa variável em uso.

Filtros

Excelente! Temos um controlador funcional. Mas mostra apenas uma lista de drivers. Vamos adicionar algumas funcionalidades implementando uma entrada de pesquisa de texto simples que filtrará nossa lista. Vamos adicionar a seguinte linha ao nosso index.html , logo abaixo da tag <body> :

 <input type="text" ng-model="nameFilter" placeholder="Search..."/>

Agora estamos usando a diretiva ng-model . Essa diretiva vincula nosso campo de texto à variável $scope.nameFilter e garante que seu valor esteja sempre atualizado com o valor de entrada. Agora, vamos visitar index.html mais uma vez e fazer um pequeno ajuste na linha que contém a diretiva ng-repeat :

 <tr ng-repeat="driver in driversList | filter: nameFilter">

Esta linha diz ng-repeat que, antes de gerar os dados, o array driversList deve ser filtrado pelo valor armazenado em nameFilter .

Nesse ponto, a vinculação de dados bidirecional entra em ação: toda vez que um valor é inserido no campo de pesquisa, o Angular imediatamente garante que o $scope.nameFilter que associamos a ele seja atualizado com o novo valor. Como a ligação funciona nos dois sentidos, no momento em que o valor nameFilter é atualizado, a segunda diretiva associada a ele (ou seja, a ng-repeat ) também recebe o novo valor e a visualização é atualizada imediatamente.

Recarregue o aplicativo e confira a barra de pesquisa.

barra de pesquisa de aplicativos

Observe que este filtro irá procurar a palavra-chave em todos os atributos do modelo, incluindo os que não estavam sendo usados. Digamos que queremos filtrar apenas por Driver.givenName e Driver.familyName : Primeiro, adicionamos driversController , logo abaixo de $scope.driversList = []; linha:

 $scope.searchFilter = function (driver) { var keyword = new RegExp($scope.nameFilter, 'i'); return !$scope.nameFilter || keyword.test(driver.Driver.givenName) || keyword.test(driver.Driver.familyName); };

Agora, de volta ao index.html , atualizamos a linha que contém a diretiva ng-repeat :

 <tr ng-repeat="driver in driversList | filter: searchFilter">

Recarregue o aplicativo mais uma vez e agora temos uma busca por nome.

Rotas

Nosso próximo objetivo é criar uma página de detalhes do piloto que nos permitirá clicar em cada piloto e ver os detalhes de sua carreira.

Primeiro, vamos incluir o serviço $routeProvider (em app.js ) que nos ajudará a lidar com essas variadas rotas de aplicativos . Em seguida, adicionaremos duas dessas rotas: uma para a tabela do campeonato e outra para os detalhes do piloto. Aqui está nosso novo app.js :

 angular.module('F1FeederApp', [ 'F1FeederApp.services', 'F1FeederApp.controllers', 'ngRoute' ]). config(['$routeProvider', function($routeProvider) { $routeProvider. when("/drivers", {templateUrl: "partials/drivers.html", controller: "driversController"}). when("/drivers/:id", {templateUrl: "partials/driver.html", controller: "driverController"}). otherwise({redirectTo: '/drivers'}); }]);

Com essa alteração, navegar para http://domain/#/drivers carregará o driversController e procurará a visualização parcial para renderizar em partials/drivers.html . Mas espere! Ainda não temos visualizações parciais, certo? Precisamos criá-los também.

Visualizações parciais

O AngularJS permitirá que você vincule suas rotas a controladores e visualizações específicos.

Mas primeiro, precisamos dizer ao Angular onde renderizar essas visualizações parciais. Para isso, usaremos a diretiva ng-view , modificando nosso index.html para espelhar o seguinte:

 <!DOCTYPE html> <html> <head> <title>F-1 Feeder</title> </head> <body ng-app="F1FeederApp"> <ng-view></ng-view> <script src="bower_components/angular/angular.js"></script> <script src="bower_components/angular-route/angular-route.js"></script> <script src="js/app.js"></script> <script src="js/services.js"></script> <script src="js/controllers.js"></script> </body> </html>

Agora, sempre que navegarmos pelas rotas do nosso aplicativo, o Angular carregará a visualização associada e a renderizará no lugar da tag <ng-view> . Tudo o que precisamos fazer é criar um arquivo chamado partials/drivers.html e colocar nossa tabela de campeonato HTML lá. Também usaremos essa chance para vincular o nome do motorista à nossa rota de detalhes do motorista:

 <input type="text" ng-model="nameFilter" placeholder="Search..."/> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList | filter: searchFilter"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> <a href="#/drivers/{{driver.Driver.driverId}}"> {{driver.Driver.givenName}}&nbsp;{{driver.Driver.familyName}} </a> </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table>

Finalmente, vamos decidir o que queremos mostrar na página de detalhes. Que tal um resumo de todos os fatos relevantes sobre o motorista (por exemplo, nascimento, nacionalidade) junto com uma tabela contendo seus resultados recentes? Para fazer isso, adicionamos a services.js :

 angular.module('F1FeederApp.services', []) .factory('ergastAPIservice', function($http) { var ergastAPI = {}; ergastAPI.getDrivers = function() { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK' }); } ergastAPI.getDriverDetails = function(id) { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/driverStandings.json?callback=JSON_CALLBACK' }); } ergastAPI.getDriverRaces = function(id) { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/results.json?callback=JSON_CALLBACK' }); } return ergastAPI; });

Desta vez, fornecemos o ID do motorista ao serviço para que recuperemos as informações relevantes apenas para um motorista específico. Agora, modificamos controllers.js :

 angular.module('F1FeederApp.controllers', []). /* Drivers controller */ controller('driversController', function($scope, ergastAPIservice) { $scope.nameFilter = null; $scope.driversList = []; $scope.searchFilter = function (driver) { var re = new RegExp($scope.nameFilter, 'i'); return !$scope.nameFilter || re.test(driver.Driver.givenName) || re.test(driver.Driver.familyName); }; ergastAPIservice.getDrivers().success(function (response) { //Digging into the response to get the relevant data $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings; }); }). /* Driver controller */ controller('driverController', function($scope, $routeParams, ergastAPIservice) { $scope.id = $routeParams.id; $scope.races = []; $scope.driver = null; ergastAPIservice.getDriverDetails($scope.id).success(function (response) { $scope.driver = response.MRData.StandingsTable.StandingsLists[0].DriverStandings[0]; }); ergastAPIservice.getDriverRaces($scope.id).success(function (response) { $scope.races = response.MRData.RaceTable.Races; }); });

O importante a notar aqui é que acabamos de injetar o serviço $routeParams no controlador de driver. Este serviço nos permitirá acessar nossos parâmetros de URL (para o :id , neste caso) usando $routeParams.id .

Agora que temos nossos dados no escopo, precisamos apenas da visão parcial restante. Vamos criar um arquivo chamado partials/driver.html e adicionar:

 <section> <a href="./#/drivers"><- Back to drivers list</a> <nav class="main-nav"> <div class="driver-picture"> <div class="avatar"> <img ng-show="driver" src="img/drivers/{{driver.Driver.driverId}}.png" /> <img ng-show="driver" src="img/flags/{{driver.Driver.nationality}}.png" /><br/> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </div> </div> <div class="driver-status"> Country: {{driver.Driver.nationality}} <br/> Team: {{driver.Constructors[0].name}}<br/> Birth: {{driver.Driver.dateOfBirth}}<br/> <a href="{{driver.Driver.url}}" target="_blank">Biography</a> </div> </nav> <div class="main-content"> <table class="result-table"> <thead> <tr><th colspan="5">Formula 1 2013 Results</th></tr> </thead> <tbody> <tr> <td>Round</td> <td>Grand Prix</td> <td>Team</td> <td>Grid</td> <td>Race</td> </tr> <tr ng-repeat="race in races"> <td>{{race.round}}</td> <td><img src="img/flags/{{race.Circuit.Location.country}}.png" />{{race.raceName}}</td> <td>{{race.Results[0].Constructor.name}}</td> <td>{{race.Results[0].grid}}</td> <td>{{race.Results[0].position}}</td> </tr> </tbody> </table> </div> </section>

Observe que agora estamos colocando a diretiva ng-show em bom uso. Esta diretiva só mostrará o elemento HTML se a expressão fornecida for true (ou seja, nem false , nem null ). Nesse caso, o avatar só aparecerá quando o objeto driver for carregado no escopo pelo controlador.

Toques finais

Adicione um monte de CSS e renderize sua página. Você deve terminar com algo assim:

página renderizada com CSS

Agora você está pronto para iniciar seu aplicativo e certificar-se de que ambas as rotas estão funcionando conforme desejado. Você também pode adicionar um menu estático ao index.html para melhorar os recursos de navegação do usuário. As possibilidades são infinitas.

EDIT (maio de 2014): Recebi muitos pedidos de uma versão para download do código que construímos neste tutorial. Por isso, decidi liberá-lo aqui (despojado de qualquer CSS). No entanto, eu realmente não recomendo baixá-lo, pois este guia contém todas as etapas necessárias para criar o mesmo aplicativo com suas próprias mãos, o que será um exercício de aprendizado muito mais útil e eficaz.

Conclusão

Neste ponto do tutorial, cobrimos tudo o que você precisa para escrever um aplicativo simples (como um alimentador de Fórmula 1). Cada uma das páginas restantes da demonstração ao vivo (por exemplo, tabela do campeonato de construtores, detalhes da equipe, calendário) compartilha a mesma estrutura e conceitos básicos que analisamos aqui.

Por fim, lembre-se de que o Angular é um framework muito poderoso e mal arranhamos a superfície em termos de tudo o que ele tem a oferecer. Na Parte 2 deste tutorial, daremos exemplos de por que o Angular se destaca entre suas estruturas MVC de front-end de pares: testabilidade. Analisaremos o processo de escrever e executar testes de unidade com o Karma, alcançando integração contínua com Yeomen, Grunt e Bower, e outros pontos fortes desse fantástico framework front-end.