directivas - ¿Cómo acceder al ámbito principal desde una directiva personalizada*con ámbito propio*en AngularJS?
directivas personalizadas angular 5 (6)
Acceder al método del controlador significa acceder a un método en el ámbito principal desde la directiva controlador / enlace / alcance.
Si la directiva comparte / hereda el ámbito principal, entonces es bastante sencillo invocar un método de ámbito primario.
Se requiere un poco más de trabajo cuando se desea acceder al método de ámbito principal desde el ámbito de la directiva aislada.
Existen pocas opciones (puede ser más que las enumeradas a continuación) para invocar un método de alcance primario desde el alcance de directivas aisladas o para observar las variables del alcance principal ( opción # 6 especialmente).
Tenga en cuenta que usé la link function
en estos ejemplos, pero también puede usar un directive controller
según el requisito.
Opción 1. A través de Object literal y desde la plantilla de la directiva html.
index.html
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write(''<base href="'' + document.location + ''" />'');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="[email protected]" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<p> Directive Content</p>
<sd-items-filter selected-items="selectedItems" selected-items-changed="selectedItemsChanged(selectedItems)" items="items"> </sd-items-filter>
<P style="color:red">Selected Items (in parent controller) set to: {{selectedItemsReturnedFromDirective}} </p>
</body>
</html>
itemfilterTemplate.html
<select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;" ng-change="selectedItemsChanged({selectedItems:selectedItems})" ng-options="item.id as item.name group by item.model for item in items | orderBy:''name''">
<option>--</option>
</select>
app.js
var app = angular.module(''plunker'', []);
app.directive(''sdItemsFilter'', function() {
return {
restrict: ''E'',
scope: {
items: ''='',
selectedItems: ''='',
selectedItemsChanged: ''&''
},
templateUrl: "itemfilterTemplate.html"
}
})
app.controller(''MainCtrl'', function($scope) {
$scope.name = ''TARS'';
$scope.selectedItems = ["allItems"];
$scope.selectedItemsChanged = function(selectedItems1) {
$scope.selectedItemsReturnedFromDirective = selectedItems1;
}
$scope.items = [{
"id": "allItems",
"name": "All Items",
"order": 0
}, {
"id": "CaseItem",
"name": "Case Item",
"model": "PredefinedModel"
}, {
"id": "Application",
"name": "Application",
"model": "Bank"
}]
});
plnkr de trabajo: http://plnkr.co/edit/rgKUsYGDo9O3tewL6xgr?p=preview
Opcion 2. A través de Object literal y desde la directiva link / scope
index.html
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write(''<base href="'' + document.location + ''" />'');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="[email protected]" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<p> Directive Content</p>
<sd-items-filter selected-items="selectedItems" selected-items-changed="selectedItemsChanged(selectedItems)" items="items"> </sd-items-filter>
<P style="color:red">Selected Items (in parent controller) set to: {{selectedItemsReturnedFromDirective}} </p>
</body>
</html>
itemfilterTemplate.html
<select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;"
ng-change="selectedItemsChangedDir()" ng-options="item.id as item.name group by item.model for item in items | orderBy:''name''">
<option>--</option>
</select>
app.js
var app = angular.module(''plunker'', []);
app.directive(''sdItemsFilter'', function() {
return {
restrict: ''E'',
scope: {
items: ''='',
selectedItems: ''='',
selectedItemsChanged: ''&''
},
templateUrl: "itemfilterTemplate.html",
link: function (scope, element, attrs){
scope.selectedItemsChangedDir = function(){
scope.selectedItemsChanged({selectedItems:scope.selectedItems});
}
}
}
})
app.controller(''MainCtrl'', function($scope) {
$scope.name = ''TARS'';
$scope.selectedItems = ["allItems"];
$scope.selectedItemsChanged = function(selectedItems1) {
$scope.selectedItemsReturnedFromDirective = selectedItems1;
}
$scope.items = [{
"id": "allItems",
"name": "All Items",
"order": 0
}, {
"id": "CaseItem",
"name": "Case Item",
"model": "PredefinedModel"
}, {
"id": "Application",
"name": "Application",
"model": "Bank"
}]
});
plnkr de trabajo: http://plnkr.co/edit/BRvYm2SpSpBK9uxNIcTa?p=preview
Opción # 3. A través de la referencia de la función y de la plantilla de la directiva html
index.html
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write(''<base href="'' + document.location + ''" />'');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="[email protected]" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<p> Directive Content</p>
<sd-items-filter selected-items="selectedItems" selected-items-changed="selectedItemsChanged" items="items"> </sd-items-filter>
<P style="color:red">Selected Items (in parent controller) set to: {{selectedItemsReturnFromDirective}} </p>
</body>
</html>
itemfilterTemplate.html
<select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;"
ng-change="selectedItemsChanged()(selectedItems)" ng-options="item.id as item.name group by item.model for item in items | orderBy:''name''">
<option>--</option>
</select>
app.js
var app = angular.module(''plunker'', []);
app.directive(''sdItemsFilter'', function() {
return {
restrict: ''E'',
scope: {
items: ''='',
selectedItems:''='',
selectedItemsChanged: ''&''
},
templateUrl: "itemfilterTemplate.html"
}
})
app.controller(''MainCtrl'', function($scope) {
$scope.name = ''TARS'';
$scope.selectedItems = ["allItems"];
$scope.selectedItemsChanged = function(selectedItems1) {
$scope.selectedItemsReturnFromDirective = selectedItems1;
}
$scope.items = [{
"id": "allItems",
"name": "All Items",
"order": 0
}, {
"id": "CaseItem",
"name": "Case Item",
"model": "PredefinedModel"
}, {
"id": "Application",
"name": "Application",
"model": "Bank"
}]
});
plnkr de trabajo: http://plnkr.co/edit/Jo6FcYfVXCCg3vH42BIz?p=preview
Opción # 4. A través de la referencia de funciones y de la directiva de enlace / alcance
index.html
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write(''<base href="'' + document.location + ''" />'');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="[email protected]" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<p> Directive Content</p>
<sd-items-filter selected-items="selectedItems" selected-items-changed="selectedItemsChanged" items="items"> </sd-items-filter>
<P style="color:red">Selected Items (in parent controller) set to: {{selectedItemsReturnedFromDirective}} </p>
</body>
</html>
itemfilterTemplate.html
<select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;" ng-change="selectedItemsChangedDir()" ng-options="item.id as item.name group by item.model for item in items | orderBy:''name''">
<option>--</option>
</select>
app.js
var app = angular.module(''plunker'', []);
app.directive(''sdItemsFilter'', function() {
return {
restrict: ''E'',
scope: {
items: ''='',
selectedItems: ''='',
selectedItemsChanged: ''&''
},
templateUrl: "itemfilterTemplate.html",
link: function (scope, element, attrs){
scope.selectedItemsChangedDir = function(){
scope.selectedItemsChanged()(scope.selectedItems);
}
}
}
})
app.controller(''MainCtrl'', function($scope) {
$scope.name = ''TARS'';
$scope.selectedItems = ["allItems"];
$scope.selectedItemsChanged = function(selectedItems1) {
$scope.selectedItemsReturnedFromDirective = selectedItems1;
}
$scope.items = [{
"id": "allItems",
"name": "All Items",
"order": 0
}, {
"id": "CaseItem",
"name": "Case Item",
"model": "PredefinedModel"
}, {
"id": "Application",
"name": "Application",
"model": "Bank"
}]
});
plnkr de trabajo: http://plnkr.co/edit/BSqx2J1yCY86IJwAnQF1?p=preview
Opción # 5: A través del modelo ng y el enlace bidireccional, puede actualizar las variables del alcance principal. . Por lo tanto, es posible que no necesite invocar funciones de ámbito principal en algunos casos.
index.html
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write(''<base href="'' + document.location + ''" />'');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="[email protected]" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<p> Directive Content</p>
<sd-items-filter ng-model="selectedItems" selected-items-changed="selectedItemsChanged" items="items"> </sd-items-filter>
<P style="color:red">Selected Items (in parent controller) set to: {{selectedItems}} </p>
</body>
</html>
itemfilterTemplate.html
<select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;"
ng-options="item.id as item.name group by item.model for item in items | orderBy:''name''">
<option>--</option>
</select>
app.js
var app = angular.module(''plunker'', []);
app.directive(''sdItemsFilter'', function() {
return {
restrict: ''E'',
scope: {
items: ''='',
selectedItems: ''=ngModel''
},
templateUrl: "itemfilterTemplate.html"
}
})
app.controller(''MainCtrl'', function($scope) {
$scope.name = ''TARS'';
$scope.selectedItems = ["allItems"];
$scope.items = [{
"id": "allItems",
"name": "All Items",
"order": 0
}, {
"id": "CaseItem",
"name": "Case Item",
"model": "PredefinedModel"
}, {
"id": "Application",
"name": "Application",
"model": "Bank"
}]
});
plnkr de trabajo: http://plnkr.co/edit/hNui3xgzdTnfcdzljihY?p=preview
Opción # 6: a través de $watch
y $watchCollection
Es un enlace bidireccional para los items
en todos los ejemplos anteriores, si los elementos se modifican en el ámbito principal, los elementos de la directiva también reflejarán los cambios.
Si desea ver otros atributos u objetos del ámbito principal, puede hacerlo usando $watch
y $watchCollection
como se indica a continuación
html
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>
document.write(''<base href="'' + document.location + ''" />'');
</script>
<link rel="stylesheet" href="style.css" />
<script data-require="[email protected]" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<p>Hello {{user}}!</p>
<p>directive is watching name and current item</p>
<table>
<tr>
<td>Id:</td>
<td>
<input type="text" ng-model="id" />
</td>
</tr>
<tr>
<td>Name:</td>
<td>
<input type="text" ng-model="name" />
</td>
</tr>
<tr>
<td>Model:</td>
<td>
<input type="text" ng-model="model" />
</td>
</tr>
</table>
<button style="margin-left:50px" type="buttun" ng-click="addItem()">Add Item</button>
<p>Directive Contents</p>
<sd-items-filter ng-model="selectedItems" current-item="currentItem" name="{{name}}" selected-items-changed="selectedItemsChanged" items="items"></sd-items-filter>
<P style="color:red">Selected Items (in parent controller) set to: {{selectedItems}}</p>
</body>
</html>
script app.js
var app = angular.module (''plunker'', []);
app.directive(''sdItemsFilter'', function() {
return {
restrict: ''E'',
scope: {
name: ''@'',
currentItem: ''='',
items: ''='',
selectedItems: ''=ngModel''
},
template: ''<select ng-model="selectedItems" multiple="multiple" style="height: 140px; width: 250px;"'' +
''ng-options="item.id as item.name group by item.model for item in items | orderBy:/'name/'">'' +
''<option>--</option> </select>'',
link: function(scope, element, attrs) {
scope.$watchCollection(''currentItem'', function() {
console.log(JSON.stringify(scope.currentItem));
});
scope.$watch(''name'', function() {
console.log(JSON.stringify(scope.name));
});
}
}
})
app.controller(''MainCtrl'', function($scope) {
$scope.user = ''World'';
$scope.addItem = function() {
$scope.items.push({
id: $scope.id,
name: $scope.name,
model: $scope.model
});
$scope.currentItem = {};
$scope.currentItem.id = $scope.id;
$scope.currentItem.name = $scope.name;
$scope.currentItem.model = $scope.model;
}
$scope.selectedItems = ["allItems"];
$scope.items = [{
"id": "allItems",
"name": "All Items",
"order": 0
}, {
"id": "CaseItem",
"name": "Case Item",
"model": "PredefinedModel"
}, {
"id": "Application",
"name": "Application",
"model": "Bank"
}]
});
Siempre puede consultar la documentación de AngularJs para obtener explicaciones detalladas sobre las directivas.
Estoy buscando cualquier forma de acceder al ámbito "padre" dentro de una directiva. Cualquier combinación de alcance, transclusión, requerimiento, pasar variables (o el alcance en sí) desde arriba, etc. Estoy totalmente dispuesto a hacer lo imposible, pero quiero evitar algo totalmente intrincado o que no se pueda mantener. Por ejemplo, sé que podría hacerlo ahora mismo tomando $scope
de los parámetros de preLink e iterando sobre sus $sibling
para encontrar el "padre" conceptual.
Lo que realmente quiero es poder $watch
una expresión en el ámbito principal. Si puedo hacer eso, entonces puedo lograr lo que estoy tratando de hacer aquí: AngularJS - ¿Cómo representar un parcial con variables?
Una nota importante es que la directiva debe ser reutilizable dentro del mismo ámbito principal. Por lo tanto, el comportamiento predeterminado (alcance: falso) no funciona para mí. Necesito un ámbito individual por instancia de la directiva, y luego necesito $watch
una variable que vive en el ámbito primario.
Un ejemplo de código vale 1000 palabras, así que:
app.directive(''watchingMyParentScope'', function() {
return {
require: /* ? */,
scope: /* ? */,
transclude: /* ? */,
controller: /* ? */,
compile: function(el,attr,trans) {
// Can I get the $parent from the transclusion function somehow?
return {
pre: function($s, $e, $a, parentControl) {
// Can I get the $parent from the parent controller?
// By setting this.$scope = $scope from within that controller?
// Can I get the $parent from the current $scope?
// Can I pass the $parent scope in as an attribute and define
// it as part of this directive''s scope definition?
// What don''t I understand about how directives work and
// how their scope is related to their parent?
},
post: function($s, $e, $a, parentControl) {
// Has my situation improved by the time the postLink is called?
}
}
}
};
});
Consulte ¿Cuáles son los matices del alcance prototípico / herencia prototípica en AngularJS?
Para resumir: la forma en que una directiva accede a su ámbito principal ( $parent
) depende del tipo de ámbito que crea la directiva:
default (
scope: false
): la directiva no crea un nuevo ámbito, por lo que no hay herencia aquí. El alcance de la directiva es el mismo que el principal / contenedor. En la función de enlace, use el primer parámetro (típicamente,scope
).scope: true
: la directiva crea un nuevo ámbito secundario que se hereda prototípicamente del ámbito principal. Las propiedades que están definidas en el ámbito principal están disponibles para elscope
la directiva (debido a la herencia prototípica). Solo tenga cuidado de no escribir en una propiedad de ámbito primitivo, que creará una nueva propiedad en el ámbito de la directiva (que oculta / sombrea la propiedad del ámbito principal del mismo nombre).scope: { ... }
: la directiva crea un nuevo alcance aislado / aislado. No hereda de forma prototípica el ámbito principal. Aún puede acceder al ámbito principal utilizando$parent
, pero esto normalmente no se recomienda. En su lugar, debe especificar qué propiedades de ámbito principal (y / o función) necesita la directiva a través de atributos adicionales en el mismo elemento donde se usa la directiva, utilizando la notación=
,@
y&
.transclude: true
: la directiva crea un nuevo ámbito secundario "transcluido", que se hereda prototípicamente del ámbito principal. Si la directiva también crea un ámbito aislado, los ámbitos transcluidos y aislados son hermanos. La propiedad$parent
de cada ámbito hace referencia al mismo ámbito principal.
Actualización de v1.3 angular : si la directiva también crea un ámbito de aislamiento, el ámbito transcluido ahora es un elemento secundario del ámbito de aislamiento. Los ámbitos transcluidos y aislados ya no son hermanos. La propiedad$parent
del ámbito transcluido ahora hace referencia al ámbito aislado.
El enlace de arriba tiene ejemplos e imágenes de los 4 tipos.
No puede acceder al ámbito en la función de compilación de la directiva (como se menciona aquí: https://github.com/angular/angular.js/wiki/Understanding-Directives ). Puede acceder al alcance de la directiva en la función de enlace.
Acecho:
Para 1. y 2. más arriba: normalmente usted especifica qué propiedad principal necesita la directiva a través de un atributo, luego $ verla:
<div my-dir attr1="prop1"></div>
scope.$watch(attrs.attr1, function() { ... });
Si está viendo una propiedad de objeto, necesitará usar $ parse:
<div my-dir attr2="obj.prop2"></div>
var model = $parse(attrs.attr2);
scope.$watch(model, function() { ... });
Para 3. arriba (aislar el alcance), observe el nombre que le da a la propiedad directiva utilizando la notación @
o =
:
<div my-dir attr3="{{prop3}}" attr4="obj.prop4"></div>
scope: {
localName3: ''@attr3'',
attr4: ''='' // here, using the same name as the attribute
},
link: function(scope, element, attrs) {
scope.$watch(''localName3'', function() { ... });
scope.$watch(''attr4'', function() { ... });
Este es un truco que utilicé una vez: cree una directiva "ficticia" para mantener el alcance principal y colóquela en algún lugar fuera de la directiva deseada. Algo como:
module.directive(''myDirectiveContainer'', function () {
return {
controller: function ($scope) {
this.scope = $scope;
}
};
});
module.directive(''myDirective'', function () {
return {
require: ''^myDirectiveContainer'',
link: function (scope, element, attrs, containerController) {
// use containerController.scope here...
}
};
});
y entonces
<div my-directive-container="">
<div my-directive="">
</div>
</div>
Tal vez no sea la solución más elegante, pero hizo el trabajo.
Habiendo probado todo, finalmente encontré una solución.
Simplemente coloca lo siguiente en tu plantilla:
{{currentDirective.attr = parentDirective.attr; ''''}}
Simplemente escribe el atributo / variable de ámbito principal al que desea acceder al ámbito actual.
También note el ; ''''
; ''''
al final de la declaración, es para asegurarse de que no haya resultados en su plantilla. (Angular evalúa cada declaración, pero solo saca la última).
Es un poco hacky, pero después de unas horas de prueba y error, hace el trabajo.
Si está utilizando la sintaxis de ES6 Classes y ControllerAs
, necesita hacer algo ligeramente diferente.
Vea el fragmento a continuación y tenga en cuenta que vm
es el valor de ControllerAs
del ControllerAs
principal tal como se utiliza en el HTML principal
myApp.directive(''name'', function() {
return {
// no scope definition
link : function(scope, element, attrs, ngModel) {
scope.vm.func(...)
scope: false
transclude: false
y tendrás el mismo alcance (con elemento padre)
$scope.$watch(...
Hay muchas maneras de acceder al alcance principal dependiendo de estas dos opciones de alcance y cobertura.