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'};
}]);
Related
I need to call one function after directive render.
Actually i tried using $timeout function. But it's not working.
HTML Code:
<div ng-app='myApp' ng-controller='module-menu-controller'>
<layout-render></layout-render>
<div after-grid-render="getGridObject()"></div>
</div>
JS Code:
var app = angular.module("myApp", []);
app.controller("module-menu-controller", function($scope, $compile) {
$scope.getGridObject = function() {
alert("After render");
};
});
app.directive("layoutRender", function() {
return {
restrict : "E",
template : "<h1>Testing</h1>"
};
});
app.directive('afterGridRender', ['$timeout', function ($timeout) {
var def = {
restrict: 'A',
terminal: true,
transclude: false,
link: function (scope, element, attrs) {
$timeout(scope.$eval(attrs.getGridObject),0); //Calling a scoped method
}
};
return def;
}]);
JS Fiddle Link: https://jsfiddle.net/bagya1985/k64fyy22/1/
Here's a working fiddle.
Just have an additional attribute on the directive with the function:
HTML:
<div after-grid-render fnc="getGridObject()"></div>
Directive:
$timeout(scope.$eval(attrs.fnc),0);
Try this? Simple and clear
HTML
<div ng-app='myApp' ng-controller='module-menu-controller'>
<grid after-grid-render="getGridObject"></grid>
</div>
JS
var app = angular.module("myApp", []);
app.controller("module-menu-controller", function($scope) {
$scope.getGridObject = function() {
alert("After render");
};
});
app.directive("grid", function($timeout) {
return {
restrict : "E",
template : "<h1>Testing</h1>",
scope:{
afterGridRender:'='
},
link: function (scope, element, attrs) {
$timeout(scope.afterGridRender(),0); //Calling a scoped method
}
};
});
JSFiddle: https://jsfiddle.net/6o62kx3e/
Update
Do you mean you want it to be an attribute?
How about this one: https://jsfiddle.net/rt747rkk/
HTML
<div ng-app='myApp' ng-controller='module-menu-controller'>
<my-directive a='5' after-grid-render="getGridObject"></my-directive>
</div>
<script type="text/ng-template" id="myDirectiveTemplate.html">
<div> {{a}} {{b}} </div>
</script>
JS
var app = angular.module("myApp", []);
app.controller("module-menu-controller", function($scope) {
$scope.getGridObject = function() {
alert("After render");
};
});
app.directive('myDirective', function () {
return {
restrict: 'E',
replace: true,
templateUrl:"myDirectiveTemplate.html",
scope:{
a:'='
},
link: function (scope, element, attrs) {
scope.b=123;
scope.gridObject="my grid object here";
}
};
});
app.directive('afterGridRender', ['$timeout', function ($timeout) {
var def = {
restrict: 'A',
transclude: false,
link: function (scope, element, attrs) {
$timeout(function(){
scope.getGridObject();
alert(scope.$$childHead.gridObject);
});
}
};
return def;
}]);
I'm not really sure what you want to do.
If you want the attribute directive to access the scope of the element (as shown in the second alert box in the example), I don't think there's an elegant way. One way is to use scope.$$childHead, it works but variables prefixed with $$ are angular internal variables and we should not use them generally speaking.
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 was just reading here about accessing one directive's controller from within another directive via the require option:
http://jasonmore.net/angular-js-directives-difference-controller-link/
The directive droppable and dashboard declarations in on my view - on two different divs:
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-lg-12" data-droppable drop="handleDrop">
<div id="dash" dashboard="dashboardOptions" class="dashboard-container"></div>
</div>
</div>
However I can't seem to get it to work. My dashboardCtrl param below is NULL.
Here in my droppable directive, I use the REQUIRE option:
.directive('droppable', function () {
return {
scope: {
drop: '&',
},
//****************** dashboard directive is optionally requested ************
require: '?dashboard',
link: function (scope, element, attributes, dashboardCtrl) {
el.addEventListener('drop', function (e) {
if (e.preventDefault) { e.preventDefault(); }
this.classList.remove('over');
var item = document.getElementById(e.dataTransfer.getData('Text'));
this.appendChild(item.cloneNode(true));
// *** CALL INTO THE dashboardCtrl controller ***
dashboardCtrl.addWidgetInternal();
return false;
}, false);
}
}
});
and the dashboard directive :
angular.module('ui.dashboard')
.directive('dashboard', ['WidgetModel', 'WidgetDefCollection', '$modal', 'DashboardState', '$log', function (WidgetModel, WidgetDefCollection, $modal, DashboardState, $log) {
return {
restrict: 'A',
templateUrl: function (element, attr) {
return attr.templateUrl ? attr.templateUrl : 'app/shared/template/dashboard.html';
},
scope: true,
controller: ['$scope', '$attrs', function (scope, attrs) {
// ommitted for brevity
}],
link: function (scope) {
scope.addWidgetInternal = function (event, widgetDef) {
event.preventDefault();
scope.addWidget(widgetDef);
};
};
}
}]);
However, my dashboardCtrl parameter is NULL. Please help me to figure out how to use require.
I actually need to call the addWidget() function, which is within the link option; but I suppose I can copy or move that into the controller option.
thank you !
Bob
Here is an example of "parent" directive dashboard requiring droppable, and communication between the two making use of require and passing dashboardCtrl
Here is a good article to see directive to directive communication
Fiddle example also built from your previous question
JSFiddle
app.directive('droppable', [function () {
return {
restrict: 'A',
require: 'dashboard',
link: function (scope, elem, attrs, dashboardCtrl) {
dashboardCtrl.controllerSpecificFunction('hello from child directive!');
scope.addWidgetInternal = function(message) {
console.log(message);
}
}
}
}]);
app.directive('dashboard', [function () {
return {
restrict: 'A',
controller: function ($scope) {
$scope.handleDrop = function(message) {
$scope.addWidgetInternal(message)
}
this.controllerSpecificFunction = function(message) {
console.log(message);
}
}
}
}]);
Edit
Based on discussion, here is a solution for what I currently understand the problem to be
Parent directive dashboard optionally requires child directive droppable and there needs to be communication between the two
<div dashboard>
<button id="dash" droppable ng-click="handleDrop($event)">Handle Drop</button>
</div>
app.directive('droppable', [function () {
return {
restrict: 'A',
require: '^?dashboard',
link: function (scope, elem, attrs, dashboardCtrl) {
scope.handleDrop = function($event) {
dashboardCtrl.addWidgetInternal($event);
}
}
}
}]);
app.directive('dashboard', [function () {
return {
restrict: 'A',
controller: function ($scope) {
this.addWidgetInternal = function($event) {
console.log($event);
}
}
}
}]);
Updated JSFiddle