I'm using an angular directive to add a reusable popup to various elements. Due to styling constraints, rather than adding the popover to the element, I need to add it to the document body. I would later like to access it in my controller - how would I go about doing so?
.controller 'slUserCtrl', ($element) ->
$element.popup
hoverable: true
position: 'bottom right'
popup: # What do I put here to get the "template" DOM element?
.directive 'slUser', ($document) ->
template = $templateCache.get 'users/sl-user.html'
return {
restrict: "A"
controller: 'slUserCtrl'
compile: (elem, attrs) ->
angular.element(document.body).append template
}
When you want to display a modal popup by attaching it to the document body, you are manipulating the DOM. There is one place where DOM manipulation is Ok, and that's the directive's link function.
var app = angular.module('app',[]);
app.controller('ctrl', function($scope) {
});
app.directive('modal', function() {
return {
restrict: 'A',
transclude: 'element',
controller: function($rootScope) {
$rootScope.dialog = { show: false };
},
link: function(scope, element,attr,ctrl, transclude) {
transclude(function(clone, scope) {
scope.$watch('dialog.show', function (val) {
if (val) {
clone.css('display', 'block');
}
else {
clone.css('display', 'none');
}
});
angular.element(document.body).append(clone);
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="app">
<div ng-controller="ctrl">
Hello World!
<button ng-click="dialog.show = !dialog.show">Open Modal </button> {{test}}
<div modal>
This is a modal dialog
</div>
</div>
</body>
Related
I have a template for my directive, which contains a scope variable called content:
<div class="directive-view">
<span class="directive-header">My Directive</span>
<span ng-bind-html="content"></span>
</div>
I have the following directive, which watches for changes to content and then compiles the element's contents:
(function () {
"use strict";
angular
.module('myApp.myDirective', [])
.directive("myDirective", myDirective);
function myDirective($compile, $sce) {
return {
restrict: 'E',
scope: {
},
templateUrl:'../partials/directives/my-directive.html',
controller: function($scope) {
$scope.testAlert = function(msg) { alert(msg) };
},
link: function (scope, element, attrs, ctrl) {
scope.content = $sce.trustAsHtml('Initial value');
scope.$watch(attrs.content, function(value) {
element.html(value);
console.log("Content changed -- recompiling");
$compile(element.contents())(scope);
});
scope.content = $sce.trustAsHtml('<button ng-click="testAlert(\'clicked!\')">Click</button>');
}
};
}
})();
The directive renders the button element. However, the controller function testAlert() does not get called when the button element is clicked on.
Also, the $watch callback is called only once, after content is set to Initial value. The callback is not triggered after content is set to the button. (I would have thought that the callback is triggered when the value of attrs.content is changed.)
If I manually recompile the element:
$compile(element.contents())(scope);
the button element still does not trigger the testAlert function when clicked.
How do I correctly recompile the element contents, so that the correct bindings are made?
You do not need use $sce in this case. Use just $compile.
Live example on jsfiddle.
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', function($scope) {
$scope.name = 'Superhero';
$scope.changeTEmplate = function(){
$scope.cont = '<div><b>i\'m changed</b></div>';
}
})
.directive("myDirective", function myDirective($compile) {
return {
restrict: 'E',
scope: {content:"="},
template: `<div class="directive-view">
<span class="directive-header">My Directive</span>
<span></span>
</div>`,
controller: function($scope) {
$scope.testAlert = function(msg) {
alert(msg)
};
},
link: function(scope, element, attrs, ctrl) {
scope.$watch('content', function(value) {
var span = angular.element(element.find('span')[1]);
span.html(value);
console.log("Content changed -- recompiling",value);
$compile(span)(scope);
});
scope.content = '<button ng-click="testAlert(\'clicked!\')">Click</button>';
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="MyCtrl">
Hello, {{name}}!
<button ng-click="changeTEmplate()"> change Content</button>
<my-directive content="cont"></my-directive>
</div>
</div>
I have a directive that controls a personalized multiselect. Sometimes from the main controller I'd like to clear all multiselects. I have the multiselect value filling a "filter" bidirectional variable, and I am able to remove content from there, but when doing that I also have to change some styles and other content. In other words: I have to call a method belonging to the directive from a button belonging to the controller. Is that even posible with this data structure?:
(By the way, I found other questions and examples but their directives didn't have their own scope.)
function MultiselectDirective($http, $sce) {
return {
restrict: 'E',
replace: true,
templateUrl: 'temp.html',
scope: {
filter: "=",
name: "#",
url: "#"
},
link: function(scope, element, attrs){
//do stuff
scope.function_i_need_to_call = function(){
//updates directtive template styles
}
}
}
}
The best solution and the angular way - use event.
Live example on jsfiddle.
angular.module('ExampleApp', [])
.controller('ExampleOneController', function($scope) {
$scope.raise = function(val){
$scope.$broadcast('raise.event',val);
};
})
.controller('ExampleTwoController', function($scope) {
$scope.raise = function(val){
$scope.$broadcast('raise.event',val);
};
})
.directive('simple', function() {
return {
restrict: 'A',
scope: {
},
link: function(scope) {
scope.$on('raise.event',function(event,val){
console.log('i`m from '+val);
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="ExampleApp">
<div ng-controller="ExampleOneController">
<h3>
ExampleOneController
</h3>
<form name="ExampleForm" id="ExampleForm">
<button ng-click="raise(1)" simple>
Raise 1
</button>
</form>
</div>
<div ng-controller="ExampleTwoController">
<h3>
ExampleTwoController
</h3>
<form name="ExampleForm" id="ExampleForm">
<button ng-click="raise(2)" simple>
Raise 2
</button>
</form>
</div>
</body>
I think better solution to link from controller to directives is this one:
// in directive
return {
scope: {
controller: "=",
},
controller: function($scope){
$scope.mode = $scope.controller.mode;
$scope.controller.function_i_need_to_call = function(){}
$scope.controller.currentState = state;
}
}
// in controller
function testCtrl($scope){
// config directive
$scope.multiselectDirectiveController = {
mode: 'test',
};
// call directive methods
$scope.multiselectDirectiveController.function_i_need_to_call();
// get directive property
$scope.multiselectDirectiveController.currentState;
}
// in template
<Multiselect-directive controller="multiselectDirectiveController"></Multiselect-directive>
Context
I m actually developping an application in which I need to generate directive dynamically from a controller to a view (drag n drop system).
It works like this :
the directive
angular.module('app')
.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function (html) {
ele.html(html);
$compile(ele.contents())(scope);
});
}
};
});
And in the controller, it looks like :
$scope.listModules = [
{libelle: "Utilisateurs connectés", template: "<div user-connecte></div>", drag: true}
]
And in the HTML file :
<div ng-repeat="currentModule in listModules" dynamic="currentModule.template">
The directive loaded
</div>
The problem
I want to use the
$scope.$emit('event');
from my controller, to send some information to my directive, which is supposed to get it with :
$scope.$on('event',function(){console.log('yiihaaa');})
It seems that it doesnt happen anything...
I need the log to be displayed.
Can you help me ?
Thanks for advance
ngRepeate create own isolated scope, and scope in your directive link function is this isolated scope.
When you do $emit
Dispatches an event name upwards through the scope hierarchy notifying the registered $rootScope.Scope listeners.
For sending event to child scopes you need use $broadcast
So for solving your problem you have at least two ways
1) use $broadcast instead of $emit
$scope.rechercherStats = function () { $scope.$broadcast('reload'); };
// Code goes here
angular.module('app',[])
.controller('ctrl',function($scope){
$scope.listModules = [
{libelle: "Utilisateurs connectés", template: "<div user-connecte></div>", drag: true}
];
$scope.rechercherStats = function () {console.log('reload'); $scope.$broadcast('reload'); };
});
angular.module('app')
.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function (html) {
ele.html(html);
$compile(ele.contents())(scope);
});
scope.$on('reload',function(){console.log('yiihaaa');})
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
Sample
<div ng-repeat="currentModule in listModules" dynamic="currentModule.template">
The directive loaded
</div>
<input type="button" ng-click="rechercherStats()" value="btn"/>
</div>
2) add listener not in scope ngRepeat, but in parent scope
angular.module('app')
.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function (html) {
ele.html(html);
$compile(ele.contents())(scope);
});
scope.$parent.on('event',function(){console.log('yiihaaa');});
}
};
});
// Code goes here
angular.module('app',[])
.controller('ctrl',function($scope){
$scope.listModules = [
{libelle: "Utilisateurs connectés", template: "<div user-connecte></div>", drag: true}
];
$scope.rechercherStats = function () {console.log('reload'); $scope.$emit('reload'); };
});
angular.module('app')
.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function (html) {
ele.html(html);
$compile(ele.contents())(scope);
});
scope.$parent.$on('reload',function(){console.log('yiihaaa');})
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
Sample
<div ng-repeat="currentModule in listModules" dynamic="currentModule.template">
The directive loaded
</div>
<input type="button" ng-click="rechercherStats()" value="btn"/>
</div>
As #Grundy said. Use $broadcast and $on from $rootScope.
var subscription = $rootScope.$on("myEvent", function() {
console.log("yiihao");
});
Don't forget to destroy it.
$rootScope.$on('$destroy', function () {
subscription();
});
The $broadcast would be like that.
$rootScope.$broadcast("myEvent", {});
Have you tried this link: http://onehungrymind.com/angularjs-dynamic-templates/
They are loading directives on the fly. The approach can be adapted for your requirements?
I am setting up a <button> that is supposed to open a chat window.
the chat window has a ng-show depending on scope.openChat which is false to start.
When I clicked the button I update scope.openChat to true and use $scope.apply.
The scope seems to have updated but the ng-show does not do anything.
here is the html
<div ng-controller="MessagesCtrl">
<button start-chat>Send Messages</button>
</div>
and
<div ng-show="openChat" ng-controller="MessagesCtrl">
<div>CHAT WINDOW
</div>
</div>
here is the controller:
app.controller("MessagesCtrl", function MessagesCtrl($scope,Auth) {
$scope.openChat = false;
$scope.message = { recipient : undefined, sender: undefined, text:'text'};
});
Here is the directive for the button:
'use strict';
app.directive('startChat',[ 'Post', 'Auth', function (Post, Auth) {
return {
restrict: 'A',
replace: true,
bindToController: true,
controller: 'MessagesCtrl',
link: function(scope, element, attrs) {
element.bind('click', function() {
scope.$apply(function() {
scope.openChat = true;
scope.message.recipient = scope.profile.$id;
scope.message.sender = Auth.resolveUser().uid;
});
});
}
}
}]);
thank you
I'd suggest not creating two instances of MessagesCtrl. Here is a simplified working example of the issue you are trying to solve. Examine the markup and see that MessagesCtrl contains both of these elements. Otherwise, you were on the right track updating $scope and calling $apply
Also note that .on() is the preferred method to .bind() see jQuery docs
JSFiddle Link
<div ng-app="app">
<div ng-controller="MessagesCtrl">
<button start-chat>Send Messages</button>
<div class="chatWindow" ng-show="openChat"></div>
</div>
</div>
app.directive('startChat', [function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.on('click', function() {
scope.$apply(function() {
scope.openChat = true;
});
});
}
}
}]);
app.controller('MessagesCtrl', ['$scope', function($scope) {
$scope.openChat = false;
$scope.message = { recipient : undefined, sender: undefined, text:'text'};
}]);
Directive in AngularJS: I find out that the elements inside an element with the directive do not inherit its "scope".
For example:
app
.controller('xxx', function($scope) {})
.directive('yyy', function() {
return {
scope: {},
link: function(scope,elem,attrs) {}
};
});
When we use it in the HTML:
<body ng-controller="xxx">
<div id='withD' yyy>
<div id='inside'>Inside the element with a directive</div>
</div>
</body>
"body" will have a scope whose $id may be 003;
then "#withD" will have an isolate scope $id=004;
the "#inside" will have the scope $id=003, which means the "#inside" inherits "body"'s scope.
If I use "transinclude" for the directive "yyy"; then "body" scope.$id=003, "#withD" scope.$id=004, "#inside" scope.$id=005; moreover, 003 has two children 004 and 005. However, I wanna make the element with the directive has an isolate scope and its child elements inherit the scope.
I read over "ui.bootstrap.tabs" source code but I do not like the style, for it is strange and also not make the parent element share its scope with child elements'; it looks like this:
app
.directive('xitem', function() {
scope: {},
controller: function($scope) {
$scope.subitem = [];
return {
add: function(xsubitem) {$scope.subitem.push(xsubitem);}
}
},
link: function(scope,elem,attrs) {}
})
.directive('xsubitem', function() {
require: '^xitem',
link: function(scope,elem,attrs,ctrl) {ctrl.add(elem);}
});
My expectation is that:
<div ng-controller="xxx">
<div yyy>
<button ng-click="sayHi()">Hi</button>
<div>
</div>
when you click the "Hi" button, the alert dialog will pop up with the message "Hello World" not "Error: Scope".
app
.controller('xxx', function($scope) {
$scope.sayHi = function(){alert('Error: Scope');};
})
.directive('yyy', function() {
return {
scope: {},
link: function(scope,elem,attrs) {
scope.sayHi = function(){alert('Hello World');};
}
};
});
Moreover, I tried this:
app
.controller('xxx', function($scope) {
$scope.sayHi = function(){alert('Error: Scope');};
})
.directive('yyy', function() {
return {
scope: {},
controller: function($scope, $compile) {$scope._compile = $compile;}
link: function(scope,elem,attrs) {
elem.children().forEach(function(one) {
scope._compile(one)(scope);
});
scope.sayHi = function(){alert('Hello World');};
}
};
});
Then it will pop up two alert dialogs with the message "Error: Scope" and "Hello World" respectively.
Now I found the solution - load template dynamically and use $compile to specify scope:
.controller('main', function($scope) {
$scope.sayHi = function() {alert('scope error');};}
)
.directive('scopeInherit', ['$http', '$compile', function($http, $compile) {
return {
scope: {},
link: function(scope, elem, attrs) {
scope.sayHi = function() {alert('hello world');};
scope.contents = angular.element('<div>');
$http.get(elem.attr('contentsURL'))
.success(function (contents) {
scope.contents.html(contents);
$compile(scope.contents)(scope);
});
},
};
}]);
Then we write HTML:
<div ng-controller="main">
<div scope-inherit contents="test.html"></div>
</div>
where there is a test.html:
<button ng-click="sayHi()">speak</button>
Then click on the "speak" button, it will pop up the alert dialog with "hello world"
To do what you want, you need to use a template (either as a string or a templateUrl). If angularjs would work how you expect it in this case then a lot of the angular directives wouldn't work right (such as ng-show, ng-click, etc).
So to work how you want it, change your html to this:
<script type="text/ng-template" id="zzz.html">
<button ng-click="sayHi()">Hi 2</button>
</script>
<div ng-controller="xxx">
<button ng-click="sayHi()">Hi 1</button>
<div yyy></div>
</div>
And update your directive definition to use a templateUrl (or you can provide the string as a template property)
app
.controller('xxx', function($scope) {
$scope.sayHi = function() {
console.error('Error: Scope in xxx', new Date());
};
})
.directive('yyy', function() {
return {
scope: {},
templateUrl: 'zzz.html',
link: function(scope, elem, attrs) {
scope.sayHi = function() {
console.log('Hello World in zzz', new Date());
};
}
};
});
Here's a plunker with this code: http://plnkr.co/edit/nDathkanbULyHHzuI2Rf?p=preview
Update to use multiple templates
Your latest comment was a question about what if you wanted to use different templates on the same page. In that case we can use ng-include.
html:
<div yyy contents="template1.html"></div>
<div yyy contents="template2.html"></div>
<div yyy contents="template3.html"></div>
js:
app
.controller('xxx', ...)
.directive('yyy', function() {
return {
scope: {
theTemplateUrl: '#contents'
},
template: '<ng-include src="theTemplateUrl"></ng-include>',
link: function(scope, elem, attrs) {
scope.sayHi = function() {
console.log('Hello World in yyy', new Date());
};
}
};
});
The benefit of using ng-include is that this is already built into angularjs and is well tested. Plus it supports loading template either inline in a script tag or from an actual url or even pre-loaded into the angular module cache.
And again, here is a plunker with a working sample: http://plnkr.co/edit/uaC4Vcs3IgirChSOrfSL?p=preview