"Pensando em AngularJS" se eu tiver um background jQuery?

Suponha que I'm esteja familiarizado com o desenvolvimento de aplicações client-side em jQuery, mas agora I'gostaria de começar a usar AngularJS. Você pode descrever a mudança de paradigma que é necessária? Aqui estão algumas perguntas que podem ajudá-lo a enquadrar uma resposta:

  • Como faço para arquitetar e projetar aplicações web do lado do cliente de maneira diferente? Qual é a maior diferença?
  • O que devo parar de fazer/usar; O que devo começar a fazer/usar em vez disso?
  • Existem algumas considerações/restrições do lado do servidor?

I'não estou procurando por uma comparação detalhada entre jQuery e AngularJS.

Solução

1. Não desenhe a sua página, e depois mude-a com DOM manipulações

Em jQuery, você desenha uma página, e depois torna-a dinâmica. Isto porque jQuery foi desenhado para aumentar e cresceu incrivelmente a partir dessa simples premissa. Mas em AngularJS, você deve começar do zero com a sua arquitetura em mente. Em vez de começar por pensar "Eu tenho esta peça do DOM e quero que ela faça X", você tem que começar com o que você quer realizar, então vá projetando sua aplicação, e então finalmente vá projetando sua visão.

2. Não aumente jQuery com AngularJS

Da mesma forma, não comece com a ideia de que jQuery faz X, Y, e Z, por isso vou apenas adicionar AngularJS em cima disso para modelos e controladores. Isto é realmente tentador quando você está apenas começando, e é por isso que eu sempre recomendo que novos desenvolvedores AngularJS não usem jQuery de jeito nenhum, pelo menos até que eles se acostumem a fazer as coisas do "Angular Way". Já vi muitos desenvolvedores aqui e na lista de discussão criar estas elaboradas soluções com plugins jQuery de 150 ou 200 linhas de código que depois colam no AngularJS com uma coleção de callbacks e $aplicações que são confusas e complicadas; mas acabam por pô-lo a funcionar! O problema é que em mais casos o plugin jQuery poderia ser reescrito em AngularJS em uma fração do código, onde de repente tudo se torna compreensível e direto. O resultado final é o seguinte: ao resolver, primeiro "pense em AngularJS"; se não conseguir pensar numa solução, pergunte à comunidade; se depois de tudo isso não houver uma solução fácil, então sinta-se à vontade para chegar ao jQuery. Mas não deixe jQuery se tornar uma muleta ou você nunca dominará AngularJS.

3. Pense sempre em termos de arquitectura.

Primeiro saiba que aplicações de uma página são aplicações. São páginas web não. Então precisamos pensar como um desenvolvedor do lado do servidor em adição a pensar como um desenvolvedor do lado do cliente. Temos de pensar em como dividir a nossa aplicação em componentes individuais, extensíveis e testáveis. Então como* você faz isso? Como você "pensa em AngularJS"? Aqui estão alguns princípios gerais, em contraste com jQuery.

A vista é o "registo oficial"

Em jQuery, nós programamos a mudança de visão. Poderíamos ter um menu dropdown definido como um ul como tal:

<ul class="main-menu">
    <li class="active">
        <a href="#/home">Home</a>
    </li>
    <li>
        <a href="#/menu1">Menu 1</a>
        <ul>
            <li><a href="#/sm1">Submenu 1</a></li>
            <li><a href="#/sm2">Submenu 2</a></li>
            <li><a href="#/sm3">Submenu 3</a></li>
        </ul>
    </li>
    <li>
        <a href="#/home">Menu 2</a>
    </li>
</ul>

Em jQuery, em nossa lógica de aplicação, nós o ativaríamos com algo parecido:

$('.main-menu').dropdownMenu();

Quando olhamos apenas para a vista, não é imediatamente óbvio que haja alguma funcionalidade aqui. Para aplicações pequenas, tudo bem. Mas, para aplicações não triviais, as coisas rapidamente se tornam confusas e difíceis de manter. No AngularJS, no entanto, a view é o registro oficial da funcionalidade baseada na view. Nossa declaração 'ul' seria parecida com esta:

<ul class="main-menu" dropdown-menu>
    ...
</ul>

