> Manuales > Manual de AngularJS

Realizamos diversas variantes de construcción de aplicaciones en Angular con distintos controladores y repasamos las condiciones del scope en cada una.

En el artículo anterior pudimos aprender bastantes detalles sobre el "famoso" scope de AngularJS. Vimos que se van creando a medida que se usan los controladores dentro de la página y conocimos que existen diversos ámbitos o distintos "scopes" en una página. Ahora vamos a ver que cuando se trabaja con varios controladores se producen varios ámbitos diferentes y que se puede acceder a unos u otros dependiendo las necesidades y situaciones.

Cuando se trata de controladores, los podemos definir "en paralelo" y "anidados" en cada situación los scopes también se colocarán lado a lado o bien unos dentro de otros. Lo veremos mejor con ejemplos de código.

Controladores en paralelo

En este caso tenemos un controlador al lado del otro, en el código HTML. No es la primera vez en este Manual de AngularJS que implementamos controladores en paralelo, así que no debería de representar mucho problema.

Para hacer dos controladores funcionando en paralelo podremos tener un HTML en el que se define un controlador al lado del otro.

<body ng-app="app">
       <header ng-controller="headerCtrl">
        {{ scopeHeader }}
    </header>
    <section ng-controller="sectionCtrl">
        {{ scopeSection }}
    </section>
</body>

El Javascript con el que se declaran y definen estos controladores lo puedes ver a continuación:

var app = angular.module("app", [])

var headerController = function($scope){
    $scope.scopeHeader = "Este es un elemento del header";
};
app.controller("headerCtrl", headerController);

var sectionController = function($scope){
    $scope.scopeSection = "Este es un elemento del section";
};
app.controller("sectionCtrl", sectionController);

Como sabes, en cada controlador se crea un scope son independiente. Todos dependen de un scope raíz que sigue existiendo y dentro de éste tendremos los dos scopes de cada controlador. Si inspeccionamos estos ámbitos con la extensión Batarang obtendremos esto:

Desde un scope no puedo acceder al otro. Eso quiere decir que los elementos definidos para el HEADER, como la propiedad "$scope.scopeHeader", no se puede acceder desde el controlador definido en el SECTION.

Nota:En general, si quisiéramos compartir datos entre ambos controladores nos tocaría implementar una factoría o servicio, o quizás si lo consideramos más adecuado para nuestro caso, crear datos en el scope raíz, inyectando $rootScope en los controladores y asignando propiedades en ese objeto. El $rootScope lo conocimos en el artículo titulado Scope en Angular, manejando ámbitos con $parent.

Controladores anidados

Las características y las posibilidades de navegación entre distintos scopes las vemos mejor todavía en el caso de los controladores anidados. En este caso tenemos un HTML en el que se coloca un controller y dentro de éste otro pedazo de HTML donde se declara otro elemento con un nuevo controller.

<body ng-app="app">
    <section ng-controller="sectionCtrl">
        <p>
            {{ scopeSection }}
        </p>
        <article ng-controller="articleCtrl">
            {{ scopeArticle }}
        </article>
    </section>
</body>

Ahora, en la declaración de los controllers en Javascript, observarás que no hay ningún indicio que en el HTML estén anidados. De hecho es lo correcto, pues los controladores deben ser agnósticos a cómo está hecho nuestro HTML.

var app = angular.module("app", [])

var sectionController = function($scope){
    $scope.scopeSection = "Este es un elemento del section";
};
app.controller("sectionCtrl", sectionController);

var articleController = function($scope){
    $scope.scopeArticle = "Este es un contenido del article";
};
app.controller("articleCtrl", articleController);

En este caso podremos observar que los ámbitos están uno dentro de otro, gracias a la extensión Batarang.

Lo interesante en este caso es que podemos acceder desde un scope a los datos declarados en un scope padre, gracias a la anidación definida en el HTML.

<section ng-controller="sectionCtrl">
    <p>
        {{ scopeSection }}
    </p>
    <article ng-controller="articleCtrl">
        {{ scopeArticle }}
        <p>
        Si quiero, puedo acceder al scope del elemento padre <b>{{ scopeSection }}</b>
        </p>
    </article>
</section>

Como puedes ver, dentro del ARTICLE, definido con controller "articleCtrl" podemos acceder perfectamente a un dato que está en el scope del controlador padre: {{scopeSection}}.

Nota:Esto, que ya se explicó, es gracias a que en Angular, si no se encuentran los datos en el scope actual, automáticamente se busca en el scope padre, subiendo todos los niveles que haga falta hasta llegar al scope raíz. Ese es un comportamiento automático para el cual no necesitamos configurar ni programar nada.

Uso de $parent para ir al scope padre

También explicamos ya que es posible usar $parent dentro de nuestra vista, para acceder al scope padre. Pero ahora lo vamos a ver con controladores anidados.

La variable $parent contiene simplemente una referencia al scope padre. Éste puede ser el scope raíz o si tenemos controladores anidados es el del nivel superior en la anidación. El código HTML anterior podríamos haberlo escrito así.

<section ng-controller="sectionCtrl">
    <p>
        {{ scopeSection }}
    </p>
    <article ng-controller="articleCtrl">
        {{ scopeArticle }}
        <p>
        Si quiero, puedo acceder al scope del elemento padre <b>{{ $parent.scopeSection }}</b>
        </p>
    </article>
