AngularJS e Diretivas: Como criar um custom control básico

Uma das muitas features que o AngularJS entrega é a possibilidade de construir nossos próprios controles para reaproveitamento, devido a extensibilidade das diretivas (conceito que foi bastante explorado neste post).

Mas o que seria um custom control? Sabe aqueles controles de ratings, formulários de logon, efeitos de carrossel, etc? Estes são exemplos de controles customizados.

Um custom control sempre encapsula uma saída visual através de diretivas próprias, portanto temos o combo Html+Javascript na construção do recurso.

Você se recorda o que é diretiva? No AngularJS temos uma série delas em nosso HTML, todas elas iniciam com ng-*. Você já viu a ng-app, ng-controller, ng-repeat e ng-model em um de nossos artigos. Estas são diretivas built-in, ou seja, já vem com o AngularJS. Mas não estamos restritos a elas, sendo que o AngularJS fornece os mecanismos para criar as nossas próprias.

É através da criação destas diretivas que iremos construir os nossos customs controls.

Para este artigo, vamos criar um controle básico, apenas para nos aprofundarmos mais um pouco no AngularJS.

O controle será um formulário de login contendo um campo para usuário, senha, um botão para efetuar o login e um botão de “esqueci a minha senha”. O comportamento será bem simples e não intenta ser aquilo que você deveria implementar na realidade num controle com este foco.

Antes de chegar a tanto, vamos começar do mais simples para que o entendimento fique claro.

Crie um diretório qualquer e coloque os seguintes arquivos:

+ index.html
+ app.js
+ loginCustomControl.js

Também iremos baixar o LABjs para carregar nossos scripts assincronamente, do mesmo modo que fizemos nos posts anteriores.

Edite o seu index.html e deixe o da seguinte forma:

<!DOCTYPE html>

<html>

<head>

<title>AngularJS .::. Simple Login Control</title>

</head>

<body>

<div>

<h1>Custom Control Sample</h1>

<div login-control></div>

</div>

<script src=”LAB.min.js”></script>

<script>

$LAB

