Ao escrever uma diretiva no AngularJS, como decido se não preciso de um novo escopo, um novo escopo filho ou um novo escopo isolado?

Eu estou procurando algumas diretrizes que podem ser usadas para ajudar a determinar qual tipo de escopo usar ao escrever uma nova diretiva. Idealmente, eu gostaria de algo semelhante a um streamgrama que me faça passar por várias perguntas e encontre a resposta correta – nenhum novo novo escopo, novo escopo filho ou novo escopo isolado – mas isso provavelmente está exigindo demais. Aqui está o meu atual conjunto de diretrizes:

  • Não use um escopo isolado se o elemento que usar a diretiva usar ng-model
    Consulte Posso usar o ng-model com escopo isolado? e
    Por que os formatadores não funcionam com escopo isolado?
  • Se a diretiva não modificar nenhuma propriedade de escopo / modelo, não crie um novo escopo
  • Os escopos isolados parecem funcionar bem se a diretiva estiver encapsulando um conjunto de elementos DOM ( a documentação diz “uma estrutura DOM complexa”) e a diretiva será usada como um elemento, ou sem outras diretivas no mesmo elemento.

Estou ciente de que o uso de uma diretiva com um escopo isolado em um elemento força todas as outras diretivas no mesmo elemento a usar o mesmo escopo (um) isolado, portanto isso não limita severamente quando um escopo isolado pode ser usado?

Espero que alguns membros da equipe da Angular-UI (ou outros que escreveram muitas diretivas) possam compartilhar suas experiências.

Por favor, não adicione uma resposta que simplesmente diz “use um escopo isolado para componentes reutilizáveis”.

Que ótima pergunta! Eu adoraria ouvir o que os outros têm a dizer, mas aqui estão as diretrizes que uso.

A premissa de alta altitude: o escopo é usado como a “cola” que usamos para se comunicar entre o controlador pai, a diretiva e o modelo de diretiva.

Escopo pai: scope: false , portanto, nenhum novo escopo em tudo

Eu não uso isso com muita frequência, mas, como disse @MarkRajcok, se a diretiva não acessa nenhuma variável de escopo (e obviamente não define nenhuma!), Então isso é ótimo para mim. Isso também é útil para diretivas filho que são usadas apenas no contexto da diretiva pai (embora sempre haja exceções) e que não tenham um modelo. Basicamente, qualquer coisa com um modelo não pertence ao compartilhamento de um escopo, porque você está expondo inerentemente esse escopo para access e manipulação (mas tenho certeza de que há exceções a essa regra).

Como exemplo, criei recentemente uma diretiva que desenha um gráfico de vetor (estático) usando uma biblioteca SVG que estou no processo de gravação. Ele $observe dois atributos ( width e height ) e os usa em seus cálculos, mas não define nem lê variables ​​de escopo e não tem modelo. Esse é um bom caso de uso para não criar outro escopo; nós não precisamos de um, então por que se preocupar?

Mas em outra diretiva SVG, no entanto, eu precisei de um dataset para usar e, além disso, tive que armazenar um pouquinho de estado. Nesse caso, usar o escopo pai seria irresponsável (novamente, em geral). Então, ao invés disso …

Escopo infantil: scope: true

Diretivas com um escopo filho são sensíveis ao contexto e destinam-se a interagir com o escopo atual.

Obviamente, uma vantagem importante disso sobre um escopo isolado é que o usuário está livre para usar a interpolação em qualquer atributo desejado; por exemplo, usar class="item-type-{{item.type}}" em uma diretiva com um escopo isolate não funcionará por padrão, mas funciona bem em um com escopo filho, porque o que for interpolado ainda pode ser encontrado por padrão o escopo pai. Além disso, a própria diretiva pode avaliar com segurança atributos e expressões no contexto de seu próprio escopo, sem se preocupar com poluição ou danos aos pais.

Por exemplo, uma dica de ferramenta é algo que acaba de ser adicionado; um escopo isolado não funcionaria (por padrão, veja abaixo) porque é esperado que usemos outras diretivas ou atributos interpolados aqui. A dica de ferramenta é apenas um aprimoramento. Mas a dica de ferramenta também precisa definir algumas coisas no escopo para usar com uma sub-diretiva e / ou modelo e, obviamente, para gerenciar seu próprio estado, portanto, seria muito ruim usar o escopo pai. Nós estamos poluindo ou danificando, e também não é bom.

Eu me vejo usando escopos filho com mais frequência do que escopos isolados ou parentais.

Isolar escopo: scope: {}

Isso é para componentes reutilizáveis. 🙂

Mas, falando sério, penso em “componentes reutilizáveis” como “componentes independentes”. A intenção é que eles sejam usados ​​para uma finalidade específica, portanto, combiná-los com outras diretivas ou adicionar outros atributos interpolados ao nó DOM inerentemente não faz sentido.

