Multiple directives [myPopup, myDraggable] asking for new/isolated scope
Solution 1
From docs:
Example scenarios of multiple incompatible directives applied to the same element include:
Multiple directives requesting isolated scope.
Multiple directives publishing a controller under the same name.
Multiple directives declared with the transclusion option.
Multiple directives attempting to define a template or templateURL.
Try removing isolate scope on myDraggable
's directive:
app.directive('myDraggable', ['$document',
function ($document) {
return {
restrict: 'A',
replace: false,
scope: { enabled: '=myDraggable' }, //remove this line
Replace scope.enabled
with attrs.enabled
:
if (attrs.enabled == "true") {
And modify your template to bind the enable attribute:
<div my-draggable="draggable" enabled="{{draggable}}"
Solution 2
A DOM element is creating a collision with your attempted isolate scopes. Therefore, you should always ask yourself if an isolate scope is needed.
Consider removing the isolate scope on myDraggable
, interpolating the myDraggable value (like you did with isDraggable), and accessing the attribute in the link
function.
<div class="draggable" my-draggable="{{isDraggable}}">I am draggable {{isDraggable}}</div>
...
replace: false,
link: function (scope, elm, attrs) {
var startX, startY, initialMouseX, initialMouseY,
enabled = attrs.myDraggable === 'true';
if (enabled === true) {
...
See your updated Plunker here and notice the change in the myPopup template.
If you want to see the myDraggable attribute changes then implement something like:
attrs.$observe('myDraggable', function(iVal) {
enabled = iVal === 'true';
// AND/OR
if (iVal === 'true') doSomething();
});
See Angular Attribute Docs $observe function
Solution 3
I ran into a similar situation. If it doesn't mess up your layout and you definitely need to have an isolate scope on both directives, my suggestion would be to remove the property replace: true
from the myPopup directive definition.
Solution 4
There is a way to work around it.
You will not isolate scope of the directive, instead of it, we will create a new isolated scope using $new method . This method creates a new child scope, if you use true at 1st parameter it will create an isolated scope:
If true, then the scope does not prototypically inherit from the parent scope. The scope is isolated, as it can not see parent >scope properties. When creating widgets, it is useful for the widget to not accidentally read parent state.
But it isn't a problem because we have access to private scope by the directive link function, so is possible to work parallel with "parent" and isolated scope into a very close behavior of a directive with an isolated scope.
Se the example bellow:
app.directive('myDraggable', ['$document',
function ($document) {
return {
restrict: 'A',
replace: false,
scope: false,
//scope: { enabled: '=myDraggable', oneWayAttr: "@" }, //Just for reference I introduced a new
link: function(parentScope, elem, attr) {
var scope = parentScope.$new(true); //Simulated isolation.
scope.oneWayAttr = attr.oneWayAttr; //one-way binding @
scope.myDraggable = parentScope.$parent.$eval(attr.myDraggable);
scope.watchmyDraggable = function () {
return scope.myDraggable = parentScope.$parent.$eval(attr.myDraggable); //parent -> isolatedscope
};
scope.$watch(scope.watchmyDraggable, function(newValue, oldValue) {
//(...)
});
parentScope.innerScope = scope; //If you need view access, you must create a kind of symbolic link to it.
//(...)
}
I developed this work around to a validation directive, that's works very well.
Solution 5
I have included my directive js file twice when I compressed my app. This caused the error.
Martin
Updated on July 09, 2022Comments
-
Martin almost 2 years
I wrote a directive for dialogs (myPopup) and another one for dragging this dialog (myDraggable), but I allways get the error:
Multiple directives [myPopup, myDraggable] asking for new/isolated scope
Here is a Plunker: http://plnkr.co/edit/kMQ0hK5RnVw5xOBdDq5P?p=preview
What can I do?
JS code:
var app = angular.module('myApp', []); function myController($scope) { $scope.isDraggable = true; } app.directive('myPopup', [ function () { "use strict"; return { restrict: 'E', replace: true, transclude: true, template: '<div my-draggable="draggable"class="dialog"><div class="title">{{title}}</div><div class="content" ng-transclude></div></div>', scope: { title: '@?dialogTitle', draggable: '@?isDraggable', width: '@?width', height: '@?height', }, controller: function ($scope) { // Some code }, link: function (scope, element, attr) { if (scope.width) { element.css('width', scope.width); } if (scope.height) { element.css('height', scope.height); } } }; } ]); app.directive('myDraggable', ['$document', function ($document) { return { restrict: 'A', replace: false, scope: { enabled: '=myDraggable' }, link: function (scope, elm, attrs) { var startX, startY, initialMouseX, initialMouseY; if (scope.enabled === true) { elm.bind('mousedown', function ($event) { startX = elm.prop('offsetLeft'); startY = elm.prop('offsetTop'); initialMouseX = $event.clientX; initialMouseY = $event.clientY; $document.bind('mousemove', mousemove); $document.bind('mouseup', mouseup); $event.preventDefault(); }); } function getMaxPos() { var computetStyle = getComputedStyle(elm[0], null); var tx, ty; var transformOrigin = computetStyle.transformOrigin || computetStyle.webkitTransformOrigin || computetStyle.MozTransformOrigin || computetStyle.msTransformOrigin || computetStyle.OTransformOrigin; tx = Math.ceil(parseFloat(transformOrigin)); ty = Math.ceil(parseFloat(transformOrigin.split(" ")[1])); return { max: { x: tx + window.innerWidth - elm.prop('offsetWidth'), y: ty + window.innerHeight - elm.prop('offsetHeight') }, min: { x: tx, y: ty } }; } function mousemove($event) { var x = startX + $event.clientX - initialMouseX; var y = startY + $event.clientY - initialMouseY; var limit = getMaxPos(); x = (x < limit.max.x) ? ((x > limit.min.x) ? x : limit.min.x) : limit.max.x; y = (y < limit.max.y) ? ((y > limit.min.y) ? y : limit.min.y) : limit.max.y; elm.css({ top: y + 'px', left: x + 'px' }); $event.preventDefault(); } function mouseup() { $document.unbind('mousemove', mousemove); $document.unbind('mouseup', mouseup); } } }; }]);
-
Martin over 10 yearsSorry, this does not work because of this part
if (scope.enabled === true) { elm.bind('mousedown', function ($event) {
@Matthias -
Martin over 10 yearsSorry, the DEMO doen´t work, becouse attrs.enabled is in the link-part and so it never will be updated. @Khanh TO
-
Khanh TO over 10 years@Martin: I don't see you bind it anywhere. In case you need to get notified when attrs.enabled is updated, you could try
scope.$watch(function(){ return attrs.enabled },function (value){});
-
Martin over 10 yearsOkay, now I have solved it. Here is my Plunker
-
Stephen J Barker over 10 yearsAwesome, @Martin! If this is solved will you mark an accepted answer and vote for the relevant answers? Thanks!
-
beNerd over 6 yearsanyway we can have a two-way data binding with this?
-
LeonanCarvalho over 6 yearsYes, you can. But it's emulated with an $watch
-
Christiaan Westerbeek about 6 yearsAfter reading bennadel.com/blog/… I scrolled down to find out you answered it already. This needs an upvote since the rest is just a distraction when by accident you defined the component twice by mistake. Like this for example:
angular.module('myapp', []).component('thing', Thing).component('thing', Thing)
. The mistake will cause this error once rendered: > Multiple directives [thing, thing] asking for new/isolated scope on: <thing>