Cuando ng-transclude pasa a una directiva, el contenido html con la referencia {{item}} que desea repetir (a través de ng-repeat = "elemento en la colección" implementado en la directiva) no funciona con la versión 1.2.18
oh Lawdy. @petebacondarwin ¿ quieres hacer otro de estos? esto es realmente el "no crear un alcance hermano con ng-transclude" de nuevo, es solo que funcionó antes para este caso debido a roturas
(tal vez) relacionado: https://github.com/angular/angular.js/issues/7842
Lamentablemente, no es así como funciona la transclusión ng-transclude
. Lo que está intentando hacer es crear una directiva de contenedor que haga uso de sus elementos secundarios como una "plantilla" de lo que se debe eliminar dentro de su propia directiva.
Me encontré con esto algunas veces en el pasado y tuve un largo debate con Misko al respecto. El contenido que se transcluye está por definición vinculado al ámbito del lugar donde se instancia la directiva; no al alcance de la plantilla de la directiva.
Anteriormente, esto puede haber funcionado ya que en realidad estábamos vinculando la transclusión al alcance incorrecto en algunos casos que involucraban escenarios de transclusión anidados.
Entonces, en realidad, no necesita usar la transclusión aquí, ya que lo que realmente intenta hacer es simplemente inyectar el HTML interno en su propia plantilla. Puede hacer esto en la función de compilación de esta manera:
http://plnkr.co/edit/j3NwMGxkVRM6QMhmydQC?p=preview
app.directive('test', function(){
return {
restrict: 'E',
compile: function compile(tElement, tAttrs, tTransclude) {
// Extract the children from this instance of the directive
var children = tElement.children();
// Wrap the chidren in our template
var template = angular.element('<div ng-repeat="item in collection"></div>');
template.append(children);
// Append this new template to our compile element
tElement.html('');
tElement.append(template);
return {
pre: function preLink(scope, iElement, iAttrs, crtl, transclude) {
scope.collection = [1, 2, 3, 4, 5];
},
post: function postLink(scope, iElement, iAttrs, controller) {
console.log(iElement[0]);
}
};
}
};
});
Otro ejemplo de esto (creo):
Index.html:
<html ng-app='myApp'>
<head>
<title>AngularJS Scopes</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.min.js"></script>
<script src='index.js'></script>
</head>
<body ng-controller='myController'>
<people>Hello {{person.name}}</people>
</body>
</html>
index.js:
var myApp = angular.module( 'myApp', [] );
myApp.controller( 'myController', function( $scope ) {
$scope.people = [
{ name: 'Rob' },
{ name: 'Alex' },
{ name: 'John' }
];
});
myApp.directive( 'people', function() {
return {
restrict: 'E',
transclude: true,
template: '<div ng-repeat="person in people" ng-transclude></div>',
}
});
Trabajó con Angular 1.2.1, pero no con 1.2.18;
El desarrollador inocente solo podía esperar que el código anterior funcionara. Este documento dice:
... a veces es deseable poder pasar una plantilla completa en lugar de una cadena o un objeto. Digamos que queremos crear un componente de "cuadro de diálogo". El cuadro de diálogo debería poder ajustar cualquier contenido arbitrario.
Mientras que la documentación de ngTransclude dice:
Directiva que marca el punto de inserción para el DOM transcluido de la directiva principal más cercana que utiliza la transclusión. Cualquier contenido existente del elemento en el que se coloca esta directiva se eliminará antes de que se inserte el contenido transcluido.
¿En qué se diferencia esto de la definición de @petebacondarwin :
... crear una directiva de contenedor que haga uso de sus elementos secundarios como una "plantilla" de lo que se debe eliminar dentro de su propia directiva
Realmente no entiendo por qué la transclusión no es la solución correcta aquí. En todo caso, esperaría que la solución razonable implique inyectar el alcance de la plantilla a la función de transclusión.
La diferencia es que el contenido transcluido está ligado al "exterior", es decir, al ámbito del lugar donde se encuentra el elemento <people>
.
Mientras que lo que desea es que la plantilla en línea esté vinculada al "interior", es decir, al ámbito de la directiva.
Si su directiva no crea su propio alcance, entonces esto es aproximadamente lo mismo. Si su directiva crea un alcance aislado, por ejemplo, definitivamente no es lo mismo. El alcance interno no tiene acceso al alcance externo.
Supongo que podría crear su propia directiva que de hecho inyecta el alcance de la plantilla en la función de transclusión ...
Lo que está diciendo tiene mucho sentido, pero eso es solo si comprende las profundidades de Angular. Mi punto es que el ejemplo anterior debería funcionar de alguna manera sin demasiado trabajo adicional.
Me parece muy razonable y muy práctico que la función de transclusión pueda acceder a la plantilla (o al ámbito "interno") de alguna manera. Puedo pensar en muchos casos por los que esto será necesario.
El mismo problema se explica en este blog . Creo que cada vez más personas se quejarán de cómo están las cosas en este momento (ya hay una multitud de problemas relacionados en Github como resultado de este comportamiento).
Y muchas gracias por el código. Lo estoy replicando aquí para beneficio de otros:
var myApp = angular.module( 'myApp', [] );
myApp.controller( 'myController', function( $scope ) {
$scope.people = [
{ name: 'Rob' },
{ name: 'Alex' },
{ name: 'John' }
];
});
myApp.directive( 'people', function() {
return {
restrict: 'E',
transclude: true,
template: '<div ng-repeat="person in people" inject></div>'
}
});
myApp.directive('inject', function(){
return {
link: function($scope, $element, $attrs, controller, $transclude) {
if (!$transclude) {
throw minErr('ngTransclude')('orphan',
'Illegal use of ngTransclude directive in the template! ' +
'No parent directive that requires a transclusion found. ' +
'Element: {0}',
startingTag($element));
}
var innerScope = $scope.$new();
$transclude(innerScope, function(clone) {
$element.empty();
$element.append(clone);
$element.on('$destroy', function() {
innerScope.$destroy();
});
});
}
};
});
:-) Estoy de acuerdo en que la transclusión no es un tema fácil de entender sin mucho rascarse la cabeza y golpear el teclado.
¿Quizás necesitamos aclarar la documentación aún más en torno al hecho de que ng-transclude vinculará el contenido transcluido al alcance "externo"?
Personalmente, creo que la documentación actual, al menos en esta página fundamental , es bastante explícita y clara:
¿Qué hace exactamente esta opción de transcluir? transclude hace que el contenido de una directiva con esta opción tenga acceso al alcance fuera de la directiva en lugar de dentro.
(y todo lo que sigue ilustra esto aún más).
Consideraría agregar quizás la directiva que ha proporcionado al marco, posiblemente llamándola 'ng-transclude-internal'. Sé de al menos una persona más que intentó esto, con la directiva llamada 'transcope'.
He intentado una solución, pero me enfrenté a otro problema, por qué el alcance de los padres ng-repeat no es el alcance de la directiva
@luboid, la razón por la que no funciona en su plunker es porque su directiva tiene un alcance aislado, y el DOM compilado modificado (no haga esto, por cierto, esta es una forma tonta de resolver este problema) usará un hermano del padre del alcance aislado.
Agregaré un ejemplo de una forma adecuada de hacer que funcione como espera. (Pero, este sigue siendo un diseño bastante horrible en general, no hay una buena razón para hacer esto)
En realidad, ahora que lo pienso, con ng-repeat u otras directivas de transclusión de elementos, realmente no se puede arreglar esto. Así que sí, eso no funcionará desde la versión 1.2.0
@petebacondarwin Una pregunta realmente para novatos aquí. ¿Por qué usar var innerScope
lugar de solo:
myApp.directive( 'inject', function() {
return {
link: function( $scope, $element, $attrs, controller, $transclude ) {
if ( !$transclude ) {
throw minErr( 'ngTransclude' )( 'orphan',
'Illegal use of ngTransclude directive in the template! ' +
'No parent directive that requires a transclusion found. ' +
'Element: {0}',
startingTag( $element ));
}
$transclude( $scope, function( clone ) {
$element.empty();
$element.append( clone );
});
}
};
});
Por lo general, es mejor crear un nuevo alcance si va a compilar algunos elementos nuevos, ya que no está seguro de si la directiva original está colocada con alguna directiva compleja que también quiera crear alcance, etc. Pero es posible que pueda obtener lejos con esto aquí ...
Gracias por la ayuda,
Usaré plantillas externas ($ templateCache), luego las cosas se vuelven un poco simples, la plantilla base se compila con el alcance de la directiva
@Izhaki @petebacondarwin Muchas gracias por incluir la directiva. Finalmente se actualizó de 1.2.16 a 1.2.20 y me tomó un poco de tiempo ver por qué mi aplicación comenzó a fallar con tanta fuerza.
Pensé que la transclusión era un concepto bastante simple antes de encontrar este hilo. No.
Gracias a todos los que han estado ayudando a aclarar esto. La actualización a 1.2.21 rompió gran parte de nuestra interfaz debido a los problemas descritos aquí, y ahora estamos en el camino correcto.
@caitp @petebacondarwin : Sería muy útil si pudiéramos obtener dos datos adicionales:
Entiendo lo que ya no funciona, pero no veo nada específico en el registro de cambios cerca de esa versión que parezca correlacionarse con este problema en particular.
https://github.com/angular/angular.js/blob/master/CHANGELOG.md#120 -timely-delivery-2013-11-08
Me refería a estos en particular.
- solo pase el alcance de aislamiento a los niños que pertenecen a la directiva de aislamiento (d0efd5ee)
- hacer aislar el alcance realmente aislar (909cabd3, # 1924, # 2500)
Pero, realmente ya no puedo recordar de qué estaba hablando, quién sabe ヽ ༼ ຈ ل͜ ຈ ༽ ノ
En el registro de cambios, solo hay dos tipos de mensajes: cambios y cambios importantes. Esto no se consideró un cambio importante, sino una corrección de errores. Era difícil predecir que muchas personas usaran este comportamiento. Pero probablemente debería ir a los documentos de migración (que necesitan algo de amor)
Sho '' nuff. Ésos son los únicos. Estaba buscando cambios relacionados con la transclusión, pero este es claramente un cambio en el alcance aislado que es incidental a la transclusión. ¡Gracias!
Este cambio también rompió nuestro código. Sería bueno tener una forma declarativa person
de esas directivas y usarlas en el ámbito principal. Parece que es un caso de uso común después de ver cuántas personas lo estaban usando antes de que se corrigiera este bug
en 1.2.18.
Aquí está la demostración que funciona en 1.2.17 y rota desde 1.2.18
@evgenyneu : asumiendo que controlas tanto el contenido interno como la directiva del contenedor, hemos descubierto que usar ng-include en lugar de transclude es claro, fácil y nos da el patrón de herencia que queremos. Simplemente pasamos un nombre de plantilla como un atributo en la declaración de la directiva externa, y colocamos una directiva ng-include en el mismo lugar donde antes teníamos la directiva transclude. Problema resuelto.
Si desea que la plantilla de contenido interno esté en línea en la plantilla de vista, simplemente use ng-template para envolver la plantilla en la misma posición que estaba usando antes.
@xmlilley , excelente consejo, muchas gracias. De hecho, es el enfoque más limpio.
Aquí está la demostración: http://plnkr.co/edit/4MwEL3?p=preview
Chicos, saben que pueden hacer esto en su lugar correctamente http://plnkr.co/edit/fw7thti1u4F9ArxsuYkQ?p=preview --- no es perfecto, pero lo lleva allí
@caitp , bonito, gracias. ¡Tenemos muchas soluciones!
Para aquellos que ven este problema, he creado una directiva ng-transclude 'mejorada', cuyo valor de atributo define el alcance interno buscado y puede ser uno de 3:
Uso de ejemplo:
template:
'<div ng-transclude="parent">' +
'</div>'
Vea este plunk para un ejemplo de todos.
La salida se ve así:
.config(function($provide){
$provide.decorator('ngTranscludeDirective', ['$delegate', function($delegate) {
// Remove the original directive
$delegate.shift();
return $delegate;
}]);
})
.directive( 'ngTransclude', function() {
return {
restrict: 'EAC',
link: function( $scope, $element, $attrs, controller, $transclude ) {
if (!$transclude) {
throw minErr('ngTransclude')('orphan',
'Illegal use of ngTransclude directive in the template! ' +
'No parent directive that requires a transclusion found. ' +
'Element: {0}',
startingTag($element));
}
var iScopeType = $attrs['ngTransclude'] || 'sibling';
switch ( iScopeType ) {
case 'sibling':
$transclude( function( clone ) {
$element.empty();
$element.append( clone );
});
break;
case 'parent':
$transclude( $scope, function( clone ) {
$element.empty();
$element.append( clone );
});
break;
case 'child':
var iChildScope = $scope.$new();
$transclude( iChildScope, function( clone ) {
$element.empty();
$element.append( clone );
$element.on( '$destroy', function() {
iChildScope.$destroy();
});
});
break;
}
}
}
})
@Izhaki - FWIW, +10. No rompe las convenciones actuales, pero agrega una forma limpia y declarativa de acceder voluntariamente a un caso de uso común. ¡Gracias!
: +1: para la solución de @Izhaki . Usaré esto como está, pero me sentiría mucho más cómodo si se incluyera en angular.
+1
@Izhaki +1, ¡gran ejemplo!
Gracias por la directiva de inclusión personalizada. Terminé haciendo una conversión personalizada separada en lugar de anular la predeterminada. Hay un par de llamadas a funciones en su parche de las que no estoy seguro de dónde provienen. minErr () y startTag ()
@dehru : estas funciones son internas de AngularJS.
@Izhaki ¡muchas gracias! He bifurcado su plunk que repite el contenido transcluido, si hay alguien interesado.
Vale la pena mencionar que ng-transclude = "parent" podría no funcionar de la forma esperada.
http://plnkr.co/edit/S6ngqz?p=preview
@Izhaki Muy agradable.
Parece algo que debería estar en una solicitud de extracción a angular.
Al menos en una directiva separada con un repositorio de Github, ¿cree que puede publicarlo (para que pueda ofrecer algunos cambios ...)?
Gracias
Izhaki, esto es asombroso. Te quiero.
@Izhaki Esta es una muy buena solución al problema de los osciloscopios secundarios. Gracias.
Sin embargo, estoy confundido y me preguntaba si podría explicar cómo la herencia de la función $ transclude se traslada de la directiva externa a la directiva ngTransclude. No se indica explícitamente en ningún lugar oficial que pudiera encontrar, pero asumí que transclude: true
tenía que usarse en la directiva para usar la función $ transclude en la función de enlace. Después de jugar un poco con el código, descubrí que usar transclude: true
realmente rompe el código. ¿Que está pasando aqui?
Luché con esto durante días, así como con el hecho de que transcluir inserta los elementos transcluidos dentro del marcador de posición de transcluir en lugar de reemplazarlos con ellos.
¡Descubrí que ng-transclude-replace maneja ambos problemas! http://gogoout.github.io/angular-directives-utils/#/api/ng -directives-utils.transcludeReplace
¡No necesito propagar la directiva ng-repeat en la directiva repetida ni nada por el estilo! Todo parece estar funcionando, incluidas todas las validaciones / vinculaciones.
Aquí hay un fragmento de mi código:
<form-field label="Roles" required>
<checkbox-group>
<checkbox ng-repeat="role in roles" label="{{role.description}}">
<input type="checkbox" name="selectedRoles" ng-model="role.selected" value="{{role.description}}" ng-required="!hasRole" />
</checkbox>
</checkbox-group>
</form-field>
@abobwhite Gracias por mencionar ng-transclude-replace, gran solución.
Recientemente me encontré con este problema. Si bien @Izhaki ha trabajado para mí, tengo curiosidad por saber si ha surgido una mejor práctica en el tiempo transcurrido desde esta discusión. En particular, ¿ha habido algún interés en hacer que ng-transclude="sibling | parent | child"
forme parte del núcleo angular?
@telekid : no tenemos planes de incluir esta función en el núcleo en este momento.
Veo que hay soluciones y soluciones alternativas, pero sería muy conveniente si hubiera una forma de acceder al ámbito interno de la directiva.
Me gustaría señalar que actualicé el mod de transcluir que @Izhaki creó a angular 1.5 para que funcione con transclusión de múltiples ranuras.
Sucursal: https://github.com/NickBolles/ngTranscludeMod/tree/Angular1.5-multi-slot
PR: https://github.com/Izhaki/ngTranscludeMod/pull/2
Plunker: http://plnkr.co/edit/5XGBEX0muH9CSijMfWsH?p=preview
Lo que terminé haciendo fue usar $parent
. Es el armario de la vainilla sin tener que añadir demasiadas cosas.
Entonces tengo algo como:
angular.module('test').directive('myDirectiveWithTransclusion', function() {
return {
restrict: 'E'
transclude: {
transcludeThis: 'transcludeThis'
}
template: "<div ng-repeat='item in array'><div ng-transclude='transcludeThis'></div></div>"
}
})
<my-directive-with-transclusion>
<transclude-this>
{{$parent.item}}
</transclude-this>
</my-directive-with-transclusion>
Hola @ moneytree-doug: utilizo esta solución que proporcionaste, pero encuentro el alcance de transclude: este sigue siendo el alcance de la directiva, no el hijo del nuevo alcance que genera ng-repeat. ¿Puedes darme algunas sugerencias?
@szetin ¿Podrías poner un jsfiddle que me muestre lo que esperas frente a lo que está sucediendo?
Comentario más útil
Lo que terminé haciendo fue usar
$parent
. Es el armario de la vainilla sin tener que añadir demasiadas cosas.Entonces tengo algo como: