====== 9.2 scope ====== En el ejemplo de directiva, ''acTitulo'', que acabamos de ver en el tema anterior el texto del título siempre era "Hola Mundo", vamos a modificar la directiva para que el texto pueda ser definido por el usuario de la directiva. La forma de modificar la directiva el simplemente la siguiente: app.directive("acTitulo",[function() { var directiveDefinitionObject ={ restrict:"E", replace : true, template:"

{{texto}}

" } return directiveDefinitionObject; }]);
* Línea 6: El texto a mostrar ahora se obtiene de la propiedad ''texto'' que hay en el ''scope'' de la directiva. Ahora ya podemos tener distintos textos y para ello solo hay que cambiar la propiedad ''texto'' del ''scope'' de la directiva. Pero ahora aparece el problema ya que AngularJS nos permite muchas formas de crear ese nuevo ''scope'', las distintas formas son: * Tener exactamente el mismo ''scope'' que hay en el controlador * Tener un nuevo ''scope'' pero que hereda del ''scope'' del controlador, tal y como pasaba con la directiva [[unidades:04_masdirectivas:06_ngif]] * Tener un nuevo ''scope'' que NO hereda del controlador. Para establecer cual de las 3 usará nuestra directiva , se usa la propiedad ''scope'' del [[https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object|objeto con la definición de la directiva]]. ===== Mismo scope del controlador ===== Si la propiedad ''scope'' del [[https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object|objeto con la definición de la directiva]] vale el valor de ''false'' , la directiva usará como ''scope'' exactamente el mismo ''scope'' que tiene el controlador. Este es el funcionamiento por defecto en caso de que no pongamos nada en la propiedad ''scope'' del [[https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object|objeto con la definición de la directiva]]. Ahora la directiva quedaría asi: app.directive("acTitulo",[function() { var directiveDefinitionObject ={ restrict:"E", replace : true, template:"

{{texto}}

", scope:false } return directiveDefinitionObject; }]);
* Línea 7: Indicamos que el ''scope'' de la directiva es el mismo objeto que el ''scope'' del controlador Ahora ya podemos modificar el texto de la directiva únicamente poniendo : app.controller("PruebaController", function($scope) { $scope.texto="Adios mundo"; }); No recomiendo usar esta opción ya que la directiva será muy dependiente de lo que hay en el ''scope'' del controlador y el que use la directiva podría no ser ni consciente de dicha relación. Hacer ésto sería algo así a usar variables globales dentro de una función. Siendo la variable global el ''scope'' del controlador. ===== Nuevo scope pero que hereda del scope del controlador ===== Si la propiedad ''scope'' del [[https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object|objeto con la definición de la directiva]] vale el valor de ''true'' , la directiva usará como ''scope'' un nuevo ''scope'' pero que ha heredado del ''scope'' del controlador. Este es el caso como el que explicamos en [[unidades:04_masdirectivas:06_ngif#herencia_de_scope|Herencia de $scope]]. Ahora la directiva quedaría asi: app.directive("acTitulo",[function() { var directiveDefinitionObject ={ restrict:"E", replace : true, template:"

{{texto}}

", scope:true } return directiveDefinitionObject; }]);
* Línea 7: Indicamos que el ''scope'' de la directiva es un nuevo ''scope'' pero que hereda del ''scope'' del controlador Como en el caso anterior ya podemos modificar el texto de la directiva únicamente poniendo : app.controller("PruebaController", function($scope) { $scope.texto="Adios mundo"; }); Tampoco recomiendo usar esta opción ya que la directiva sigue siendo será muy dependiente de lo que hay en el ''scope'' del controlador y el que use la directiva tampoco podría no ser ni consciente de dicha relación. Sigue siendo como usar variables globales dentro de una función. Siendo la variable global el ''scope'' del controlador. ===== Nuevo scope ===== Como ya he dicho, las dos forma anteriores no las recomiendo ya que usamos la directiva ''acTitulo'' de la siguiente forma: Y entonces en la página HTML aparece el texto "Adios Mundo", ¿De donde ha aparecido el texto ese? Parece un poco raro. La forma correcta de usar la directiva debería haber sido la siguiente: Es decir , las directivas no tienen que usar variables globales como pueda ser el ''scope'' del controlador sino que todo lo que necesiten se debe pasar como //argumentos// en forma de atributos en la propia directiva. Para ello debemos crear un nuevo objeto JavaScript con ''{ }'' en la propiedad ''scope'' del [[https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object|objeto con la definición de la directiva]]. Ahora la directiva quedaría asi: app.directive("acTitulo",[function() { var directiveDefinitionObject ={ restrict:"E", replace : true, template:"

{{texto}}

", scope:{ } } return directiveDefinitionObject; }]);
* Línea 7: Indicamos que el ''scope'' de la directiva es un nuevo ''scope'' que no tiene nada que ver con el ''scope'' del controlador. Pero ahora nos falta decirle al controlador que queremos que el contenido del atributo ''texto'' de la directiva sea una propiedad del ''scope'' de la directiva. Para hacerlo hay 2 formas distintas que pasamos a ver. ==== Enlace unidireccional ==== Simplemente tenemos que añadir un atributo al ''scope'' cuya valor sea una arroba "@". De esa forma decimos que que queremos copiar el valor del atributo en el ''scope''. Ahora la directiva quedaría asi: app.directive("acTitulo",[function() { var directiveDefinitionObject ={ restrict:"E", replace : true, template:"

{{texto}}

", scope:{ texto:"@" } } return directiveDefinitionObject; }]);
* Línea 8 : Al poner ''texto:"@"'' lo que le hemos dicho es que el **valor** del atributo ''texto'' se copie en la propiedad ''texto'' del ''scope'' de la directiva. Ahora ya podemos usar la directiva así: Y se generará el siguiente HTML:

Texto definido en el atributo

Pero, ¿que pasa si el texto a mostrar está en una propiedad del ''$scope'' del controlador? Por ejemplo en la propiedad ''mensaje'' del ''$scope'' del controlador. app.controller("PruebaController", function($scope) { $scope.mensaje="Texto definido en el $scope del controlador"; }); Pues no hay más que usar la directiva usando las ya conocidas llaves "%%{ %%}" de AngularJS: Y se generará el siguiente HTML:

Texto definido en el $scope del controlador

==== Enlace bidireccional ==== El enlace unidireccional que acabamos de ver al usar "@" tiene 2 problemas: * Es unidireccional como su nombre indica, si cambiamos el valor de la propiedad ''texto'' del ''scope'' de la directiva no se cambia la propiedad ''mensaje'' del ''$scope'' del controlador * Tener que usar "%%{ }%%" puede resultar un poco //raro// si lo que queremos es siempre enlazar a una propiedad del ''$scope'' del controlador. Pues bien, AngularJS nos permite usar el caracter "=" para enlazar directamente una propiedad del ''scope'' de la directiva con una propiedad del ''$scope'' del controlador. Lo que queremos poder hacer es lo siguiente: Queremos que lo que ponemos en el atributo ''texto'' sea tratado como el nombre de una propiedad del ''$scope'' del controlador y no como el texto realmente. Es como añadir una indirección. Ahora la directiva quedaría asi: app.directive("acTitulo",[function() { var directiveDefinitionObject ={ restrict:"E", replace : true, template:"

{{texto}}

", scope:{ texto:"=" } } return directiveDefinitionObject; }]);
* Línea 8 : Al poner ''texto:"="'' lo que le hemos dicho es que la propiedad ''texto'' del ''scope'' de la directiva sea exactamente la propiedad del ''$scope'' del controlador especificado en el atributo ''texto'' de la directiva. (Parece un trabalenguas). Al hace este cambio ya podemos usar la directiva de la siguiente forma: Y si en el controlador establecemos el valor de la propiedad ''mensaje'': app.controller("PruebaController", function($scope) { $scope.mensaje="Texto definido en el $scope del controlador"; }); Se generará el siguiente HTML:

Texto definido en el $scope del controlador

Pero ahora lo interesante es que si cambiáramos el valor de la propiedad ''texto'' en el ''scope'' de la directiva también se cambiaría en la propiedad ''mensaje'' del ''$scope'' del controlador. Vamos ahora a cambiar la directiva para añadir un botón que cambie el valor del ''scope'' de la directiva. app.directive("acTitulo",[function() { var directiveDefinitionObject ={ restrict:"E", replace : true, template:"

{{texto}}

", scope:{ texto:"=" } } return directiveDefinitionObject; }]);
* Ahora la directiva incluye un botón que al pincharlo hace lo siguiente: ''texto='Texto cambiado desde dentro de la directiva''' pero como se ejecuta dentro de la directiva , lo que cambiar es la propiedad ''texto'' del ''scope'' de la directiva. Si pinchamos en el botón se cambiaría también la propiedad ''mensaje'' del ''$scope'' del controlador. Es decir que ya tenemos el enlace bidireccional: * Si hay un cambio en el ''$scope'' del controlador se modifica el ''scope'' de la directiva * Si hay un cambio en el ''scope'' de la directiva se modifica el ''$scope'' del controlador. ¿Que ocurre si usando ''texto:"="'' no queremos tener el texto en el ''$scope'' del controlador sino ponerlo directamente en la directiva. AngularJS tiene la siguiente sintáxsis: Hay que fijarse en los 2 apostrofes que hemos puesto antes y después del texto . ==== Enlace unidireccional vs bidireccional ==== Ahora que hemos visto los tipos de enlacen unidireccionales y bidireccionales vamos ha hacer una comparativa entre ellos ^ ^ Enlace unidireccional ^ Enlace bidireccional ^ | Carácter usado | ''@'' | ''='' | | Expresión para enlazar a valores literales | ''valor'' | '''valor''' ((Si es un número se pone directamente el número sin apostrofes)) | | Expresión para enlazar a propiedades del ''$scope'' del controlador | ''%%{{nombrePropiedad%%}}'' | ''nombrePropiedad'' | | Si se modifica la propiedad del ''$scope'' del controlador | Si se modifica la propiedad del ''scope'' de la directiva ((Solo se aplica si hemos puesto el nombre de una propiedad del ''$scope'' entre llaves )) ((No se cambiaría si desde la directiva se cambia el valor de la propiedad)) | Si se modifica la propiedad del ''scope'' de la directiva | | Si se modifica la propiedad del ''scope'' de la directiva | **NO** se modifica la propiedad del ''$scope'' del controlador | **Si** se modifica la propiedad del ''$scope'' del controlador | Ahora la pregunta que nos queda es: ¿cuando usar uno u otro? La respuesta es sencilla, solo depende de si vamos a necesitar cambiar una propiedad del ''$scope'' del controlador desde la directiva. * Si queremos cambiar una propiedad del ''$scope'' del controlador desde la directiva deberemos usar el enlace bidireccional * Si **NO** queremos cambiar una propiedad del ''$scope'' del controlador desde la directiva deberemos usar el enlace unidireccional y asi evitamos la posibilidad de un error y que pudiéramos cambiar el ''$scope'' del controlador de la directiva sin quererlo. Hay un tipo más de enlace que se usa para enlazar funciones. El carácter usado es "&" y ya lo veremos en la unidad de [[unidades:12_directivasadv:00_start]] ==== Distintos nombres ==== Una última característica que nos permite AngularJS al enlazar con las propiedades del ''scope'' es hacer que tengan distinto nombre. Imaginemos que hemos decidido que el atributo ''texto'' de la direcctiva ahora se llame ''txt''. La directiva ahora se utilizaría de la siguiente manera: Pero hemos decidido que no queremos cambiar la propiedad del ''scope'' de la directiva que queremos que siga siendo ''texto''. Pues solo tenemos que para añadir a la "@" ((O al "=")) el nombre del atributo en caso de que no esa igual que el de la propiedad del ''scope''. app.directive("acTitulo",[function() { var directiveDefinitionObject ={ restrict:"E", replace : true, template:"

{{texto}}

", scope:{ texto:"@txt" } } return directiveDefinitionObject; }]);
* Línea 8: Al ser distinto el nombre del atributo y el nombre de la propiedad del ''scope'' , le ponemos junto a la "@" el nombre del atributo de la directiva. Y ahora ya podemos usar la directiva con el atributo "txt" en vez de "texto". ===== Ejemplo ===== El ejemplo consiste en hacer 2 directivas como la última que hemos usado , la del botón, pero que una de ellas tanga enlace unidireccional y la otra enlace bidireccional. * acTituloUnidireccional * acTituloBidireccional Tendremos 2 propiedades en el ''$scope'' del controlador para en lazar en cada una de las directivas. Las propiedades son: * ''mensajeUnidireccional'' * ''mensajeBidireccional'' Al pulsar sobre los botones "Cambiar valor de scope.texto de la directiva" se cambiarán los valores de las propiedades del ''scope'' de las directivas y se podrá comprobar si también se cambian las propiedades del ''$scope'' del controlador. De esa forma comprobaremos el enlace desde la directiva hacia el controlador. Al pulsar sobre los botones "Cambiar valor del $scope.mensaje del controlador" se cambiarán los valores de las propiedades del ''$scope'' de los controladores y se podrá comprobar si también se cambian las propiedades del ''scope'' de las directivas. De esa forma comprobaremos el enlace desde el controlador hacia la directiva. {{url>http://embed.plnkr.co/ALVZoH}} ===== Referencias ===== * [[https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object|/ API Reference / ng / service / $compile / Directive Definition Object]] * [[https://docs.angularjs.org/guide/directive|/ Developer Guide / Directives]] * [[https://docs.angularjs.org/api/ng/type/angular.Module|/ API Reference / ng / type / angular.Module]] * [[https://github.com/angular/angular.js/wiki/Understanding-Scopes|Understanding Scopes]]