Controllers
ou Controladores
controllerAs View Syntax
Utilize a sintaxe
controllerAs
ao invés da sintaxeclássica controller com $scope
.Por que? Controllers são construídos, "iniciados", e fornecem um nova instância única, e a sintaxe
controllerAs
é mais próxima de um construtor JavaScript do que asintaxe clássica do $scope
.Por que? Isso promove o uso do binding de um objeto "pontuado", ou seja, com propriedades na View (ex.
customer.name
ao invés dename
), que é mais contextual, legível, e evita qualquer problema com referências que podem ocorrer sem a "pontuação"Por que? Ajuda a evitar o uso de chamadas ao
$parent
nas Views com controllers aninhados.<!-- evite --> <div ng-controller="Customer"> {{ name }} </div>
<!-- recomendado --> <div ng-controller="Customer as customer"> {{ customer.name }} </div>
controllerAs Controller Syntax
Utilize a sintaxe
controllerAs
ao invés da sintaxeclássica controller com $scope
.A sintaxe
controllerAs
usa othis
dentro dos controllers que fica ligado ao$scope
.Por que? O
controllerAs
é uma forma mais simples de lidar com o$scope
. Você ainda poderá fazer o bind para a View e ainda poderá acessar os métodos do$scope
.Por que? Ajuda a evitar a tentação de usar os métodos do
$scope
dentro de um controller quando seria melhor evitá-los ou movê-los para um factory. Considere utilizar o$scope
em um factory, ou em um controller apenas quando necessário. Por exemplo, quando publicar e subscrever eventos usando$emit
,$broadcast
, ou$on
considere mover estes casos para um factory e invocá-los a partir do controller./* evite */ function Customer($scope) { $scope.name = {}; $scope.sendMessage = function() { }; }
/* recomendado - mas veja a próxima sessão */ function Customer() { this.name = {}; this.sendMessage = function() { }; }
controllerAs with vm
Utilize uma variável de captura para o
this
quando usar a sintaxecontrollerAs
. Escolha um nome de variável consistente comovm
, que representa o ViewModel.Por que? A palavra-chave
this
é contextual e quando usada em uma função dentro de um controller pode mudar seu contexto. Capturando o contexto dothis
evita a ocorrência deste problema./* evite */ function Customer() { this.name = {}; this.sendMessage = function() { }; }
/* recomendado */ function Customer() { var vm = this; vm.name = {}; vm.sendMessage = function() { }; }
Nota: Você pode evitar qualquer jshint warnings colocando o comentário abaixo acima da linha de código.
/* jshint validthis: true */ var vm = this;
Nota: Quando watches são criados no controller utilizando o
controller as
, você pode observar o objetovm.*
utilizando a seguinte sintaxe. (Crie watches com cuidado pois eles deixam o ciclo de digest mais "carregado".)$scope.$watch('vm.title', function(current, original) { $log.info('vm.title was %s', original); $log.info('vm.title is now %s', current); });
Bindable Members Up Top
Coloque os objetos que precisam de bind no início do controller, em ordem alfabética, e não espalhados através do código do controller.
Por que? Colocar os objetos que precisam de bind no início torna mais fácil de ler e te ajuda a instantaneamente identificar quais objetos do controller podem ser utilizados na View.
Por que? Setar funções anônimas pode ser fácil, mas quando essas funções possuem mais de 1 linha do código elas podem dificultar a legibilidade. Definir as funções abaixo dos objetos que necessitam de bind (as funções serão elevadas pelo JavaScript Hoisting) move os detalhes de implementação para o final do controller, mantém os objetos que necessitam de bind no topo, e deixa o código mais fácil de se ler.
/* evite */ function Sessions() { var vm = this; vm.gotoSession = function() { /* ... */ }; vm.refresh = function() { /* ... */ }; vm.search = function() { /* ... */ }; vm.sessions = []; vm.title = 'Sessions';
/* recomendado */ function Sessions() { var vm = this; vm.gotoSession = gotoSession; vm.refresh = refresh; vm.search = search; vm.sessions = []; vm.title = 'Sessions'; //////////// function gotoSession() { /* */ } function refresh() { /* */ } function search() { /* */ }
Nota: Se a função possuir apenas 1 linha considere mantê-la no topo, desde que a legibilidade não seja afetada.
/* evite */ function Sessions(data) { var vm = this; vm.gotoSession = gotoSession; vm.refresh = function() { /** * linhas * de * código * afetam * a * legibilidade */ }; vm.search = search; vm.sessions = []; vm.title = 'Sessions';
/* recomendado */ function Sessions(dataservice) { var vm = this; vm.gotoSession = gotoSession; vm.refresh = dataservice.refresh; // 1 linha está OK vm.search = search; vm.sessions = []; vm.title = 'Sessions';
Function Declarations to Hide Implementation Details
Utilize declarações de funções para esconder detalhes de implementação. Mantenha seus objetos que necessitam de bind no topo. Quando você precisar fazer o bind de uma função no controller, aponte ela para a declaração de função que aparece no final do arquivo. Ela está ligada diretamente aos objetos que precisam de bind no início do arquivo. Para mais detalhes veja este post.
Por que? Colocar os objetos que precisam de bind no início torna mais fácil de ler e te ajuda a instantaneamente identificar quais objetos do controller podem ser utilizados na View. (Mesmo do item anterior.)
Por que? Colocar os detalhes de implementação de uma função no final do arquivo coloca a complexidade fora do foco, logo, você pode focar nas coisas importantes no topo.
Por que? Declarações de funções são içadas, logo, não existe problema de se utilizar uma função antes dela ser definida (como haveria com expressões de função).
Por que? Você nunca precisará se preocupar com declarações de funções quebrarem seu código por colocar
var a
antes devar b
por quea
depende deb
.Por que? A ordenação é crítica em expressões de função.
/** * evite * Usar expressões de funções. */ function Avengers(dataservice, logger) { var vm = this; vm.avengers = []; vm.title = 'Avengers'; var activate = function() { return getAvengers().then(function() { logger.info('Activated Avengers View'); }); } var getAvengers = function() { return dataservice.getAvengers().then(function(data) { vm.avengers = data; return vm.avengers; }); } vm.getAvengers = getAvengers; activate(); }
Note-se que as coisas importantes estão espalhadas no exemplo anterior. No exemplo abaixo, nota-se que as coisas importantes do javascript estão logo no topo. Por exemplo, os objetos que precisam de bind no controller como
vm.avengers
evm.title
. Os detalhes de implementação estão abaixo. Isto é mais fácil de ler./* * recomendado * Usar declarações de funções * e objetos que precisam de bind no topo. */ function Avengers(dataservice, logger) { var vm = this; vm.avengers = []; vm.getAvengers = getAvengers; vm.title = 'Avengers'; activate(); function activate() { return getAvengers().then(function() { logger.info('Activated Avengers View'); }); } function getAvengers() { return dataservice.getAvengers().then(function(data) { vm.avengers = data; return vm.avengers; }); } }
Defer Controller Logic
Remova a lógica do controller delegando ela a services e factories.
Por que? A lógica pode ser reutilizada em múltiplos controllers quando colocada em um service e exposta através de uma função.
Por que? A lógica em um serviço pode ser mais facilmente isolada em um teste unitário, enquanto a lógica feita no controlador pode ser facilmente mockada.
Por que? Remove as dependências e esconde os detalhes de implementação do controlador.
/* evite */ function Order($http, $q) { var vm = this; vm.checkCredit = checkCredit; vm.total = 0; function checkCredit() { var orderTotal = vm.total; return $http.get('api/creditcheck').then(function(data) { var remaining = data.remaining; return $q.when(!!(remaining > orderTotal)); }); }; }
/* recomendado */ function Order(creditService) { var vm = this; vm.checkCredit = checkCredit; vm.total = 0; function checkCredit() { return creditService.check(); }; }
Keep Controllers Focused
Defina um controller para a view e tente não reutilizar o controller para outras views. Ao invés disso, coloque as lógicas reaproveitáveis em factories e mantenha o controller simples e focado em sua view.
Por que? Reutilizar controllers em várias views é arriscado e um boa cobertura de testes end to end (e2e) é obrigatório para se garantir estabilidade em grandes aplicações.
Assigning Controllers
Quando um controller deve ser pareado com sua view e algum componente pode ser reutilizado por outros controllers ou views, defina controllers juntamente de suas rotas.
Nota: Se uma View é carregada de outra forma que não seja através de uma rota, então utilize a sintaxe
ng-controller="Avengers as vm"
.Por que? Parear os controllers nas rotas permite diferentes rotas invocarem diferentes pares de controllers e views. Quando um controller é utilizado na view usando a sintaxe
ng-controller
, esta view sempre será associada ao mesmo controller./* evite - quando utilizar com uma rota e emparelhamento dinâmico é desejado */ // route-config.js angular .module('app') .config(config); function config($routeProvider) { $routeProvider .when('/avengers', { templateUrl: 'avengers.html' }); }
<!-- avengers.html --> <div ng-controller="Avengers as vm"> </div>
/* recomendado */ // route-config.js angular .module('app') .config(config); function config($routeProvider) { $routeProvider .when('/avengers', { templateUrl: 'avengers.html', controller: 'Avengers', controllerAs: 'vm' }); }
<!-- avengers.html --> <div> </div>