Estes dois fazem a mesma coisa, mas na versão AngularJS qualquer pessoa que olhe para o template sabe o que é suposto acontecer. Sempre que um novo membro da equipa de desenvolvimento entra a bordo, ela pode olhar para isto e depois saber que existe uma directiva chamada dropdownMenu a operar nele; ela não precisa de intuir a resposta certa ou peneirar através de qualquer código. A vista disse-nos o que era suposto acontecer. Muito mais limpo. Desenvolvedores novos no AngularJS frequentemente fazem uma pergunta como: como eu encontro todos os links de um tipo específico e adiciono uma diretiva a eles. O desenvolvedor fica sempre atônito quando respondemos: você não. Mas a razão pela qual você não faz isso é que isso é como meia-jQuery, meia-AngularJS, e nada bom. O problema aqui é que o desenvolvedor está tentando "fazer jQuery" no contexto do AngularJS. Isso nunca vai funcionar bem. A visão é o registro oficial. Fora de uma diretiva (mais sobre isso abaixo), você nunca, nunca muda o DOM. E as directivas são aplicadas no ponto de vista, por isso a intenção é clara. Lembre-se: não desenhe, e depois marque. Você deve arquitetar, e depois desenhar.

Ligação de dados

Esta é de longe uma das características mais impressionantes do AngularJS e corta muito da necessidade de fazer os tipos de manipulações DOM que mencionei na secção anterior. O AngularJS irá actualizar automaticamente a sua visão para que não tenha de o fazer! Em jQuery, respondemos aos eventos e depois actualizamos o conteúdo. Algo como:

$.ajax({
  url: '/myEndpoint.json',
  success: function ( data, status ) {
    $('ul#log').append('<li>Data Received!</li>');
  }
});

Para uma vista que se parece com esta:

<ul class="messages" id="log">
</ul>

Além de misturar preocupações, também temos os mesmos problemas de intenção significante que mencionei anteriormente. Mas mais importante, tivemos de fazer referência e actualizar manualmente um nó DOM. E se quisermos apagar uma entrada de log, temos que codificar contra o DOM para isso também. Como é que testamos a lógica para além do DOM? E se quisermos alterar a apresentação? Isto é um pouco confuso e um pouco frágil. Mas no AngularJS, nós podemos fazer isto:

$http( '/myEndpoint.json' ).then( function ( response ) {
    $scope.log.push( { msg: 'Data Received!' } );
});

E a nossa vista pode parecer-se com esta:

<ul class="messages">
    <li ng-repeat="entry in log">{{ entry.msg }}</li>
</ul>

Mas já agora, a nossa visão pode ficar assim:

<div class="messages">
    <div class="alert" ng-repeat="entry in log">
        {{ entry.msg }}
    </div>
</div>

E agora em vez de usar uma lista não ordenada, estamos a usar caixas de alerta Bootstrap. E nunca tivemos de mudar o código do controlador! Mas mais importante, não importa onde ou como o log é atualizado, a visão também mudará. Automaticamente. Fixe! Embora eu não o tenha mostrado aqui, a ligação de dados é de duas vias. Portanto, essas mensagens de log também podem ser editáveis na visualização só por fazer isto: <input ng-model="entry.msg" />. E houve muito regozijo.

Distinta camada de modelo

Em jQuery, o DOM é mais ou menos como o modelo. Mas em AngularJS, temos uma camada de modelo separada que podemos gerir da forma que quisermos, de forma completamente independente da vista. Isto ajuda para a ligação de dados acima, mantém separação de preocupações, e introduz uma testabilidade muito maior. Outras respostas mencionaram este ponto, por isso vou deixar as coisas assim.

Separação de preocupações

E tudo o que foi dito acima está ligado a este tema abrangente: mantenha as suas preocupações separadas. Sua visão atua como o registro oficial do que deve acontecer (na maioria das vezes); seu modelo representa seus dados; você tem uma camada de serviço para realizar tarefas reutilizáveis; você faz manipulação de DOM e aumenta sua visão com diretrizes; e você cola tudo junto com os controladores. Isto também foi mencionado em outras respostas, e a única coisa que eu adicionaria diz respeito à testabilidade, que discuto em outra seção abaixo.

Injeção de dependência

