AngularJS series: Controllers

No último artigo dei uma breve introdução sobre o angularjs, hoje falarei sobre seus controllers e as boas práticas que podem ser adotadas em sua utilização.

Entendendo os controllers

Os controllers são os responsáveis pelo gerenciamento de dados apresentados em suas views. É neles onde ficará a sua lógica de negócios. Quando um controller é atrelado ao DOM através da diretiva ‘data-ng-controller’, um novo escopo ($scope) será criado e disponibilizado como um parâmetro injetável no construtor do controller.

Para um melhor entendimento, exemplificarei com o código abaixo.

<!DOCTYPE html>
<html data-ng-app="MyApp">
<head>
<meta charset="UTF-8">
<title>MyApp</title>
</head>
<body data-ng-controller="MyFirstController">
  <p>Hello, {{ name }}</p>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script type="text/javascript">
var app = angular.module('MyApp', []);

app.controller('MyFirstController', ['$scope', function($scope) {
$scope.name = 'Hudson Brendon';
}
]);
</script>
</body>
</html>

Você pode verificar o código em execução clicando aqui

No código acima, criei o primeiro controller com o método controller(), esse método recebe por padrão dois parâmetros, o nome que nesse caso ficou MyFirstController, e um array onde passamos o ‘$scope’ que será a cola entre o controller e a view, e a função que executa toda a lógica.

var app = angular.module('MyApp', []);

app.controller('MyFirstController', ['$scope', function($scope) {
$scope.name = 'Hudson Brendon';
}
]);

Adicionando comportamento ao $scope

Quando queremos executar alguma ação na view, precisamos adicionar comportamento ao escopo. Antes disso, no entanto, é necessário anexar os métodos ao $scope. Assim, eles ficarão disponíveis para a camada de visão. Para enteder melhor, analisaremos o código abaixo.

<!DOCTYPE html>
<html data-ng-app="MyApp">
<head>
<meta charset="UTF-8">
<title>MyApp</title>
</head>
<body data-ng-controller="MyFirstController">
<h1>{{ title }}</h1>
<form>
<input type="text" data-ng-model="item" placeholder="Informe um valor">
<button type="button" data-ng-click="addItem()">Adicionar item</button>
</form>
<h1>Itens</h1>
<ul>
<li data-ng-repeat="item in itens">{{ item }}</li>
</ul>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script type="text/javascript">
var app = angular.module('MyApp', []);
  app.controller('MyFirstController', ['$scope', function($scope) {
$scope.title = 'Adicionar item';
$scope.item = '';
$scope.itens = [];
$scope.addItem = function () {
$scope.itens.push($scope.item);
$scope.item = '';
}
}
]);
</script>
</body>
</html>

Você pode verificar o código em execução clicando aqui

No código acima, adicionamos o método addItem() ao nosso escopo, esse método tem como função adicionar no array de itens um item vindo do ‘data-ng-model’ presente em nossa view. Nesse ponto, adicionamos mais uma diretiva do angular, a ‘data-ng-click’, que executará o nosso método addItem() assim que identificar um evento de click no nosso botão, atualizando altomáticamente a nossa view graças a outra diretiva, a ‘data-ng-repeat’, que tem como função iterar sobre o array de itens e exibir todos os valores presentes, utilizando por baixo dos panos o two way data binding.

Herança de $scope

Não é segredo para ninguem os problemas com escopo existentes no javascript, o angularjs também não está livre deles. No angular temos uma coisa chamada herança de escopo que se não tomarmos cuidado pode dificultar muito nosso trabalho com o framework, analisemos o código abaixo para entender melhor.

<!DOCTYPE html>
<html data-ng-app="MyApp">
<head>
<meta charset="UTF-8">
<title>MyApp</title>
</head>
<body data-ng-controller="MyFirstController">
<h1>{{ band }}</h1>
<ul>
<li data-ng-repeat="music in musics">{{ music }}</li>
</ul>
<div data-ng-controller="MySecondController">
<h1>{{ band }}</h1>
<ul>
<li data-ng-repeat="music in musics">{{ music }}</li>
</ul>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script type="text/javascript">
var app = angular.module('MyApp', []);
app.controller('MyFirstController', ['$scope', function($scope) {
$scope.band = 'Far From Alaska';
$scope.musics = [
'Dino Vs. Dino',
'Politiks',
'Thievery',
'About Knives',
'Mama'
];
}]
);
app.controller('MySecondController', ['$scope', function($scope) {
$scope.band = 'Hellbenders';
$scope.musics = [
'Brand New Fear',
'Whorehouse Murder',
'Outburst',
'Hurricane',
'Shoot on Spot'
];
}]
);
</script>
</body>
</html>

Você pode verificar o código em execução clicando aqui

No código acima, criamos um sengundo controller chamado ‘MySecondController’, e adicionamos duas bandas e algumas musicas dessas respectivas bandas nos dois controllers, por fim adicionamos o nosso primeiro controller na tag <body>, e o segundo controller dentro do escopo do primeiro, passando o ‘MySecondController’ dentro de uma <div>. Até o momento, tudo sendo executado perfeitamente, sem nenhum problema, mas … E se omitirmos o “$scope.band = ‘Hellbenders’” do nosso controller ‘MySecondController’?

