Tabla de Contenidos

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:"<h1>{{texto}}</h1>"
  }
  
  return directiveDefinitionObject;
}]);

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:

Para establecer cual de las 3 usará nuestra directiva , se usa la propiedad scope del objeto con la definición de la directiva.

Mismo scope del controlador

Si la propiedad scope del 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 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:"<h1>{{texto}}</h1>",
    scope:false
  }
  
  return directiveDefinitionObject;
}]);

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 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 Herencia de $scope.

Ahora la directiva quedaría asi:

app.directive("acTitulo",[function() {
  
  var directiveDefinitionObject ={
    restrict:"E",
    replace : true,
    template:"<h1>{{texto}}</h1>",
    scope:true
  }
  
  return directiveDefinitionObject;
}]);

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:

<ac-titulo></ac-titulo>

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:

<ac-titulo texto="Adios Mundo"></ac-titulo>

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 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:"<h1>{{texto}}</h1>",
    scope:{
    
    }
  }
  
  return directiveDefinitionObject;
}]);

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:"<h1>{{texto}}</h1>",
    scope:{
      texto:"@"
    }
  }
  
  return directiveDefinitionObject;
}]);

Ahora ya podemos usar la directiva así:

<ac-titulo texto="Texto definido en el atributo"></ac-titulo>

Y se generará el siguiente HTML:

<h1>Texto definido en el atributo</h1>

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:

<ac-titulo texto="{{mensaje}}"></ac-titulo>

Y se generará el siguiente HTML:

<h1>Texto definido en el $scope del controlador</h1>

Enlace bidireccional

El enlace unidireccional que acabamos de ver al usar ”@” tiene 2 problemas:

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:

<ac-titulo texto="mensaje"></ac-titulo>
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:"<h1>{{texto}}</h1>",
    scope:{
      texto:"="
    }
  }
  
  return directiveDefinitionObject;
}]);

Al hace este cambio ya podemos usar la directiva de la siguiente forma:

<ac-titulo texto="mensaje"></ac-titulo>

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:

<h1>Texto definido en el $scope del controlador</h1>

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:"<div><h1>{{texto}}</h1><button ng-click=\"texto='Texto cambiado desde dentro de la directiva'\">Cambiar valor de scope.texto de la directiva</button></div>",
    scope:{
      texto:"="
    }
  }
  
  return directiveDefinitionObject;
}]);

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:

¿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:
<ac-titulo texto="'Texto directamente en el atributo'"></ac-titulo>
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' 1)
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 2) 3) 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.

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 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:

<ac-titulo txt="{{mensaje}}"></ac-titulo>

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 ”@” 4) 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:"<h1>{{texto}}</h1>",
    scope:{
      texto:"@txt"
    }
  }
   
  return directiveDefinitionObject;
}]);

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.

Tendremos 2 propiedades en el $scope del controlador para en lazar en cada una de las directivas. Las propiedades son:

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.

Referencias

1) Si es un número se pone directamente el número sin apostrofes
2) Solo se aplica si hemos puesto el nombre de una propiedad del $scope entre llaves
3) No se cambiaría si desde la directiva se cambia el valor de la propiedad
4) O al ”=”