Para nos ajudar na separação das preocupações é injeção de dependência (DI). Se você vem de uma linguagem do lado do servidor (de Java a PHP) você provavelmente já está familiarizado com este conceito, mas se você é um cara do lado do cliente vindo de jQuery, este conceito pode parecer qualquer coisa de bobo a supérfluo para hipster. Mas não é. :-) De uma perspectiva ampla, DI significa que você pode declarar componentes muito livremente e depois a partir de qualquer outro componente, basta pedir um exemplo dele e ele será concedido. Você não tem que saber sobre ordem de carregamento, ou localização de arquivos, ou qualquer coisa do gênero. A energia pode não ser imediatamente visível, mas eu vou fornecer apenas um exemplo (comum): teste. Digamos que em nossa aplicação, nós precisamos de um serviço que implemente o armazenamento do lado do servidor através de uma API REST e, dependendo do estado da aplicação, armazenamento local também. Ao executar testes em nossos controladores, não queremos ter que nos comunicar com o servidor - estamos testando o controlador, afinal de contas. Podemos apenas adicionar um serviço de simulação com o mesmo nome do nosso componente original, e o injector irá garantir que o nosso controlador recebe o falso automaticamente - o nosso controlador não sabe e não precisa de saber a diferença. Por falar em testes...

4. Desenvolvimento orientado por testes - de qualquer forma

Isto é realmente parte da secção 3 sobre arquitectura, mas é tão importante que estou a colocá-la como a sua própria secção de nível superior. De todos os muitos plugins jQuery que você já viu, usou ou escreveu, quantos deles tinham uma suíte de teste de acompanhamento? Não muitos porque o jQuery não é muito favorável a isso. Mas o AngularJS é. Em jQuery, a única forma de testar é muitas vezes criar o componente independentemente com uma página de amostra/demo contra a qual os nossos testes podem realizar a manipulação DOM. Então, temos que desenvolver um componente separadamente e então integrá-lo em nossa aplicação. Que inconveniente! Tanto tempo, quando desenvolvemos com jQuery, optamos por um desenvolvimento iterativo em vez de um desenvolvimento orientado por testes. E quem poderia nos culpar? Mas como temos separação de preocupações, podemos fazer o desenvolvimento iterativo de testes em AngularJS! Por exemplo, digamos que queremos uma directiva super-simples para indicar no nosso menu qual é a nossa rota actual. Podemos declarar o que queremos na visão da nossa aplicação:

<a href="/hello" when-active>Hello</a>

Muito bem, agora podemos escrever um teste para a inexistente directiva "quando activo":

it( 'should add "active" when the route changes', inject(function() {
    var elm = $compile( '<a href="/hello" when-active>Hello</a>' )( $scope );

    $location.path('/not-matching');
    expect( elm.hasClass('active') ).toBeFalsey();

    $location.path( '/hello' );
    expect( elm.hasClass('active') ).toBeTruthy();
}));

E quando fizermos o nosso teste, podemos confirmar que falha. Só agora é que devemos criar a nossa directiva:

.directive( 'whenActive', function ( $location ) {
    return {
        scope: true,
        link: function ( scope, element, attrs ) {
            scope.$on( '$routeChangeSuccess', function () {
                if ( $location.path() == element.attr( 'href' ) ) {
                    element.addClass( 'active' );
                }
                else {
                    element.removeClass( 'active' );
                }
            });
        }
    };
});

O nosso teste agora passa e o nosso menu funciona como solicitado. O nosso desenvolvimento é ambos iterativo e orientado para o teste. Muito fixe.

5. Conceptualmente, as directivas são não empacotadas jQuery

