Directiva aislar alcance con alcance ng-repeat en AngularJS

95

Tengo una directiva con un alcance aislado (para poder reutilizar la directiva en otros lugares), y cuando uso esta directiva con un ng-repeat, no funciona.

He leído toda la documentación y las respuestas de Stack Overflow sobre este tema y entiendo los problemas. Creo que he evitado todas las trampas habituales.

Entonces entiendo que mi código falla debido al alcance creado por la ng-repeatdirectiva. Mi propia directiva crea un alcance aislado y realiza un enlace de datos bidireccional a un objeto en el alcance principal. Mi directiva asignará un nuevo valor de objeto a esta variable vinculada y esto funciona perfectamente cuando mi directiva se usa sin ng-repeat(la variable principal se actualiza correctamente). Sin embargo, con ng-repeat, la asignación crea una nueva variable en el ng-repeatalcance y la variable principal no ve el cambio. Todo esto es el esperado según lo que he leído.

También he leído que cuando hay varias directivas en un elemento determinado, solo se crea un ámbito. Y que priorityse puede establecer un en cada directiva para definir el orden en que se aplican las directivas; las directivas se ordenan por prioridad y luego se llaman a sus funciones de compilación (busque la palabra prioridad en http://docs.angularjs.org/guide/directive ).

Así que esperaba poder usar la prioridad para asegurarme de que mi directiva se ejecute primero y termine creando un alcance aislado, y cuando se ng-repeatejecute, reutilice el alcance aislado en lugar de crear un alcance que herede prototípicamente del alcance principal. La ng-repeatdocumentación indica que esa directiva se ejecuta a nivel de prioridad 1000. No está claro si 1es un nivel de prioridad más alto o un nivel de prioridad más bajo. Cuando usé el nivel de prioridad 1en mi directiva, no hizo ninguna diferencia, así que lo intenté 2000. Pero eso empeora las cosas: mis enlaces bidireccionales se vuelven undefinedy mi directiva no muestra nada.

He creado un violín para mostrar mi problema . He comentado la priorityconfiguración en mi directiva. Tengo una lista de objetos de nombre y una directiva llamada name-rowque muestra los campos de nombre y apellido en el objeto de nombre. Cuando se hace clic en un nombre mostrado, quiero que establezca una selectedvariable en el ámbito principal. La matriz de nombres y la selectedvariable se pasan a la name-rowdirectiva mediante enlace de datos bidireccional.

Sé cómo hacer que esto funcione llamando a funciones en el alcance principal. También sé que si selectedestá dentro de otro objeto y me ato al objeto exterior, las cosas funcionarían. Pero no estoy interesado en esas soluciones en este momento.

En cambio, las preguntas que tengo son:

  • ¿Cómo evito ng-repeatcrear un alcance que hereda prototípicamente del alcance principal y, en su lugar, haga que use el alcance aislado de mi directiva?
  • ¿Por qué 2000no funciona el nivel de prioridad en mi directiva?
  • Usando Batarang, ¿es posible saber qué tipo de visor está en uso?
Deepak Nulu
fuente
1
Normalmente, no desea utilizar un ámbito aislado si su directiva se utilizará en el mismo elemento con otras directivas. Dado que está creando sus propias propiedades de alcance y necesita trabajar con ng-repeat, le sugiero que las use scope: truepara su directiva. Consulte también (si aún no lo ha hecho) stackoverflow.com/questions/14914213/… Además, el hecho de que una directiva se use en varios lugares no significa que debamos usar automáticamente un alcance aislado.
Mark Rajcok
He leído muchas de sus respuestas (son más que excelentes, gracias por escribirlas), pero nunca se me ocurrió leer sus preguntas :-). Leí lo que vinculó. Me parece que las directivas de ámbito aislado no se pueden mezclar con otras directivas. Estoy de acuerdo con el sentimiento de que tales directivas son componentes y, por lo tanto, no necesitan mezclarse con otras directivas. La única excepción (hasta ahora) para mí sería ng-repeat. Creo que es valioso poder combinar directivas independientes con ng-repeat. Continuará ...
Deepak Nulu
Continuación de arriba ... Entonces, si solo debería haber una directiva con un alcance para un elemento, entonces ng-repeatno debería tener un alcance. ng-repeattener un alcance tiene sentido para el caso de uso típico, por lo que no estoy sugiriendo que se cambie. En cambio, como comenté en la respuesta de Alex Osborn, creo que crearé una directiva de repetición basada en ng-repeatque no crea su propio alcance. Esto se puede utilizar para repetir directivas que tienen sus propios ámbitos de aislamiento. Continuará ...
Deepak Nulu
El código que repite una directiva ahora necesita saber si usar ng-repeato la directiva de repetición personalizada sin alcance. Creo que está bien que la "persona que llama" sepa esto, pero no está bien que una "persona que llama" (la directiva se repite) sepa si se está repitiendo o no. Continuará ...
Deepak Nulu
Volviéndome un poco loco con los comentarios aquí ... :-) ngRepeat debe crear su propio alcance. ¿Por qué cree que necesita un alcance aislado aquí?
Josh David Miller

