scope - example - ng-switch
Cómo enlazar adecuadamente el alcance entre la directiva y el controlador con angularJS (3)
Estoy intentando generar una lista desordenada jerárquica de n niveles con anugularJS, y he podido hacerlo con éxito. Pero ahora, tengo problemas de alcance entre la directiva y el controlador. Necesito cambiar una propiedad de alcance del padre desde dentro de una función llamada mediante ng-click en la plantilla directiva.
Ver http://jsfiddle.net/ahonaker/ADukg/2046/ - aquí está el JS
var app = angular.module(''myApp'', []);
//myApp.directive(''myDirective'', function() {});
//myApp.factory(''myService'', function() {});
function MyCtrl($scope) {
$scope.itemselected = "None";
$scope.organizations = {
"_id": "SEC Power Generation",
"Entity": "OPUNITS",
"EntityIDAttribute": "OPUNIT_SEQ_ID",
"EntityID": 2,
"descendants": ["Eastern Conf Business Unit", "Western Conf Business Unit", "Atlanta", "Sewanee"],
children: [{
"_id": "Eastern Conf Business Unit",
"Entity": "",
"EntityIDAttribute": "",
"EntityID": null,
"parent": "SEC Power Generation",
"descendants": ["Lexington", "Columbia", "Knoxville", "Nashville"],
children: [{
"_id": "Lexington",
"Entity": "OPUNITS",
"EntityIDAttribute": "OPUNIT_SEQ_ID",
"EntityID": 10,
"parent": "Eastern Conf Business Unit"
}, {
"_id": "Columbia",
"Entity": "OPUNITS",
"EntityIDAttribute": "OPUNIT_SEQ_ID",
"EntityID": 12,
"parent": "Eastern Conf Business Unit"
}, {
"_id": "Knoxville",
"Entity": "OPUNITS",
"EntityIDAttribute": "OPUNIT_SEQ_ID",
"EntityID": 14,
"parent": "Eastern Conf Business Unit"
}, {
"_id": "Nashville",
"Entity": "OPUNITS",
"EntityIDAttribute": "OPUNIT_SEQ_ID",
"EntityID": 4,
"parent": "Eastern Conf Business Unit"
}]
}]
};
$scope.itemSelect = function (ID) {
$scope.itemselected = ID;
}
}
app.directive(''navtree'', function () {
return {
template: ''<ul><navtree-node ng-repeat="item in items" item="item" itemselected="itemselected"></navtree-node></ul>'',
restrict: ''E'',
replace: true,
scope: {
items: ''=''
}
};
});
app.directive(''navtreeNode'', function ($compile) {
return {
restrict: ''E'',
template: ''<li><a ng-click="itemSelect(item._id)">{{item._id}} - {{itemselected}}</a></li>'',
scope: {
item: "=",
itemselected: ''=''
},
controller: ''MyCtrl'',
link: function (scope, elm, attrs) {
if ((angular.isDefined(scope.item.children)) && (scope.item.children.length > 0)) {
var children = $compile(''<navtree items="item.children"></navtree>'')(scope);
elm.append(children);
}
}
};
});
y aquí está el HTML
<div ng-controller="MyCtrl">
Selected: {{itemselected}}
<navtree items="organizations.children"></navtree>
</div>
Tenga en cuenta que la lista se genera a partir del modelo. Y ng-click llama a la función para establecer la propiedad del ámbito principal (elementos seleccionados), pero el cambio solo se produce localmente. El comportamiento esperado, cuando hago clic en un elemento, es que "Seleccionado: Ninguno" debería cambiar a "Seleccionado: xxx", donde xxx es el elemento en el que se hizo clic.
¿No estoy vinculando adecuadamente la propiedad entre el ámbito principal y la directiva? ¿Cómo paso el cambio de propiedad al ámbito principal?
Espero que esto quede claro.
Gracias de antemano por cualquier ayuda.
Maxdec tiene razón, esto tiene que ver con el alcance. Desafortunadamente, este es un caso que es lo suficientemente complicado como para que los documentos de AngularJS puedan ser mal dirigidos para un principiante (como yo).
Advertencia: prepárate para mí siendo un poco largo aliento cuando intento explicar esto. Si solo quieres ver el código, ve a este JSFiddle . También he encontrado que los videos de egghead.io son invaluables para aprender sobre AngularJS.
Esta es mi comprensión del problema: tiene una jerarquía de directivas (navtree, navitem) y desea pasar información desde el elemento de navegación "hasta el árbol" al controlador raíz. AngularJS, como el Javascript bien escrito en general, está configurado para ser estricto con respecto al alcance de sus variables, de modo que no desordene accidentalmente otros scripts que también se ejecutan en la página.
Hay una sintaxis especial ( &
) en Angular que le permite crear un alcance aislado y llamar a una función en el alcance principal:
// in your directive
scope: {
parentFunc: ''&''
}
Hasta ahora tan bueno. Las cosas se complican cuando tiene varios niveles de directivas, porque esencialmente desea hacer lo siguiente:
- Tener una función en el controlador raíz que acepte una variable y actualizar el modelo
- Una directiva de nivel medio.
- Una directiva a nivel de niño que puede comunicarse con el controlador raíz
El problema es que la directiva de nivel secundario no puede ver el controlador raíz. Tengo entendido que debe configurar una "cadena" en su estructura directiva que actúa de la siguiente manera:
Primero: tenga una función en su controlador raíz que devuelva una función (que tenga referencia al alcance del controlador de vista raíz):
$scope.selectFunctionRoot = function () {
return function (ID) {
$scope.itemselected = ID;
}
}
Segundo: configure la directiva de nivel medio para que tenga su propia función de selección (que pasará al niño) que devuelve algo como lo siguiente. Observe cómo debemos evitar el alcance de la directiva de nivel medio, porque cuando este código se ejecute realmente, estará en el contexto de la directiva de nivel secundario:
// in the link function of the mid-level directive. the ''navtreelist''
scope.selectFunctionMid = function () {
// if we don''t capture our mid-level scope, then when we call the function in the navtreeNode it won''t be able to find the mid-level-scope''s functions
_scope = scope;
return function (item_id) {
console.log(''mid'');
console.log(item_id);
// this will be the "root" select function
parentSelectFunction = _scope.selectFunction();
parentSelectFunction(item_id);
};
};
Tercero: en la directiva de nivel de niño ( navtreeNode
), vincule una función a ng-click
que llame a una función local, que, a su vez, "llamará a la cadena" hasta el controlador raíz:
// in ''navtreeNode'' link function
scope.childSelect = function (item_id) {
console.log(''child'');
console.log(item_id);
// this will be the "mid" select function
parentSelectFunction = scope.selectFunction();
parentSelectFunction(item_id);
};
Aquí está la JSFiddle actualizada JSFiddle , que tiene comentarios en el código.
Por favor, eche un vistazo a este violín de trabajo, http://jsfiddle.net/eeuSv/
Lo que hice fue requerir el controlador principal dentro de la directiva navtree-node
y llamar a una función miembro definida en ese controlador. La función miembro es setSelected
. Tenga en cuenta que es this.setSelected
y no $scope.setSelected
. A continuación, defina un método de ámbito de navtree-node
itemSelect
. Mientras hace clic en las etiquetas de anclaje, llamará al método itemSelect
en el navtree-node
. Esta entrada llamará al método de miembro del controlador setSelected
pasando la ID seleccionada.
scope.itemSelect = function(id){ myGreatParentControler.setSelected(id) }
Puede ser porque cada directiva crea su propio alcance (en realidad, usted les dice que lo hagan).
Puede leer más sobre las directivas docs.angularjs.org/guide/directive , especialmente el capítulo "Cómo escribir directivas (versión larga)".
alcance - si se establece en:
true : se creará un nuevo ámbito para esta directiva. Si varias directivas en el mismo elemento solicitan un nuevo ámbito, solo se creará un nuevo ámbito. La nueva regla de alcance no se aplica a la raíz de la plantilla, ya que la raíz de la plantilla siempre obtiene un nuevo alcance.
{} (hash de objeto) : luego se crea un nuevo ámbito de ''aislamiento''. El alcance ''aislado'' difiere del alcance normal en que no se hereda prototípicamente del alcance principal. Esto es útil al crear componentes reutilizables, que no deben leer o modificar datos accidentalmente en el ámbito principal.
Por lo tanto, los cambios que realice no se reflejan en el ámbito MyCtrl porque cada directiva tiene su propio alcance "aislado".
Es por eso que un clic solo cambia la variable local $scope.itemselected
y no "todos".