<!DOCTYPE html>
<html data-ng-app="MyApp">
<head>
<meta charset="UTF-8">
<title>MyApp</title>
</head>
<body data-ng-controller="MyFirstController">
<h1>{{ band }}</h1>
<ul>
<li data-ng-repeat="music in musics">{{ music }}</li>
</ul>
<div data-ng-controller="MySecondController">
<h1>{{ band }}</h1>
<ul>
<li data-ng-repeat="music in musics">{{ music }}</li>
</ul>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script type="text/javascript">
var app = angular.module('MyApp', []);
app.controller('MyFirstController', ['$scope', function($scope) {
$scope.band = 'Far From Alaska';
$scope.musics = [
'Dino Vs. Dino',
'Politiks',
'Thievery',
'About Knives',
'Mama'
];
}]
);
app.controller('MySecondController', ['$scope', function($scope) {
// $scope.band = 'Hellbenders'; // código comentado
$scope.musics = [
'Brand New Fear',
'Whorehouse Murder',
'Outburst',
'Hurricane',
'Shoot on Spot'
];
}]
);
</script>
</body>
</html>

Você pode verificar o código em execução clicando aqui

Viu o problema? Quando omitirmos a banda do segundo escopo, ele acabou assumindo o título do seu escopo pai, esse é o problema de utilizarmos esse padrão. Para solucionarmos esse problema, vamos utilizar o padrão intitulado ‘Controller as’ sugerido no style guide do John Papa.

Controllers da forma certa

O John Papa criou um style guide que vem sendo adotado em massa pela comunidade, nele ele exemplifica as melhores formas de estruturar seu código com angular, e um dos padrões para solucionar nosso problema de escopo é a utilização do ‘Controller as’, que deixa seu código mais claro e modularizado, facilitando o desenvolvimento e sanando qualquer problema que você venha a ter futuramente com escopo, indo diretamente ao ponto, vamos reescrever o código acima nesse padrão.

<!DOCTYPE html>
<html data-ng-app="MyApp">
<head>
<meta charset="UTF-8">
<title>MyApp</title>
</head>
<body data-ng-controller="MyFirstController as First">
<h1>{{ First.band }}</h1>
<ul>
<li data-ng-repeat="music in First.musics">{{ music }}</li>
</ul>
<div data-ng-controller="MySecondController as Second">
<h1>{{ Second.band }}</h1>
<ul>
<li data-ng-repeat="music in Second.musics">{{ music }}</li>
</ul>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script type="text/javascript">
var app = angular.module('MyApp', []);
app.controller('MyFirstController', MyFirstController);
function MyFirstController() {
var vm = this;
vm.band = 'Far From Alaska';
vm.musics = [
'Dino Vs. Dino',
'Politiks',
'Thievery',
'About Knives',
'Mama'
];
}
app.controller('MySecondController', MySecondController);
function MySecondController() {
var vm = this;
vm.band = 'Hellbenders';
vm.musics = [
'Brand New Fear',
'Whorehouse Murder',
'Outburst',
'Hurricane',
'Shoot on Spot'
];
}
</script>
</body>
</html>

Você pode verificar o código em execução clicando aqui

No código acima reescrevemos os nossos controllers utilizando o padrão proposto pelo John Papa, nesse padrão temos um código mais clean e de fácil entendimento, onde no método ‘controller()’ passamos apenas o nome e a função a ser executada que é definida logo abaixo. Nessas funções, utilizamos o padrão ‘vm’ (View Model) que facilita o uso do ‘this’ evitando com isso o uso do ‘$scope’.

app.controller('MyFirstController', MyFirstController);
function MyFirstController() {
var vm = this;
vm.band = 'Far From Alaska';
vm.musics = [
'Dino Vs. Dino',
'Politiks',
'Thievery',
'About Knives',
'Mama'
];
}
app.controller('MySecondController', MySecondController);
function MySecondController() {
var vm = this;
vm.band = 'Hellbenders';
vm.musics = [
'Brand New Fear',
'Whorehouse Murder',
'Outburst',
'Hurricane',
'Shoot on Spot'
];
}

Por fim, atrelamos os nossos controllers a variáveis no formato ‘Controller as’ e através delas acessamos os nossos dados.

<body data-ng-controller="MyFirstController as First">
<h1>{{ First.band }}</h1>
<ul>
<li data-ng-repeat="music in First.musics">{{ music }}</li>
</ul>
<div data-ng-controller="MySecondController as Second">
<h1>{{ Second.band }}</h1>
<ul>
<li data-ng-repeat="music in Second.musics">{{ music }}</li>
</ul>
</div>

Conclusão

Como visto aqui, os controllers do angular são algo relativamente simples, mas que temos que tomar cuidado quando o assunto é escopo, mas que podemos utilizar das boas práticas difundidas na comunidade para resolver a maioria dos problemas.