Para ser mais específico, qualquer coisa necessária para essa funcionalidade autônoma é fornecida através de atributos especificados avaliados no contexto do escopo pai; elas são strings unidirecionais (‘@’), expressões unidirecionais (‘&’) ou vinculações variables ​​bidirecionais (‘=’).

Em componentes independentes, não faz sentido precisar aplicar outras diretivas ou atributos porque existe por si só. Seu estilo é governado por seu próprio modelo (se necessário) e pode ter o conteúdo apropriado transcluído (se necessário). É autônomo, então colocamos em um escopo isolado também para dizer: “Não mexa com isso. Estou dando a você uma API definida através desses poucos atributos”.

Uma boa prática recomendada é excluir o máximo possível de material baseado em modelo do link da diretiva e das funções do controlador. Isso fornece outro ponto de configuração “semelhante à API”: o usuário da diretiva pode simplesmente replace o modelo! A funcionalidade permaneceu a mesma, e sua API interna nunca foi tocada, mas podemos mexer no estilo e na implementação DOM o quanto for necessário. O ui / bootstrap é um ótimo exemplo de como fazer isso bem, porque Peter & Pawel são incríveis.

Escopos isolados também são ótimos para uso com transclusão. Tome abas; elas não são apenas a funcionalidade completa, mas o que estiver dentro dela pode ser avaliado livremente a partir do escopo pai, deixando as guias (e painéis) para fazer o que quiserem. As guias claramente têm seu próprio estado , que pertence ao escopo (para interagir com o modelo), mas esse estado não tem nada a ver com o contexto em que foi usado – é totalmente interno ao que torna uma diretiva de guia uma diretiva de guia. Além disso, não faz muito sentido usar qualquer outra diretiva com as guias. Eles são guias – e nós já temos essa funcionalidade!

Envolva-o com mais funcionalidade ou transforme mais funcionalidades, mas a diretiva é o que já é.

Tudo o que disse, devo observar que existem maneiras de contornar algumas das limitações (ou seja, características) de um escopo isolado, como sugerido @ProLoser em sua resposta. Por exemplo, na seção de escopo filho, mencionei a interpolação em atributos não diretivos quebrando ao usar um escopo isolado (por padrão). Mas o usuário poderia, por exemplo, simplesmente usar class="item-type-{{$parent.item.type}}" e funcionaria novamente. Portanto, se houver um motivo convincente para usar um escopo isolado em um escopo filho, mas você estiver preocupado com algumas dessas limitações, saiba que é possível contornar praticamente todas elas, se necessário.

Resumo

Diretivas sem novo escopo são somente leitura; eles são totalmente confiáveis ​​(ou seja, internos ao aplicativo) e não tocam no jack. Diretivas com um escopo filho adicionam funcionalidade, mas elas não são a única funcionalidade. Por fim, escopos isolados são para diretivas que são o objective inteiro; eles são autônomos, então está tudo bem (e mais “correto”) deixá-los desonestos.

Eu queria ter meus pensamentos iniciais, mas como penso em mais coisas, atualizarei isso. Mas caramba – isso é muito tempo para uma resposta SO …


PS: Totalmente tangencial, mas já que estamos falando de escopos, eu prefiro dizer “prototípico”, enquanto outros preferem “prototypal”, que parece ser mais preciso, mas não sai da língua de maneira alguma. 🙂

Minha política pessoal e experiência:

Isolado: uma sandbox privada

Eu quero criar um monte de methods de escopo e variables ​​que são usadas apenas pela minha diretiva e nunca são vistas ou acessadas diretamente pelo usuário. Quero colocar na lista de permissions quais dados de escopo estão disponíveis para mim. Posso usar a transclusão para permitir que o usuário retorne ao escopo pai (não afetado) . Eu não quero minhas variables ​​e methods acessíveis em crianças transcluídas.

Criança: uma subseção de conteúdo

Eu quero criar methods de escopo e variables ​​que podem ser acessados ​​pelo usuário, mas não são relevantes para os escopos circundantes (irmãos e pais) fora do contexto da minha diretiva. Eu também gostaria de deixar que TODOS os dados do escopo pai caíssem de forma transparente.

Nenhum: diretivas simples, somente leitura

Eu realmente não preciso mexer com methods ou variables ​​de escopo. Eu provavelmente estou fazendo algo que não tem a ver com escopos (como exibir plugins jQuery simples, validação, etc).

Notas

  • Você não deve permitir que ngModel ou outras coisas afetem diretamente sua decisão. Você pode contornar o comportamento estranho fazendo coisas como ng-model=$parent.myVal (child) ou ngModel: '=' (isolate).
  • Isolate + transclude irá restaurar todo o comportamento normal das diretivas irmãs e retornará ao escopo pai, então também não deixe que isso afete seu julgamento.
  • Não mexa no escopo de nenhum porque é como colocar dados no escopo da metade inferior do DOM, mas não na metade superior, o que faz sentido.
  • Preste atenção às prioridades da diretriz (não tenha exemplos concretos de como isso pode afetar as coisas)
  • Injete serviços ou use controladores para se comunicar entre diretivas com qualquer tipo de escopo. Você também pode require: '^ngModel' para procurar nos elementos pais.