Respuestas:

203

Bien, a través de muchos de los comentarios anteriores, he descubierto la confusión. Primero, un par de puntos de aclaración:

  • ngRepeat no afecta su alcance aislado elegido
  • los parámetros pasados ​​a ngRepeat para su uso en los atributos de su directiva lo hacen usan un alcance heredada prototípica-
  • la razón por la que su directiva no funciona no tiene nada que ver con el alcance aislado

Aquí hay un ejemplo del mismo código pero con la directiva eliminada:

<li ng-repeat="name in names"
    ng-class="{ active: $index == selected }"
    ng-click="selected = $index">
    {{$index}}: {{name.first}} {{name.last}}
</li>

Aquí hay un JSFiddle que demuestra que no funcionará. Obtiene exactamente los mismos resultados que en su directiva.

¿Por qué no funciona? Porque los ámbitos en AngularJS usan herencia prototípica. El valor selecteden su alcance principal es primitivo . En JavaScript, esto significa que se sobrescribirá cuando un niño establezca el mismo valor. Hay una regla de oro en los ámbitos de AngularJS: los valores del modelo siempre deben tener un .en ellos. Es decir, nunca deberían ser primitivos. Consulte esta respuesta SO para obtener más información.


Aquí hay una imagen de cómo se ven inicialmente los osciloscopios.

ingrese la descripción de la imagen aquí

Después de hacer clic en el primer elemento, los ámbitos ahora se ven así:

ingrese la descripción de la imagen aquí

Observe que selectedse creó una nueva propiedad en el ámbito ngRepeat. El alcance del controlador 003 no se modificó.

Probablemente pueda adivinar lo que sucede cuando hacemos clic en el segundo elemento:

ingrese la descripción de la imagen aquí


Entonces, su problema en realidad no es causado por ngRepeat en absoluto, es causado por romper una regla de oro en AngularJS. La forma de solucionarlo es simplemente usar una propiedad de objeto:

$scope.state = { selected: undefined };
<li ng-repeat="name in names"
    ng-class="{ active: $index == state.selected }"
    ng-click="state.selected = $index">
    {{$index}}: {{name.first}} {{name.last}}
</li>

Aquí hay un segundo JSFiddle que muestra que esto también funciona.

Así es como se ven inicialmente los ámbitos:

ingrese la descripción de la imagen aquí

Después de hacer clic en el primer elemento:

ingrese la descripción de la imagen aquí

Aquí, el alcance del controlador se ve afectado, como se desea.

Además, para demostrar que esto seguirá funcionando con su directiva con un alcance aislado (porque, de nuevo, esto no tiene nada que ver con su problema), aquí también hay un JSFiddle para eso, la vista debe reflejar el objeto. Notarás que el único cambio necesario fue usar un objeto en lugar de una primitiva .

Alcances inicialmente:

ingrese la descripción de la imagen aquí