Você ouvirá muitas vezes "só faça manipulação DOM em uma diretiva". Isto é uma necessidade. Trate-o com a devida deferência! Mas vamos mergulhar um pouco mais fundo... Algumas directivas apenas decoram o que já está no ponto de vista (pense "ingClass") e por isso às vezes fazem manipulação de DOM imediatamente e depois basicamente são feitas. Mas se uma diretiva é como um "widget" e tem um modelo, ela deve também respeitar a separação das preocupações. Ou seja, o template too deve permanecer em grande parte independente da sua implementação nas funções de link e controlador. AngularJS vem com um conjunto completo de ferramentas para tornar isso muito fácil; com a 'ngClass' podemos atualizar dinamicamente a classe; a 'ngModel' permite a ligação de dados bidirecionais; a 'ngShow' e a 'ngHide' mostram ou escondem programmaticamente um elemento; e muitas mais - incluindo as que nós mesmos escrevemos. Em outras palavras, podemos fazer todos os tipos de coisas incríveis sem manipulação de DOM. Quanto menos manipulação de DOM, mais fácil é testar as diretivas, mais fácil é estilizar, mais fácil é mudar no futuro, e mais reutilizáveis e distribuíveis eles são. Eu vejo muitos desenvolvedores novos no AngularJS usando diretivas como o lugar para lançar um monte de jQuery. Em outras palavras, eles pensam "já que não posso fazer manipulação de DOM no controlador, vou pegar esse código e colocá-lo em uma diretiva". Embora isso certamente seja muito melhor, é muitas vezes stillo errado. Pense no logger que programámos na secção 3. Mesmo se colocarmos isso numa directiva, nós still queremos fazê-lo da "Via Angular". Não é preciso manipular o DOM! Há muitas vezes em que a manipulação DOM é necessária, mas é um lote mais raro do que você pensa! Antes de fazer manipulação DOM em qualquer lugar da sua aplicação, pergunte a si mesmo se você realmente precisa. Pode haver uma maneira melhor. Aqui está um exemplo rápido que mostra o padrão que eu vejo com mais frequência. Queremos um botão comutável. (Nota: este exemplo é um pouco elaborado e um verbo skosh para representar casos mais complicados que são resolvidos exatamente da mesma maneira).

.directive( 'myDirective', function () {
    return {
        template: '<a class="btn">Toggle me!</a>',
        link: function ( scope, element, attrs ) {
            var on = false;

            $(element).click( function () {
                on = !on;
                $(element).toggleClass('active', on);
            });
        }
    };
});

Há algumas coisas erradas com isto:

  1. Primeiro, jQuery nunca foi necessário. Não há nada que tenhamos feito aqui que precisasse de jQuery de todo!
  2. Segundo, mesmo que já tenhamos jQuery na nossa página, não há razão para usá-lo aqui; podemos simplesmente usar angular.element e o nosso componente ainda funcionará quando for lançado num projecto que não tenha jQuery.
  3. Terceiro, mesmo assumindo que jQuery foi necessário para que esta diretiva funcione, jqLite (angular.element) irá sempre utilizar jQuery se ele foi carregado! Então não precisamos usar o $ - podemos usar apenas o angular.element.
  4. Quarto, intimamente relacionado ao terceiro, é que os elementos jqLite não precisam ser envolvidos em $ - o elemento que é passado para a função linkseria um elemento jQuery!
  5. E quinto, que já mencionámos nas secções anteriores, porque estamos a misturar coisas de template na nossa lógica? Esta diretiva pode ser reescrita (mesmo para casos muito complicados!) muito mais simplesmente assim:
.directive( 'myDirective', function () {
    return {
        scope: true,
        template: '<a class="btn" ng-class="{active: on}" ng-click="toggle()">Toggle me!</a>',
        link: function ( scope, element, attrs ) {
            scope.on = false;

            scope.toggle = function () {
                scope.on = !scope.on;
            };
        }
    };
});

Novamente, o material do template está no template, então você (ou seus usuários) pode facilmente trocá-lo por um que atenda qualquer estilo necessário, e a lógica nunca precisou ser tocada. Reusabilidade - boom! E ainda há todos aqueles outros benefícios, como testar - é fácil! Não importa o que está no template, a API interna da diretiva nunca é tocada, então a refatoração é fácil. Você pode mudar o template o quanto quiser sem tocar na diretriz. E não importa o que você mude, seus testes ainda passam. w00t! Então, se as directivas não são apenas colecções de funções jQuery-like, quais são elas? As diretivas são na verdade extensões de HTML. Se o HTML não faz algo que você precisa que ele faça, você escreve uma diretiva para fazê-lo por você, e então usa-o como se ele fosse parte do HTML. Dito de outra forma, se o AngularJS não fizer algo fora da caixa, pense como a equipe conseguiria fazer isso para se encaixar bem no ngClick, ngClass, et al.

Resumo

Nem sequer uses jQuery. Nem sequer a inclua. Isso vai atrasar-te. E quando você chegar a um problema que você acha que já sabe como resolver em jQuery, antes de alcançar o $, tente pensar em como fazê-lo dentro dos limites do AngularJS. Se você não sabe, pergunte! 19 em 20 vezes, a melhor maneira de o fazer não precisa de jQuery e tentar resolvê-lo com jQuery resulta em mais trabalho para si.

