Multiple directives [myPopup, myDraggable] asking for new/isolated scope
AngularjsScopeAngularjs DirectiveAngularjs Problem Overview
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);
}
}
};
}]);
Angularjs Solutions
Solution 1 - Angularjs
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 - Angularjs
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 - Angularjs
my error was similar:
Error: [$compile:multidir] Multiple directives [groups, groups] asking for new/isolated scope on:
In my case I had duplicate declaration of
.component('groups', new GroupsComponent());
in app.js/app.ts file
and at the same time on the component itself
const groups = angular.module('groups', ['toaster'])
.component('groups', new GroupsComponent());
Removing it from app.js/app.ts fixed the issue.
Solution 4 - Angularjs
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 5 - Angularjs
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 6 - Angularjs
I have included my directive js file twice when I compressed my app. This caused the error.
Solution 7 - Angularjs
Leave out scope: { enabled: '=myDraggable' } from your "myDraggable"-directive you don't need it. So:
return {
restrict: 'A',
replace: false,
link: function (scope, elm, attrs) {