Depois de escrever muitas diretivas, decidi usar um escopo menos isolated . Mesmo que seja legal e você encapsule os dados e tenha certeza de não vazar dados para o escopo pai, isso limita severamente a quantidade de diretivas que você pode usar em conjunto. Assim,

Se a diretiva que você vai escrever vai se comportar inteiramente por conta própria e você não vai compartilhá-la com outras diretivas, vá para um escopo isolado . (como um componente você pode simplesmente conectá-lo, sem muita customização para o desenvolvedor final) (fica muito mais complicado quando você tenta escrever sub-elementos que possuem diretivas dentro)

Se a diretiva que você vai escrever vai apenas fazer manipulações dom que não precisam de estado interno de escopo, ou alterações explícitas de escopo (principalmente coisas muito simples); não há novos escopos . (como ngShow , ngMouseHover , ngClick , ngRepeat )

Se a diretiva que você vai escrever precisar alterar alguns elementos no escopo pai, mas também precisar manipular algum estado interno, vá para o novo escopo filho . (como ngController )

Certifique-se de verificar o código-fonte para diretivas: https://github.com/angular/angular.js/tree/master/src/ng/directive
Isso ajuda muito em como pensar sobre eles

Apenas pensei em adicionar meu entendimento atual e como ele se relaciona com outros conceitos do JS.

Padrão (por exemplo, não declarado ou escopo: falso)

Isso é filosoficamente equivalente ao uso de variables ​​globais. Sua diretiva pode acessar tudo no controlador pai, mas também está afetando-os e sendo afetados ao mesmo tempo.

escopo:{}

Isto é como um módulo, qualquer coisa que queira usar precisa ser passada explicitamente. Se EVERY diretiva que você usar for um escopo isolado, pode ser o equivalente a fazer com que CADA arquivo JS você escreva seu próprio módulo com muita sobrecarga ao injetar todas as dependencies.

escopo: criança

Este é o meio termo entre variables ​​globais e passagem explícita. É semelhante à cadeia de protótipos do javascript e apenas estende uma cópia do escopo pai. Se você criar um escopo isolado e passar em todos os atributos e funções do escopo pai, ele será funcionalmente equivalente a isso.


A chave é que qualquer diretiva pode ser escrita de qualquer maneira. As diferentes declarações de escopo estão lá apenas para ajudá-lo a se organizar. Você pode transformar tudo em um módulo ou simplesmente usar todas as variables ​​globais e ser muito cuidadoso. Para facilitar a manutenção, é preferível modularizar sua lógica em partes logicamente coerentes. Há um equilíbrio entre um campo aberto e uma cadeia fechada. A razão pela qual isso é complicado eu acredito é que quando as pessoas aprendem sobre isso elas pensam que estão aprendendo sobre como as diretivas funcionam, mas na verdade elas estão aprendendo sobre organização de código / lógica.

Outra coisa que me ajudou a descobrir como funcionam as diretivas é aprender sobre o ngInclude. ngInclude ajuda você a include parciais em html. Quando comecei a usar diretivas, descobri que você poderia usar sua opção de modelo para reduzir seu código, mas eu não estava realmente anexando nenhuma lógica.

É claro que entre as diretrizes angulares e o trabalho da equipe angular-ui eu ainda não tive que criar minha própria diretiva que faça algo substancial, então minha opinião sobre isso pode estar completamente errada.

Eu concordo com o Umur. Em teoria, os escopos isolados parecem maravilhosos e “portáteis”, mas ao criar meu aplicativo para envolver funcionalidades não-triviais, me deparei com a necessidade de incorporar várias diretivas (algumas aninhadas dentro de outras ou adicionando atributos a elas) para escrever HTML próprio, que é o propósito de uma linguagem específica de domínio.

No final, é muito estranho ter que passar cada valor global ou compartilhado para baixo da cadeia com vários atributos em cada chamada DOM de uma diretiva (como é exigido com o escopo isolate). Parece idiota escrever repetidamente tudo isso no DOM e parece ineficiente, mesmo que sejam objects compartilhados. Também complica desnecessariamente as declarações diretivas. A solução alternativa de usar $ parent para “alcançar” e pegar valores da diretiva HTML parece Very Bad Form.

Eu também acabei mudando meu aplicativo para ter principalmente diretivas de escopo filho com poucos isolados – apenas aqueles que não precisam acessar QUALQUER COISA do pai, além do que podem ser passados ​​através de atributos simples e não repetitivos.

Tendo sonhado o sonho das linguagens específicas por domínio durante décadas antes de existir tal coisa, estou entusiasmado com o fato de o AngularJS fornecer essa opção e eu sei que, quanto mais desenvolvedores trabalharem nessa área, mais alguns aplicativos muito interessantes serão exibidos. Também é fácil para seus arquitetos escrever, expandir e depurar.

– D