Comentários (22)

Imperativo → declarativo

Em jQuery, selectores são usados para encontrar elementos DOM e depois ligar/registar manipuladores de eventos a eles. Quando um evento dispara, esse código (imperativo) é executado para atualizar/alterar o DOM.

No AngularJS, você quer pensar em **visões* em vez de elementos DOM. As visualizações são HTML (declarativo) que contêm directivos AngularJS. As diretivas nos dão uma base de dados dinâmica e nos permitem a criação de gerenciadores de eventos por trás dos bastidores. Os seletores são raramente usados, portanto a necessidade de IDs (e alguns tipos de classes) é muito menor. As visualizações são vinculadas a modelos** (via escopos). As visualizações são uma projecção do modelo. Os eventos mudam os modelos (ou seja, dados, propriedades de escopo), e as vistas que projetam esses modelos são atualizadas "automaticamente".

Em AngularJS, pense em modelos, em vez de elementos DOM jQuery-selecionados que seguram seus dados. Pense em visões como projeções desses modelos, ao invés de registrar callbacks para manipular o que o usuário vê.

Separação de preocupações

jQuery emprega JavaScript discreto - o comportamento (JavaScript) é separado da estrutura (HTML).

AngularJS usa controladores e diretrizes (cada um dos quais pode ter seu próprio controlador, e/ou compilar e ligar funções) para remover o comportamento da visualização/estrutura (HTML). Angular também possui serviços e **filtros*** para ajudar a separar/organizar a sua aplicação.

Ver também https://stackoverflow.com/a/14346528/215945

Design da aplicação

Uma abordagem para desenhar uma aplicação AngularJS:

  1. Pense nos seus modelos. Crie serviços ou seus próprios objetos JavaScript para esses modelos.
  2. Pense em como você quer apresentar seus modelos -- suas opiniões. Crie templates HTML para cada view, usando as diretivas necessárias para obter uma base de dados dinâmica.
  3. Anexe um controlador a cada view (usando ng-view e routing, ou ng-controller). Faça com que o controlador localize/estime somente quaisquer dados de modelo que a view precise para fazer seu trabalho. Faça os controladores tão finos quanto possível.

Protótipo de herança

Você pode fazer muito com jQuery sem saber como funciona o JavaScript prototypal inheritance. Ao desenvolver aplicações AngularJS, você evitará algumas armadilhas comuns se você tiver um bom entendimento da herança JavaScript. Leitura recomendada: https://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs

Comentários (7)

Pode descrever a mudança de paradigma que é necessária?

**Imperativo vs Declarativo***

Com jQuery você diz ao DOM o que precisa acontecer, passo a passo. Com AngularJS você descreve que resultados você quer, mas não como fazer isso. Mais sobre isto aqui. Confira também a resposta de Mark Rajcok.

Como faço para arquitetar e projetar aplicações web do lado do cliente de maneira diferente?

AngularJS é uma estrutura completa do lado do cliente que usa o padrão MVC (confira sua representação gráfica). Ele foca muito na separação de preocupações.

Qual é a maior diferença? O que devo parar de fazer/usar; o que devo começar a fazer/usar em vez disso?

**jQuery*** é uma biblioteca

**AngularJS*** é uma bela estrutura do lado do cliente, altamente testável, que combina toneladas de coisas legais como MVC, injeção de dependência, encadernação de dados e muito mais.

Ele se concentra em separação de preocupações e testes (testes unitários e testes de ponta a ponta), o que facilita o desenvolvimento orientado por testes.

A melhor maneira de começar é passar por seu incrível tutorial. Você pode percorrer os passos em algumas horas; no entanto, caso você queira dominar os conceitos por trás dos bastidores, eles incluem uma miríade de referências para leitura posterior.

Há algumas considerações/restrições do lado do servidor?

Você pode usá-lo em aplicações existentes onde você já está usando jQuery puro. No entanto, se você quiser tirar vantagem total dos recursos do AngularJS você pode considerar a codificação do lado do servidor usando uma abordagem RESTful.

Fazendo isso, você poderá aproveitar a fábrica de recursos deles, o que cria uma abstração do seu lado do servidor RESTful API e torna as chamadas do lado do servidor (get, save, delete, etc.) incrivelmente fáceis.

Comentários (4)