.script(“https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js&#8221;)

.script(‘loginCustomControl.js’)

.script(‘app.js’)

.wait(function(){

angular.bootstrap(document, [‘app’]);

});

</script>

</body>

</html>

A novidade, por enquanto, é a adição da diretiva login-control dentro de uma div. É ela que contém o template Html que precisaremos renderizar na tela. Ou seja, esta diretiva levará a uma linked function que realizará este trabalho para nós. O resto das linhas você já conhece – caso não conheça, clique aqui para aprender.

Vamos agora editar o loginCustomControl.js.

‘use strict’;

angular.module(‘app.directives.customcontrols’, [])

.directive(‘loginControl’, function() {

return {

template: ‘<h1>Meu primeiro control customizado!</h1>’

};

});

Aqui você declara um módulo do AngularJS chamado app.directives.customcontrols, mas você poderia colocar o nome que quisesse… Isto é mais ou menos como se fosse o nome da package para o Java ou .NET. O segundo argumento é uma coleção de módulos dependentes, neste caso não temos nenhum.

Dentro deste módulo também declaramos a nossa diretiva customizada. O primeiro argumento é o nome da diretiva. O segundo é a função que deverá ser executada para renderizar o controle, a qual retorna todos os argumentos de configuração a qual o compilador do AngularJS utilizará para que este controle seja exibido e utilizado da forma desejada.

No caso acima, estamos fazendo algo bem simples, que é configurar um template fixo contendo apenas um item de cabeçalho <h1>.

Aqui reside o primeiro ponto de atenção, muito importante e por vezes confuso para quem está começando: no javascript a diretiva é chamada loginControl, mas quando declarada no Html a mesma é separada por hífen, ficando login-control.

Isto ocorre por que o compilador do AngularJS possui uma etapa de normalização para associar uma diretiva com o seu equivalente na DOM (ou seja, no HTML). A ideia aqui é fazer o match entre diretiva no script e diretiva na DOM. Ocorre que a primeira é case sensitive e por padrão é camel case (letra do primeiro nome minúscula e dos próximos nomes maiúsculas). Já a DOM é case-insensitive, logo escrever login-control, LOGIN-CONTROL, Login-Control ou mesmo LoGiN-cOnTrOl da na mesma. Para resolver este problema, o AngularJS converte as palavras da DOM separadas por hífen para o camel case e assim faz a associação.

Por fim, para finalizar, falta editar o arquivo inicializador, o app.js, que deverá ficar assim:

angular.module(‘app’, [‘app.directives.customcontrols’]);

Este está bem fácil se você acompanhou os outros artigos: o primeiro argumento é o nome do módulo, o segundo é uma coleção de módulos que este tem dependência. A associação é simples de estabelecer: se a minha página tem uma diretiva chamada ng-app configurada com este módulo (“app”) e esta mesma página utiliza o controle customizado, temos que declarar o módulo onde ele está como uma dependência.

Isto já é o suficiente para funcionar. É só abrir o index.html e provavelmente dará tudo certo. Se não der, veja o que fez de errado comparando com o Plunker desta versão: http://plnkr.co/edit/appmltKl0KYQ1m1rhm32?p=preview

Vamos fazer a primeira alteração na diretiva… Mais acima eu frisei que a diretiva login-control está dentro de uma div. Mas nós podemos também criar diretivas que não precisam exibir como atributos dentro de um elemento já existente. Temos como configurá-la para ela poder ser escrita como um elemento HTML próprio! Ainda não entendeu? Pois altere o seu Html para isto:

<!DOCTYPE html>

<html>

<head>

<title>AngularJS .::. Simple Login Control</title>

</head>

<body>

<div>

<h1>Custom Control Sample</h1>

<login-control></login-control>

</div>

<script src=”LAB.min.js”></script>

<script>

$LAB

.script(‘https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js&#8217;)

.script(‘loginCustomControl.js’)

.script(‘app.js’)

.wait(function(){

angular.bootstrap(document, [‘app’]);

});

</script>

</body>

</html>

É isto que estou dizendo! Você acabou de extender o Html e criou uma nova TAG para ser utilizada! Claro que ainda não pronto… Ainda precisamos instruir a diretiva sobre este comportamento, o que faremos alterando o loginCustomControl.js para o seguinte:

‘use strict’;

angular.module(‘app.directives.customcontrols’, [])

.directive(‘loginControl’, function() {

return {

          restrict: ‘E’,

template: ‘<h1>Meu primeiro control customizado!</h1>’

};

});

A novidade é esta propriedade restrict, que é configurada com um valor ‘E’. Este ‘E’ vem de Elemento, ou seja, você está dizendo que a diretiva deve funcionar restritamente como um elemento. O padrão é ‘A’, de Atributo (a forma como estávamos declarando diretivas até agora). Você também pode aceitar as duas formas, basta configurar como ‘AE’.

Veja como ficou no plunker: http://plnkr.co/edit/1w3qpZ4cquJqbaYDiVeL?p=preview

Vamos mudar mais um pouco e tirar aquele <h1>Meu primeiro control customizado!</h1> do arquivo javascript para um template no próprio HTML.

Crie um arquivo chamado loginCustomControlTemplate.html e adicione a seguinte linha:

<h1>Meu primeiro control customizado!</h1>

Agora altere o loginCustomControl.js para recebermos este template em arquivo, ao invés de estar engessado no script. Repare que abandonamos a propriedade “template” e passamos a utilizar a “templateUrl”.

‘use strict’;

angular.module(‘app.directives.customcontrols’, [])

.directive(‘loginControl’, function() {

return {

restrict: ‘E’,

          templateUrl: ‘loginCustomControlTemplate.html’

};

});

Plunker: http://plnkr.co/edit/XxSfN5scJtMR5jYtxbTX?p=preview

Agora vamos dar um passo mais significativo, pois de tela de login até agora só vimos o título!

Primeiro, vamos alterar o arquivo loginCustomControl.js para trazermos uma nova configuração: a entrada do escopo (scope). Isto por que eu quero fazer o bind com as minhas informações de logon (usuário, senha, método para entrar e método para recuperar a senha). A ideia é criar uma entrada onde a página que irá utilizar este controle possa passar um controller que implemente estas informações de logon.

Tentando fazer uma analogia com linguagens orientadas a objetos como Java e C#, a ideia é criar uma propriedade que irá receber uma classe que implementa a interface descrita pelo template (a qual veremos na sequência). Alteremos então o loginCustomControl.js:

‘use strict’;

angular.module(‘app.directives.customcontrols’, [])

.directive(‘loginControl’, function() {

return {

restrict: ‘E’,

templateUrl: ‘loginCustomControlTemplate.html’ ,

            scope: {

                data: ‘=’

            }

};

});

A novidade é esta propriedade chamada scope (que faz parte da configuração da diretiva). Lá dentro temos uma propriedade chamada data. Este propriedade somos nós que definimos e poderia chamar qualquer coisa. Uma vez definido, é este o nome que iremos utilizar na página que chama o controller para passar o provedor com a implementação da interface especificada pelo template que ainda iremos alterar.

Este nome propriedade é um termo OO para fazer uma analogia, mas no AngularJS o comportamento é bem diferente. O que estamos fazendo aqui é criando um escopo interno isolado (Isolate Scope), que não irá se misturar com o escopo externo da diretiva, porém estão mapeados. Ou seja, de fora conseguimos chegar no Isolate Scope, mas não diretamente, somente via algum intermediário, que será o que eu chamei de provider.

Este sinal de igual (“=”) isolado na configuração da propriedade é um atalho que diz ao compilador que o nome da variável é exatamente o mesmo nome do atributo a qual ele deverá fazer o binding. Caso queira que o compilador faça binding em outro nome (sinceramente eu não sei por que faria), era só digitar assim: data: ‘=anything’.

Vamos a alteração do loginCustomControlTemplate.html, que terá aquilo que eu chamei de interface que os controllers deverão implementar.

<div>

<h1>Login Control</h1>

<label for=”lc_username”>Usuário: </label>

<input id=”lc_username” type=”text” placeholder=”Seu Usuário” ng-model=”data.usuario”>

<label for=”lc_password”>Senha: </label>

<input id=”lc_password” type=”password” placeholder=”Sua Senha” ng-model=”data.senha”>

<br>

<input type=”button” value=”Entrar” ng-click=”data.entrar()”>

<input type=”button” value=”Esqueci a minha senha” ng-click=”data.esqueci()”>

</div>

Destaquei os pontos mais importantes:
+ data.usuario
+ data.senha
+ data.entrar()
+ data.esqueci()

Logo, duas propriedades e dois métodos. Esta a minha interface. Todos os meus provedores que queiram utilizar este template deverão implementar este contrato no controller. Esta palavra data que precede a propriedade é o Isolate Scopo que configuramos no script anterior. Se quiser alterar o nome lá, terá que alterar neste template também.

Agora vamos criar o provedor. Vou criar dois, apenas para ilustrar. Crie um arquivo chamado LoginController.js e adicione o seguinte conteúdo:

angular.module(‘app.controller.login’,[]);

function LoginController($scope, $window){

$scope.alertProvider = {

entrar : function(){

alert(‘usuário: ‘ + $scope.alertProvider.usuario + ‘ | senha: ‘ + $scope.alertProvider.senha);

},

esqueci : function(){

alert(‘Esqueci’);

}

};

$scope.urlProvider = {

entrar : function(){

$window.location.href = ‘entrar.html?user=’ + $scope.urlProvider.usuario;

},

esqueci : function(){

$window.location.href = ‘esqueci.html?user=’ + $scope.urlProvider.usuario;

}

};

};

No código acima, declaramos um controller que recebe o $scope e a diretiva $window (opcional), que vem com o AngularJS – utilizaremos ela para fazer o redirecionamento de páginas em um dos provedores (provavelmente você não teria isto em seus próprios provedores).

Ademais, temos dois provedores: um chamado alertProvider e outro chamado urlProvider. O primeiro simplesmente chama um alert com as credenciais, o segundo redireciona para uma página para realizar a autenticação ou o serviço de reativação de senha. Veja que os dois declaram as funções do contrato e fazem uso das propriedades, no contexto de Isolate Scope (por isto utilizam o nome do provider – é ele o intermediário que citei na explicação sobre o recurso).

Agora que já está quase tudo pronto, não podemos nos esquecer de incluir a dependência deste novo módulo controller que adicionamos no arquivo app.js:

angular.module(‘app’, [‘app.directives.customcontrols’, ‘app.controller.login’]);

Por fim, basta você alterar a página index.html para passar qualquer um dos provider para o seu custom-control. Vamos ver:

<!DOCTYPE html>

<html>

<head>

<title>AngularJS .::. Simple Login Control</title>

</head>

<body>

<div ng-controller=”LoginController”>

<h1>Custom Control Sample</h1>

<login-control data=”alertProvider”></login-control>

</div>

<script src=”LAB.min.js”></script>

<script>

$LAB

.script(‘https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js&#8217;)

.script(‘loginCustomControl.js’)

.script(‘loginController.js’)

.script(‘app.js’)

.wait(function(){

angular.bootstrap(document, [‘app’]);

});

</script>

</body>

</html>

Veja que configuramos o controller que criamos na diretiva ng-controller (que agora você já sabe que na verdade se chama ngController). Além disto, passamos para a Isolate Scope data o provider que iremos utilizar.

Quando você digitar o usuário e senha e clicar no botão “Entrar” irá receber um alert ambas as informações.

Agora troque o provider para <login-control data=”urlProvider”></login-control>

Ao clicar no mesmo botão “Entrar” você será redirecionado para a página entrar.html?user=, que obviamente não existe, visto que não criamos (fique a vontade).

Lá no plunker tem uma página para cada provider (index.html e loginAlert.html), fique a vontade para visitar e brincar com o código: http://plnkr.co/edit/ZxedzKfBo2WtzWNiiqQD

É isto aí pessoal, com apenas duas lições vocês já podem fazer uma série de coisas bacanas em seus projetos.

Etiquetado , , , ,

3 pensamentos sobre “AngularJS e Diretivas: Como criar um custom control básico

  1. Caraka, mto foda seu post!!!

  2. Filipe disse:

    Muito bom seu post, obrigado por compartilhar.

  3. Silas Stoffel disse:

    Show, belo cenário explicativo, parabéns ao autor

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: