Explicaciones más detalladas del omnipresente scope de AngularJS, nuestro ámbito. Introducimos el $parent para acceder al scope padre.
El scope, un término que encontrarás nombrado hasta la saciedad en la literatura relacionada con este framework de Javascript. ¿Qué es exactamente el scope? ¿Qué podemos decir sobre él para completar lo visto hasta ahora en el Manual de AngularJS? Sobre todo ello vamos a hablar en este artículo.
La traducción de scope es "ámbito", sin embargo, el término es tan habitual que solemos usar directamente la palabra en inglés. Pero creo que usando la traducción nos podemos enterar mejor qué es en realidad el scope: el "ámbito" de los datos donde estamos trabajando en las vistas.
En Angular no existe un único ámbito, puedes tener (tendrás) diferentes scopes en diferentes partes de tu código, anidados o en paralelo. Los scope se crean automáticamente para cada uno de tus controladores y además existe un scope raíz para toda tu aplicación. A continuación te enseñaremos a trabajar con ellos, almacenando datos y recuperándolos.
Scope hace de enlace entre las vistas y los controladores
El scope es la magia que permite que los datos que manejas en los controladores pasen a las vistas, y viceversa. Es el enlace que nos traslada esos datos de un lugar a otro, sin que nosotros tengamos que programar nada.
En los controladores hemos aprendido a generar elementos en el scope de diversas maneras. Lo común es acceder al scope si lo inyectas en el controlador mediante el parámetro $scope. Luego podrás añadir datos simplemente como propiedades a ese objeto $scope inyectado:
.controller('otroCtrl', function($scope){
$scope.algo = "probando scope...";
$scope.miDato = "otra cosa..."
});
Eso te generará un dato en tu scope llamado "algo" y otro llamado "miDato".
Desde las vistas puedes acceder a datos de tu scope a través de expresiones del tipo:
{{ algo }}
Ese "algo" es un dato que tienes almacenado en el scope desde el controlador. Como sabes, con esa expresión lo vuelcas en la página. También podrás acceder al scope cuando usas la directiva ng-model y defines un dato en tu modelo.
<input type="text" ng-model="miDato">
Como se decía, el scope nos sirve para trasladar esos datos entre el controlador y la vista. Nosotros para poder acceder a "algo" o a "miDato" no necesitamos enviarlo desde el controlador a la vista ni de la vista al controlador, salvo crear esa propiedad en el $scope. Lo bonito, que vimos en el artículo de Binding en Angular, es que cuando el dato se modifica, ya sea porque se le asigna un nuevo valor desde el controlador o porque se cambia lo que hay escrito en el INPUT de la vista, la otra parte es consciente de ese cambio sin tener que suscribirnos a eventos.
Ámbitos del scope dentro de la vista
Si lees eso en español "ámbito del scope" parece un trabalenguas o quizás una frase infantíl, "ámbito del ámbito". Queremos decir que cada scope tiene su ámbito restringido a una parte del código, aunque la propia palabra "scope" ya nos dice por ella misma que se trata de eso.
Bueno en definitiva, vamos a echar un vistazo a un HTML:
<div ng-controller="miAppController">
<p>Contenido de nuestra vista acotado por un controlador</p>
<p>{{ cualquierCosa }}</p>
</div>
<section>
Desde fuera de esa división no tengo acceso al scope del controlador
</section>
Lo que queremos que se vea ahora es que el scope creado por ese controlador "miAppController" tiene validez dentro de ese código HTML en el lugar donde se ha definido la directiva ngController. Es decir, los elementos que asignes a $scope dentro de tu controlador se podrán ver desde la etiqueta DIV y todas sus hijas, pero no desde etiquetas que se encuentren fuera, como es el caso del SECTION que hay después.
Alias del scope
Existe otra modalidad de enviar datos al scope y es por medio de un alias. Esto te sirve cuando al declarar tus controladores en el HTML (directiva ng-controller) usas "controller..as".
<div ng-controller="pruebaAppCtrl as vm">
En este caso podrás generar datos en el scope de dos maneras, o bien a través del mismo objeto $scope que puedes inyectar, tal como acabamos de explicar, o bien a través de la variable this dentro de la función.
.controller('pruebaAppCtrl', function($rootScope){
var modeloDeLaVista = this;
modeloDeLaVista.otroDato = "Esto está ahora en el scope, para acceder a través de un alias";
});
Gracias al alias en la vista podrás acceder a ese dato con una expresión como esta:
{{ vm.otroDato }}
Este alias del scope nos simplificará la vida cuando estemos trabajando con varios ámbitos. Enseguida lo veremos.
Ámbito raíz con $rootScope
En AngularJS existe un ámbito o scope raíz, sobre el que podemos insertar datos. Ese ámbito es accesible desde cualquier lugar de la aplicación y nos puede servir para definir valores que puedes acceder desde cualquier punto de tu HTML, independientemente del controlador donde te encuentres.
El scope raíz de Angular lo puedes acceder desde tu controlador, por medio de $rootScope, que necesitas inyectar en la función de tu controlador en caso que desees usarlo.
.controller('pruebaAppCtrl', function($scope, $rootScope){
$scope.scopeNormal = "Esto lo coloco en el scope normal de este controlador...";
$rootScope.scopeRaiz = "Esto está en el scope raíz";
});
Cuando guardas algo en el ámbito raíz puedes acceder a ese valor, desde tu vista, a través del nombre de la propiedad que has creado en $rootScope.
<div ng-controller="pruebaAppCtrl">
{{ scopeNormal }}
<br />
{{ scopeRaiz }}
</div>
Como ves, en este ejemplo accedemos de la misma manera a un dato que tienes en el scope de este controlador y a un dato que tiene en el scope raíz. Esto es así porque en AngularJS, si una variable del modelo no se encuentra en el scope actual, el propio sistema busca el dato en el scope padre. Si no, en el padre y así sucesivamente.
Conociendo $parent
El problema es cuando tienes un dato con el mismo nombre en dos scopes distintos.
.controller('pruebaAppCtrl', function($scope, $rootScope){
//sobrescribo una variable del scope
//en realidad son dos datos distintos con dos valores distintos
//uno lo tengo en el scope del controlador y otro en el scope raíz "root"
$scope.repetido = "Algo en el scope normal";
$rootScope.repetido = "Algo en el scope raíz";
});
En ese caso, si en a vista hacemos algo como {{ repetido }} verás que existe una ambigüedad, pues ese valor puede significar dos cosas, dependiento si miramos en un scope u otro. Angular resuelve esa situación devolviendo el dato del scope más específico. Osea, te dará el valor que tiene en $scope.repetido y no podrás acceder a $rootScope.repetido, a no ser que uses $parent.
$parent permite acceder al scope "padre", eso en caso que tengas dos controladores uno dentro de otro. O en el caso que solo tengas un controlador con $parent podrás acceder al scope raíz.
Así pues, para acceder a ambos valores podría usar un HTML como este:
<div ng-controller="pruebaAppCtrl">
{{ repetido }} --- {{ $parent.repetido }}
</div>
El primer "repetido" nos permite acceder al dato que tenemos en el scope actual y por su parte $parent.repetido nos permite acceder al dato que habíamos guardado sobre $rootScope.
Ejemplo con ámbitos corrientes y ámbito root
Todo el código que hemos visto desmembrado en este artículo se puede resumir en el siguiente ejemplo.
HTML:
<div ng-app="pruebaApp" ng-controller="pruebaAppCtrl">
{{ scopeNormal }}
<br />
{{ scopeRaiz }}
<br />
{{ algo }} --- {{ $parent.algo }}
</div>
Javascript:
.controller('pruebaAppCtrl', function($scope, $rootScope){
$scope.scopeNormal = "Esto lo coloco en el scope normal de este controlador...";
$rootScope.scopeRaiz = "Esto está en el scope raíz";
//sobrescribo una variable del scope
$scope.algo = "Algo en el scope normal";
$rootScope.algo = "Algo en el scope raíz";
});
Puedes apreciar más o menos todo lo que venimos comentando sobre los ámbitos, pero lo verás mejor de una manera visual con la extensión Angular Batarang.
Angular Batarang
Existe una herramienta que se integra en Google Chrome que te sirve de inspector de scopes en AngularJS. Se llama "Batarang" y nos permite ver de una manera rápida lo que tenemos en los diferentes scopes de nuestra aplicación.
Para desarrollar con AngularJS es imprescindible. Seguro que la conoces.
Si inspeccionamos el ejercicio anterior con la extensión "AngularJS Batarang" podrás encontrar más o menos lo que ves en esta imagen:
Como ves, las herramientas para desarrolladores de Chrome tienen ahora una nueva pestaña que me permite examinar los distintos scopes de esta aplicación. En la imagen anterior puedes ver dos pantallas, examinando el scope raíz y el scope común generado por un controlador.
Finalizamos por aquí, a la espera de publicar un artículo que nos permita construir controladores anidados, o en paralelo, donde podremos observar también diferentes construcciones de ámbitos.
Miguel Angel Alvarez
Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...