Ámbitos después de hacer clic en el primer elemento:

ingrese la descripción de la imagen aquí

Para concluir: una vez más, su problema no es con el alcance aislado ni con cómo funciona ngRepeat. Su problema es que está rompiendo una regla que se sabe que conduce a este mismo problema. Los modelos en AngularJS siempre deben tener una extensión ..

Josh David Miller
fuente
Mis experimentos con directivas con ámbitos aislados y enlace de datos bidireccional me llevan a creer que la .regla de oro solo es necesaria para los ámbitos que tienen herencia prototípica. Con isolate-scopes, las variables que le interesan se definen en la scope: {}definición de la directiva y, por lo tanto, esas variables existirán en isolate-scope desde el principio. Además, no enmascaran las variables principales porque el ámbito aislado no hereda prototípicamente del ámbito principal. es decir, no hay un ámbito "principal" para enmascarar.
Deepak Nulu
1
En caso de tener también dijo: su directiva no está creando un ámbito secundario, pero podría ser fácilmente utilizado en un contexto que requiere un ámbito secundario. ngRepeat es un caso. También lo es la transclusión. Créame, use el ..
Josh David Miller
1
¡Discusión sobre el Grupo de Google AngularJS donde @JoshDavidMiller finalmente pudo aclarar mi confusión!
Deepak Nulu
2
¿De dónde sacan todos estos geniales diagramas? He visto a otro usuario publicar estos también.
Asad Saeeduddin
3
@Asad, recientemente puse la herramienta en GitHub, se llama Peri $ scope .
Mark Rajcok
6

Sin tratar directamente de evitar responder a sus preguntas, en su lugar, eche un vistazo al siguiente violín:

http://jsfiddle.net/dVPLM/

El punto clave es que en lugar de tratar de luchar y cambiar el comportamiento convencional de Angular, podría estructurar su directiva para trabajar ng-repeaten lugar de intentar anularla.

En tu plantilla:

    <name-row 
        in-names-list="names"
        io-selected="selected">
    </name-row>

En su directiva:

    template:
'        <ul>' +      
'            <li ng-repeat="name in inNamesList" ng-class="activeClass($index)" >' +
'                <a ng-click="setSelected($index)">' +
'                    {{$index}} - {{name.first}} {{name.last}}' +
'                </a>' +
'            </li>' +
'        </ul>'

En respuesta a sus preguntas:

  • ng-repeat creará un alcance, realmente no debería intentar cambiar esto.
  • La prioridad en las directivas no es solo el orden de ejecución; consulte: AngularJS: ¿Cómo organiza el compilador HTML el orden de compilación?
  • En Batarang, si verifica la pestaña de rendimiento, puede ver las expresiones vinculadas para cada alcance y verificar si esto coincide con sus expectativas.
Alex Osborn
fuente
Gracias por una respuesta tan rápida. Si lo que estoy intentando va contra la corriente, me entristece un poco (AngularJS es, no obstante, un marco increíble). Una directiva está destinada a ser un componente reutilizable. Cuando esté escrito, el autor no debería tener que preocuparse por si se utilizará con un ng-repeat. Quizás cuando se escribe por primera vez, nunca se usa con ng-repeat. Y en algún momento en el futuro, podría acostumbrarse ng-repeat, y en ese momento, debería funcionar sin una reescritura. Con suerte, una versión futura de AngularJS lo hará posible.
Deepak Nulu
Me gustaría aclarar mi comentario anterior. Creo que es posible escribir una directiva que no se preocupe por si se usa con ng-repeato no. Pero parece que tendría que pasar una función a la directiva para que pueda modificar las variables en el ámbito principal, en lugar de poder mutar un enlace bidireccional en el propio ámbito de la directiva. Las directivas reutilizables y de enlace bidireccional son mis dos cosas favoritas de AngularJS y ng-repeatestá demostrando ser una mosca en el ungüento. Tal vez pueda escribir un ng-repeatequivalente que no cree su propio alcance.
Deepak Nulu