Prefácio
O AngularJS é fácil de desenvolver, tem muitos recursos e bons efeitos, resultando em mais aplicações, e algumas armadilhas são acompanhadas. Este artigo lista alguns problemas comuns que o AngularJS é propenso a problemas. Vamos dar uma olhada juntos.
1. Estrutura do diretório MVC
AngularJS, para ser franco, é uma estrutura de MVC. Seu modelo não é tão claramente definido quanto a estrutura de backbone.js, mas sua arquitetura é apropriadamente. Quando você trabalha em uma estrutura de MVC, é comum classificá -lo por tipo de arquivo:
modelos/ _login.html _feed.htmlapp/ app.js controllers/ logincontroller.js feedcontroller.js Directives/ feedentryDirection.js Services/ logInservice.js FeedService.js Filters/ capatalizeFilter.js
Parece que essa parece ser uma estrutura óbvia, sem mencionar que os Rails fazem o mesmo. No entanto, uma vez que o aplicativo comece a se expandir, essa estrutura fará com que você abra muitos diretórios de uma só vez. Esteja você usando sublime, Visual Studio ou Vim combinado com a árvore nerd, você investirá muito tempo deslizando constantemente para cima e para baixo na árvore do diretório.
Ao contrário dos arquivos de divisão por tipo, em vez disso, podemos dividir arquivos por características:
app/ app.js feed/ _feed.html feedcontroller.js feedentrydirection.js feedService.js login/ _login.html logincontroller.js loginservice.js shared/ capatalizeFilter.js
Essa estrutura de diretório facilita a localização de todos os arquivos relacionados a um recurso e, assim, acelerar nosso progresso no desenvolvimento. Embora possa ser controverso colocar arquivos .html e .js em um só lugar, o tempo economizado é mais valioso.
2. Módulo
É muito comum colocar tudo no módulo principal. Para aplicativos pequenos, não há problema no começo, mas você em breve descobrirá que algo está trapaceando.
var app = angular.module ('App', []); App.Service ('MyService', function () {// Código de serviço}); App.Controller ('Myctrl', função ($ SCOPE, MyService) {// Código do controlador});Depois disso, uma estratégia comum é classificar objetos do mesmo tipo.
var Services = Angular.module ('Serviços', []); Services.Service ('MyService', function () {// Código de serviço}); var controladores = angular.module ('controladores', ['serviços']); controladores.Controller ('myctrl', função ($ scope, myService) {// Código do controlador}); var app = angular.module ('app', ['controladores', 'serviços']);Este método é semelhante à estrutura do diretório mencionada na primeira parte acima: não é bom o suficiente. De acordo com a mesma filosofia, pode ser classificada por características, o que leva à escalabilidade.
var sharedServicesModule = angular.module ('SharedServices', []); SharedServices.Service ('NetworkService', função ($ http) {}); var LoginModule = angular.module ('login', ['SharedServices']); LoginModule.Service ('LogInservice', function (NetworkService) {}); LoginModule.Controller ('Loginctrl', função ($ SCOPE, LOGINSERVICE) {}); var app = angular.module ('app', ['compartilhado', 'login']);Quando desenvolvemos um grande aplicativo, pode não ser tudo contido em uma única página. Colocar o mesmo tipo de recursos em um módulo facilita a reutilização dos módulos nos aplicativos.
3. Injeção de dependência
A injeção de dependência é um dos melhores padrões do AngularJS, o que simplifica o teste e é claro sobre confiar em qualquer objeto especificado. O método de injeção de AngularJS é muito flexível. A maneira mais fácil é apenas passar o nome de dependência na function do módulo:
var app = angular.module ('app', []); App.Controller ('malaCtrl', função ($ scope, $ timeout) {$ timeout (function () {console.log ($ scope);}, 1000);}); Aqui, é óbvio que o Mainctrl depende do $scope e $timeout .
Tudo é lindo até que você esteja pronto para implantá -lo na produção e querer otimizar seu código. Se você usar UglifyJs, o exemplo anterior se tornará assim:
var app = angular.module ("App", []); App.Controller ("MainCtrl", function (e, t) {t (function () {console.log (e)}, 1e3)})Como o AngularJS sabe de quem depende? O AngularJS fornece uma solução alternativa muito simples, a saber, passando dependências para uma matriz, o último elemento da matriz é uma função e todas as dependências são usadas como seus parâmetros.
App.Controller ('Mainctrl', ['$ scope', '$ timeout', function ($ scope, $ timeout) {$ timeout (function () {console.log ($ scope);}, 1000);}]);Fazer isso fará com que o código simplificasse, e o AngularJS sabe como interpretar essas dependências explícitas:
App.Controller ("MainCtrl", ["$ scope", "$ timeout", function (e, t) {t (function () {console.log (e)}, 1e3)}]))3.1 Dependência global
Isso geralmente acontece ao escrever programas AngularJS: um objeto tem uma dependência, e esse objeto se liga ao escopo global, o que significa que essa dependência está disponível em qualquer código AngularJS, mas isso destrói o modelo de injeção de dependência e causa alguns problemas, especialmente durante o processo de teste.
O uso do AngularJS facilita a encapsular essas dependências globais nos módulos, para que possam ser injetados como módulos padrão do AngularJS.
Subscore.js é uma ótima biblioteca que simplifica o código JavaScript em um estilo funcional e você pode convertê -lo em um módulo das seguintes maneiras:
var subdcore = angular.module ('subscore', []); subscore.factory ('_', function () {return window._; // subscore já deve ser carregado na página}); var app = angular.module ('app', ['subcore'); App.Controller ('malanctrl', ['$ scope', '_', function ($ scope, _) {init = function () {_.keys ($ scope);} init ();}]);Essa abordagem permite que o aplicativo continue a se desenvolver no estilo da injeção de dependência do AngularJS e também pode trocar o sublinhado durante a fase de teste.
Isso pode parecer trivial e desnecessário, mas se o seu código estiver usando o uso rigoroso (e ele deve ser usado), isso será necessário.
4. Expansão do controlador
O controlador é carne e batatas angulares e, se você não tiver cuidado, adicionará muita lógica, especialmente no início. O controlador nunca deve operar o DOM ou manter o seletor DOM, é aí que precisamos usar instruções e modelo NG. Da mesma forma, a lógica de negócios deve existir no serviço, não no controlador.
Os dados também devem ser armazenados no serviço, a menos que já estejam vinculados ao $ SCOPE. O serviço em si é Singleton e existe ao longo da vida do aplicativo, mas o controlador é transitório entre os estados do aplicativo. Se os dados forem salvos no controlador, quando forem instanciados novamente, eles precisam obter novamente os dados de algum lugar. Mesmo que os dados sejam armazenados no LocalSorage, a velocidade de pesquisa é uma ordem de magnitude mais lenta que as variáveis JavaScript.
AngularJS funciona bem ao seguir o princípio de responsabilidade única (SRP). Se o controlador for o coordenador entre a visualização e o modelo, ele deve conter o mínimo de lógica possível, o que também facilitará o teste.
5. Serviço vs fábrica
Quase todos os desenvolvedores do AngularJS ficam preocupados com esses substantivos quando ele é iniciante, o que realmente não merece, porque eles são apenas açúcar sintático por quase a mesma coisa!
Aqui estão suas definições no Código Fonte do AngularJS:
function factory (nome, factoryfn) {Return provedor (nome, {$ get: factoryfn}); } função service (nome, construtor) {retornar fábrica (nome, ['$ injetor', função ($ injector) {return $ injector.instantiate (construtor);}]);} A partir do código -fonte, você pode ver que o serviço simplesmente chama a função factory e o último chama a função provider . De fato, o AngularJS também fornece encapsulamento adicional provider para alguns valores, constantes e decoração, o que não leva a confusão semelhante, e sua documentação é muito clara.
Como o serviço chama apenas a função factory , qual é a diferença? A pista está em $injector.instantiate : nesta função, $injector cria uma nova instância no construtor service .
Aqui está um exemplo mostrando como um service e uma factory fazem a mesma coisa:
var app = angular.module ('app', []); App.Service ('HelloworldService', function () {this.hello = function () {return "Hello World";};}); App.Factory ('HelloworldFactory', function () {return {hello: function () {return "Hello World";}}}); Quando helloWorldService ou helloWorldFactory é injetado no controlador, todos eles têm um método Hello que retorna "Hello World". O construtor do service é instanciado uma vez quando declarado, e factory é passado toda vez que é injetado, mas ainda existe apenas uma instância factory . Todos providers são singletons.
Como você pode fazer a mesma coisa, por que você precisa de dois estilos diferentes? Comparado ao service , factory fornece mais flexibilidade porque pode retornar funções, que podem ser criadas posteriormente. Isso atende ao conceito de padrões de fábrica na programação orientada a objetos, onde uma fábrica pode ser um objeto que pode criar outros objetos.
App.Factory ('Hellofactory', function () {return function (nome) {this.name = name; this.hello = function () {return "hello" + this.name;};};};}); Aqui está um exemplo de controlador, usando service e duas factory , helloFactory retorna uma função que define o valor do name quando um novo objeto é criado.
App.Controller ('Helloctrl', função ($ SCOPE, HELLOWORLDSERVICE, HELLOWORLDFACTION, HELLOFACTORY) {init = function () {HelloworldService.hello (); // 'Hello World' HelloworldFactory.hello (); init ();});Quando você é iniciante, é melhor usar o serviço.
Factory também é útil ao projetar uma aula com muitos métodos privados:
App.Factory ('PrivateFactory', function () {var privatefunc = function (nome) {return name.split (""). reverse (). junção (""); // reverte o nome}; return {hello: function (name) {return "hello" + privatefunc (nome);}};}); Com este exemplo, podemos tornar o método privateFunc inacessível para a API pública do privateFactory . Esse padrão pode ser feito em service , mas é mais fácil na factory .
6. Batarang não é usado
O BATARANG é um excelente plug -in Chrome para desenvolver e testar aplicativos AngularJS.
O BATARANG fornece a capacidade de navegar por modelos, o que nos dá a capacidade de observar como o AngularJS está ligado ao escopo, o que é muito útil ao lidar com instruções e isolar uma faixa de valores ligados.
O BATARANG também fornece um gráfico de dependência, que é útil se formos expostos a uma base de código não testada, que pode determinar quais serviços devem ser focados.
Finalmente, o BATARANG fornece análise de desempenho. O Angular pode ser usado como um pacote e tem um bom desempenho, mas às vezes não é tão suave para um aplicativo cheio de instruções personalizadas e lógica complexa. Usando a ferramenta de desempenho do BATARANG, você pode observar diretamente qual função funciona mais tempo em um ciclo de digestão. As ferramentas de desempenho também podem mostrar uma árvore de relógios completa, o que é útil quando temos muitos observadores.
7. Muitos observadores
No ponto anterior, mencionamos que o AngularJS pode ser usado como um pacote e tem um bom desempenho. Como as verificações de dados sujas precisam ser concluídas em um ciclo de digestão, uma vez que o número de observadores cresça para cerca de 2000, esse ciclo causará problemas significativos de desempenho. (Não se pode dizer que o número 2000 causa uma queda significativa de desempenho, mas esse é um bom valor empírico. Na versão de liberação do AngularJS 1.3, já existem algumas alterações que permitem controle rigoroso dos ciclos de digestão.)
A seguinte "Expressão da função de execução imediata (IIFE)" imprimirá o número de todos os observadores na página atual. Você pode simplesmente colá -lo no console e observar os resultados. Este iife é derivado da resposta de Jared no Stackoverflow:
(function () {var root = $ (document.getElementsByTagName ('body')); var watchers = []; var f = function (element) {if (element.data (). hasOwnProperty ('$ scope')) {angular.foreach (eleg.data (). $ scope. Angular.ForEach (Element.Children (), função (ChildElement) {F ($ (ChildElement);});Dessa forma, o número de observadores é obtido, combinado com a árvore de relógios na seção de desempenho de Batarang, você deve ver onde existe código duplicado ou onde existem dados constantes e também possuem relógios.
Quando houver dados inalterados e você deseja usar o AngularJS para modelar, considere usar o Bindonce. O Bindonce é uma diretiva simples que permite que você use modelos no AngularJs, mas não adiciona ao relógio, o que garante que o número de relógios não cresça.
8. Faixa limitada de $ escopo
A herança baseada em protótipo JavaScript tem uma diferença sutil da herança baseada em classe em orientação a objetos orientados a objetos, o que geralmente não é um problema, mas essa sutileza é manifestada ao usar $scope . Em AngularJS, cada um $scope herda o $scope dos pais, que é chamado $rootScope no mais alto nível. ( $scope é um pouco diferente das diretivas tradicionais. Eles têm um certo escopo de ação e apenas herdam propriedades explicitamente declaradas.)
Devido às características da herança do protótipo, não é importante compartilhar dados entre as classes de pais e filhos, mas se você não tomar cuidado, é fácil usar mal a propriedade de um $scope pais.
Por exemplo, precisamos exibir um nome de usuário em uma barra de navegação, que é inserida no formulário de login. A seguinte tentativa deve funcionar:
<Div ng-controller = "Navctrl"> <pan> {{user}} </span> <div ng --Tontroller = "Loginctrl"> <pan> {{user}} </span> <input ng-model = "user"> </input> </div> <//Div>Então a pergunta é ...: o modelo NG do usuário está definido na entrada de texto. Quando o usuário inserir o conteúdo, qual modelo será atualizado? Navctrl ou Loginctrl, ou tudo?
Se você escolheu Loginctrl, pode ter entendido como o protótipo de herança funciona.
A cadeia de protótipo não funciona quando você recupera o literal. Se o NAVCTRL também for atualizado ao mesmo tempo, será necessário recuperar a cadeia de protótipo; Mas se o valor for um objeto, isso acontecerá. (Lembre -se, em JavaScript, funções, matrizes e objetos são todos objetos)
Portanto, para obter o comportamento esperado, você precisa criar um objeto no NAVCTRL, que pode ser referenciado pelo Loginctrl.
<Div ng --Controller = "Navctrl"> <pan> {{user.name}} </span> <div ng --Tontroller = "Loginctrl"> <pan> {{user.name}} </span> <input ng-Model = "user.name"> </input> Agora, como o usuário é um objeto, a cadeia de protótipos funcionará, e o modelo NAVCTRL e $scope e loginCtrl serão atualizados.
Parece um exemplo bastante artificial, mas esse problema pode surgir facilmente quando você usa certas instruções para criar um $scope uma criança, como ngRepeat .
9. Teste manual
Como o TDD pode não ser a maneira como todos os desenvolvedores preferem, eles fazem testes manuais quando verificam se o código funciona ou afeta outra coisa.
Não faz sentido não testar o aplicativo AngularJS. O AngularJS foi projetado para torná -lo testável do zero, e os módulos de injeção de dependência e NGMOCK são a prova disso. A equipe principal do AngularJS desenvolveu inúmeras ferramentas que podem levar testes para o próximo nível.
9.1 Transferidor
O teste de unidade é a base do trabalho de teste, mas considerando a complexidade cada vez mais do aplicativo, o teste de integração está mais próximo da situação real. Felizmente, a equipe principal do AngularJS forneceu as ferramentas necessárias.
Estabelecemos transferidor, um testador de ponta a ponta para simular as interações do usuário, o que pode ajudá-lo a verificar a saúde do seu programa AngularJS.
O transferidor usa a estrutura de teste de jasmim para definir testes. O transferidor possui uma API muito robusta para diferentes comportamentos de interação de página.
Temos outras ferramentas de teste de ponta a ponta, mas a vantagem do transferidor é que ele entende como trabalhar com o Código AngularJS, especialmente no ciclo $ Digest.
9.2 Karma
Depois de concluir a redação dos testes de integração com transferidor, a próxima etapa é executar os testes. Esperar que os testes sejam executados, especialmente os testes de integração, é uma tristeza fraca para todos os desenvolvedores. A equipe central dos AngularJS também se sentiu extremamente angustiada, então eles desenvolveram o karma.
O Karma é um testador que ajuda a desativar os loops de feedback. O karma pode fazer isso porque executa testes quando o arquivo especificado é alterado. O Karma também executará testes em vários navegadores, e diferentes dispositivos também podem apontar para o servidor Karma, que pode cobrir melhor os cenários de aplicativos do mundo real.
10. Use jQuery
O JQuery é uma biblioteca legal, com desenvolvimento padronizado de plataforma cruzada, e quase se tornou uma necessidade para o desenvolvimento da Web moderno. No entanto, apesar de tantas características excelentes do jQuery, sua filosofia não é consistente com os angulares.
AngularJS é uma estrutura para a criação de aplicativos, enquanto o JQuery é uma biblioteca que simplifica "operações de documentos HTML, processamento de eventos, animações e Ajax". Esta é a diferença mais básica entre os dois. O AngularJS está comprometido com a arquitetura do programa e não tem nada a ver com páginas HTML.
Para entender melhor como construir um programa AngularJS, pare de usar o jQuery. O JQuery permite que os desenvolvedores pensem em problemas com os padrões HTML existentes, mas como a documentação diz, "o AngularJS permite expandir o termo HTML em seu aplicativo".
As operações DOM devem ser feitas apenas em instruções, mas isso não significa que elas só podem ser encapsuladas com o jQuery. Antes de usar o jQuery, você deve sempre pensar se essa função já é fornecida pelo AngularJS. É realmente poderoso criar ferramentas poderosas quando as instruções dependem uma da outra.
Mas um ótimo jQuery é uma necessidade quando o dia pode chegar, mas apresentá -lo em primeiro lugar é um erro comum.
Resumir
AngularJS é uma excelente estrutura que está sempre melhorando com a ajuda da comunidade. Embora o AngularJS ainda seja um conceito em evolução, espero que as pessoas possam seguir as convenções mencionadas acima para evitar os problemas encontrados no desenvolvimento de aplicações do AngularJS. Espero que o conteúdo deste artigo seja útil para todos. Se você tiver alguma dúvida, pode deixar uma mensagem para se comunicar. Obrigado pelo seu apoio ao wulin.com.