</section>

Prácticamente no hay ninguna diferencia, excepto que cuando accedemos desde el ARTICLE a un modelo (dato) del SECTION lo hacemos a través de $parent:

{{ $parent.scopeSection }}

En este caso usando $parent el código puede quedar un poco más claro pues el que lo lea sabrá que ese modelo está en el scope del controlador padre. Sin embargo, enseguida veremos otra manera de hacer esto en la que el código puede quedar más claro todavía.

Sobrescribir variables del modelo en scopes anidados

La situación que nos puede surgir en nuestras aplicaciones es que tengamos una misma propiedad en un scope y en un scope anidado. En una circunstancia como esa, estaremos obligados a usar $parent para acceder al modelo del scope padre. Esto lo veremos mejor con un ejemplo.

<section ng-controller="manualCtrl">
    <p>
        Manual: {{ nombre }}
    </p>
    <article ng-controller="articuloCtrl">
        Artículo: {{ nombre }}
        <p>
        Este artículo pertenece al manual {{ $parent.nombre }}
        </p>
    </article>
</section>

Como puedes observar, tanto el "manualCtrl" como "articuloCtrl" tienen un modelo llamado "nombre" que será el título del manual o el título del artículo. Si desde el controller del artículo queremos acceder al nombre del manual, no podemos escribir {{ nombre }} porque nos saldría el título del artículo. En ese caso tenemos que escribir la ruta $parent.nombre.

Esta situación y uso de $parent se puede extender a múltiples niveles de anidación. Por ejemplo podríamos tener comentarios dentro de los artículos y en ellos el modelo "nombre" que en este caso sería el autor del comentario. Desde el scope de los comentarios podríamos acceder a los dos scope padre, tanto artículo como el manual, encadenando $parent.

<section ng-controller="manualCtrl">
    <p>
        Manual: {{ nombre }}
    </p>
    <article ng-controller="articuloCtrl">
        Artículo: {{ nombre }}
        <p>
        Este artículo pertenece al manual {{ $parent.nombre }}
        </p>
        <p ng-controller="comentariosCtrl">
            Este comentario lo escribe {{ nombre }}
            <br>
            Pertenece al artículo {{ $parent.nombre }}
            <br>
            Que a su vez pertenece al manual {{ $parent.$parent.nombre }}
        </p>
    </article>
</section>

Sintaxis con controller..as

Ahora, creo que todos estaremos de acuerdo en que algo como {{ $parent.$parent.nombre }} no ayuda a la legibilidad del código. Es cierto que sabemos con leer esa expresión que estamos accediendo a un ámbito padre, en este caso dos veces seguidas, pues será el padre dos niveles por arriba. Pero esa complejidad de anidación de los controladores puede que no esté en nuestra cabeza y ya no nos acordemos que hay tantos niveles para arriba.

En fin, que existe una sintaxis que nos mejora el uso que hemos resumido de $parent a través de distintos espacios de nombres. No es otra solución que "controller..as" que ya debes de conocer.

Nota:La sintaxis del "controller as" la hemos usado en diversas ocasiones a lo largo de este manual. La explicamos por primera vez en el artículo sobre los códigos para hacer un controlador y sus variantes.

<section ng-controller="manualCtrl as manual">
    <p>
        Manual: {{ manual.nombre }}
    </p>
    <article ng-controller="articuloCtrl as articulo">
        Artículo: {{ articulo.nombre }}
        <p>
        Este artículo pertenece al manual {{ manual.nombre }}
        </p>
        <p ng-controller="comentariosCtrl as comentario">
            Este comentario lo escribe {{ nombre }}
            <br>
            Pertenece al artículo {{ articulo.nombre }}
            <br>
            Que a su vez pertenece al manual {{ manual.nombre }}
        </p>
    </article>
</section>

Supongo que se aprecian las diferencias. Ahora cada uno de los scopes que tenemos en cada controlador tiene un alias y dentro de ese controller y todos los hijos existe ese alias. A través del alias somos capaces de acceder al modelo declarado en cada controlador, no solo en el que estamos, sino también en todos los controladores padre.

Este modo de usar los controladores produce un cambio de operativa en nuestro Javascript que ya se explicó también. Dejamos el código fuente para que encuentres las diferencias.

var app = angular.module("app", [])

var manualCtrl = function(){
    var vm = this;
    vm.nombre = "Introducción a AngularJS";
};
app.controller("manualCtrl", manualCtrl);

var articuloCtrl = function(){
    var vm = this;
    vm.nombre = "Aprendiendo a usar el scope";
};
app.controller("articuloCtrl", articuloCtrl);

var comentariosCtrl = function(){
    var vm = this;
    vm.nombre = "Miguel Angel Alvarez - DesarrolloWeb.com";
};
app.controller("comentariosCtrl", comentariosCtrl);

Puedes ver el aspecto que tendría este ejemplo con el inspector de los scopes de Batarang:

Con esto termina nuestro recorrido a los scopes de AngularJS, esperamos que esta información te haya resultado de utilidad y comprendas mejor el alcance de los ámbitos en los controladores y el modo de trabajar con ellos.

Miguel Angel Alvarez

Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...

Manual