scope change in a directive not a apply in view - angularjs

I have a directive in a template.html, included by a ng-include, in this directive I change the scope , but it is not change in my view
Here is my html
<div ng-controller="myCtrl">
<div id="modal">
<div ng-show="showDIv">Somthing to controll</div>
</div>
<div ng-include src="template.html">
</div>
Here is my template
<a ng-support></a>
And here is my directive
app.directive('ngSupport', function(){
return {
restrict: 'A',
link: function(scope, elem, attr, ctrl) {
elem.bind('click', function(e) {
$("#modal").dialog({height:518,width:900,modal:true });
scope.showDiv = true;
scope.$apply();
});
}
};
});
When i change the scope in the directive it is not apply in the view, anyone could help please ?

ng-include creates a new scope so scope.showDiv only affects the local scope.
Depending on how you want to structure your application, you could try accessing scope.$parent.showDiv instead, but it is not really future proof as it will depend on the HTML nesting.
A better solution would be to have the showDiv property stored inside an object in the parent scope. For example scope.ui = {}, this way, when you set scope.ui.showDiv = true in your directive, it will look up the parent scope automatically (using prototype inheritance), instead of adding the property to the local scope.
Finally, another solution would be to refactor your code to make it less complex: I think using a ng-include just for adding one element is an overkill, you could put directly <a ng-support></a> inside your html, which would avoid the problem you have with an intermediary scope being generated.

Another option is to broadcast an event, and watch for it in the controller. Something like this.
app.directive('ngSupport', function($rootScope){
return {
restrict: 'A',
link: function(scope, elem, attr, ctrl) {
elem.bind('click', function(e) {
$("#modal").dialog({height:518,width:900,modal:true });
$rootScope.$broadcast('event:modal-clicked');
});
}
};
});
With this in your controller.
$scope.$on('event:modal-clicked', function() {
$scope.showDiv = true;
});

Related

pass data from controller to directive's link?

In my controller :
myApp.controller('homeCtrl', function($scope, $rootScope, $state, 'red';
$rootScope.$on('new_story', function(event, data) {
$scope.cardObj = {key:'value'};
});
});
In my HTML :
<div clickmeee ></div>
<div id="feedContainer" card='{{cardObj}}'> </div>
In my directive :
myApp.directive('clickmeee', function($compile, $rootScope) {
return {
restrict: 'A',
scope: {
card: '#'
},
link: function(scope, element, attrs) {
element.bind('click', function() {   
scope.$watch('card', function(newVal, oldVal) {
alert(scope.card);
});       
});
}
};
});
How do I pass data from controller to this directive. I compile some html and prepend it to the div. All of that is sorted out but I need some data from object I am trying to pass.
Any help??
There are several problems in your code:
you define a scope attribute named 'card', but you use cardObj instead
you use a watch that is completely unnecessary. And worse: you create a new watch every time the element is clicked
you don't define any card attribute on your clickmeee element. Instead, you're placing it on another element, on which the directive is not applied
you're passing the attribute with '#'. That works, but the directive will receive a string, containing the JSONified object, rather than the object itself
you're not showming us where you emit an event that will initialize cardObj in the controller scope
Here is a plunkr showing a working version of your code.
Also, note that using bind('click') is a bad idea. You'd better have a template in your directive and use ng-click in the template, or simply not use a directive at all and just use ng-click directly on the div element.
Bad news. You are doing it wrong all the ways.
Firstly
card='{{cardObj}}' >
this one should be put in the
<div clickmeee ></div>
So you can take it as binded scope variable in your directive registration
Secondly
If you managed to use '#' syntax
card: '#'
it will turn your input to string, not a binded scope. Use '=' instead.
In the end
You dont need to use watch here:
scope.$watch('card', function(newVal, oldVal) {
alert(newVal);
});
since scope.card is binded via '=' connector. Just simple use alert(scope.card). (Need to warn you that alert an object is not a good idea)
I have tried your code here: plunker. Changed a litte bit by using cardObj as string for easier presentation. Does it match your work?
You should watch the card object:
myApp.directive('clickmeee', function() {
return {
restrict: 'A',
scope: {
card: '#'
},
link: function(scope, element, attrs) {
scope.$watch('card', function(value) {
console.log(value);
});
}
};
});
And:
<div clickmeee id="feedContainer" card='{{cardObj}}'> </div>
Whenever the controller changes the cardObj, the directive's watch on card is triggered:
$scope.$apply(function() {
$scope.cardObj = "test";
}

How to access parent's controller function from within a custom directive using *parent's* ControllerAs?

I'm in need of building a transformation directive that transforms custom directives into html.
Input like: <link text="hello world"></link>
should output to: <a class="someclass" ng-click="linkClicked('hello world')"></a>
linkClicked should be called on the parent controller of the directive.
It would have been very easy if I was the one responsible for the html holding the 'link' directive (using isolated scope), but I'm not. It's an as-is input and I have to figure a way to still do it.
There are countless examples on how to do similar bindings using the default scope of the directive, but I'm writing my controllers using John Papa's recommendations with controllerAs, but don't want to create another instance on the controller in the directive.
This is what I have reached so far:
(function () {
'use strict';
angular
.module('app')
.directive('link', link);
link.$inject = ['$compile'];
function link($compile) {
return {
restrict: 'E',
replace: true,
template: '<a class="someclass"></a>',
terminal: true,
priority: 1000,
link: function (scope, element, attributes) {
element.removeAttr('link'); // Remove the attribute to avoid indefinite loop.
element.attr('ng-click', 'linkClicked(\'' + attributes.text + '\')');
$compile(element)(scope);
},
};
}
})();
$scope.linkClicked = function(text){...} in the parent controller works.
element.attr('ng-click', 'abc.linkClicked(..)') in the directive (where the parent's controllerAs is abc) - also works.
The problem is I don't know which controller will use my directive and can't hard-code the 'abc' name in it.
What do you suggest I should be doing?
It's difficult to understand from your question all the constraints that you are facing, but if the only HTML you get is:
<link text="some text">
and you need to generate a call to some function, then the function must either be:
assumed by the directive, or
conveyed to the directive
#1 is problematic because the user of the directive now needs to understand its internals. Still, it's possible if you assume that a function name is linkClicked (or whatever you want to call it), and the user of your directive would have to take special care to alias the function he really needs (could be done with "controllerAs" as well):
<div ng-controller="FooCtrl as foo" ng-init="linkClicked = foo.actualFunctionOfFoo">
...
<link text="some text">
...
</div>
app.directive("link", function($compile){
return {
transclude: "element", // remove the entire element
link: function(scope, element, attrs, ctrl){
var template = '<a class="someclass" ng-click="linkClicked(\'' +
attrs.text +
'\')">link</a>';
$compile(template)(scope, function(clone){
element.after(clone);
});
}
};
});
Demo
#2 is typically achieved via attributes, which isn't possible in your case. But you could also create a sort of "proxy" directive - let's call it onLinkClick - that could execute whatever expression you need:
<div ng-controller="FooCtrl as foo"
on-link-click="foo.actualFunctionOfFoo($data)">
...
<link text="some text">
...
</div>
The link directive now needs to require: "onLinkClick":
app.directive("link", function($compile){
return {
transclude: "element", // remove the entire element
scope: true,
require: "?^onLinkClick",
link: function(scope, element, attrs, ctrl){
if (!ctrl) return;
var template = '<a class="someclass" ng-click="localClick()">link</a>';
scope.localClick = function(){
ctrl.externalFn(attrs.text);
};
$compile(template)(scope, function(clone){
element.after(clone);
});
}
};
});
app.directive("onLinkClick", function($parse){
return {
restrict: "A",
controller: function($scope, $attrs){
var ctrl = this;
var expr = $parse($attrs.onLinkClick);
ctrl.externalFn = function(data){
expr($scope, {$data: data});
};
},
};
});
Demo
Notice that having a link directive would also execute on <link> inside <head>. So, make attempts to detect it and skip everything. For the demo purposes, I used a directive called blink to avoid this issue.

AngularJS $watch controller variable from a directive with scope

From the directive, I want to track changes to a controller variable using $watch.
I have created this jsfiddle. (https://jsfiddle.net/hqz1seqw/7/)
When the page loads, the controller and both directives $watch function gets called but when I change the radio buttons, only the controllers and dir-two $watch function gets called. Why isnt dir-ones $watch function being called?
I want both the directives $watch to fire however, I can only get one of them to (i.e. dir-two). Not sure what I need to change. Does it have something to do with isolated scope? Is there a better way of doing this?
AngularJS Code:
var mod = angular.module("myApp", []);
//Controller
mod.controller("myCtrl", function($scope){
$scope.tempformat = "C";
$scope.one="25 - dir-one";
$scope.$watch('tempformat', function(nv){
alert("nv from controller");
});
$scope.two="35 - dir-two";
});
//dir-one directive
mod.directive("dirOne", function(){
return{
restrict: 'E',
template: "<p>{{info}}</p>",
scope: {info: '='
},
link: function (scope, element, attr) {
scope.$watch('tempformat', function(nv){
alert("nv from directive-one");
if(scope.tempformat === "C"){
element.find("p").append("C");
}
else if(scope.tempformat === "F"){
element.find("p").append("F");
}
});
}
}});
//dir-two directive
mod.directive("dirTwo", function($window){
return{
restrict: "EA",
template: "<p></p>",
link: function (scope, element, attr) {
scope.$watch('tempformat', function(nv){
alert("nv from directive-two");
if(scope.tempformat === "C"){
element.find("p").append("C");
}
else if(scope.tempformat === "F"){
element.find("p").append("F");
}
});
}
}
});
HTML Code:
<div ng-app="myApp" ng-controller="myCtrl">
<h2>Temperature</h2>
<input type="radio" ng-model="tempformat" value="C"/> Celcius
<input type="radio" ng-model="tempformat" value="F"/> Farenheit
<dir-one info="one"></dir-one>
<dir-two info="two"></dir-two>
</div>
Does it have something to do with isolated scope?
The problem is the fact that dir-one separates its scope from the parent. There are some alternatives that can be done in this situation such as:
scope.$watch('$parent.tempformat', function(nv){ //...
which will look to the parent for the specified content.
Another alternative is to bind to the directive itself:
scope: {
info: '=',
tempformat: '='
},
and then in the html:
<dir-one info="one" tempformat="tempformat"></dir-one>
see: the documentation for more information. Particularly the Isolating the Scope of a Directive area.
Is there a better way of doing this?
In general isolate scopes help construct reusable components (as noted in the documentation) so if this is something that is being attempted (from the content noted in the answer) then I would support something along the lines of the second option where you can specify that watch content on the directive itself and consider that the "better" way of doing this.
From my experience, and this is solely my own preference, I would bind it to the directive since I usually isolate my scope(s) for a reason.

Directive does not have access to controller function (AngularJS)

When I declare a directive to use that accesses a controller function, it cannot find the controller.
Here is my directive declared in app.js:
app.directive("delete", function() {
return {
restrict: 'A',
link: function(scope, elem, attr, ctrl) {
elem.bind('click', function(e) {
alertCtrl.alert();
});
}
}
});
Here is my controller:
app.controller('AlertController', function() {
this.alert = function() {
alert('Ahah!');
}
});
And here is my HTML:
<div ng-controller="AlertController as alertCtrl">
<div ng-repeat="i in [1,2,3]">
<img src='image.png' delete />
</div>
</div>
If I click on the image, I get no alert and the console says that alertCtrl is not defined. How come alertCtrl is not defined when you click on the image with the delete directive on it?
If I change the controller to have $scope.alert = function()... it works fine. But I do not want this.
Also, is this the proper way to handle such a situation? If not, what is the best practice?
You need to call, it on the scope. scope has the property alertCtrl which is your controller instance. Your controller alias (alertCtrl) is available when you bind it on the html since scope is implicit there. but when you do it in the javascript i.e in the directive, or another controller you would need to get the controller instance (defined as alias) from the scope as a property.
scope.alertCtrl.alert();
Plnkr

How can I pass a model into custom directive

My goal is to pass the projectName model from my MainController to my custom contenteditable directive.
I have the following controller:
app.controller("MainController", function($scope){
$scope.projectName = "Hot Air Balloon";
});
Here is how I'm calling the directive:
<div class="column" ng-controller="MainController">
<h2 contenteditable name="myWidget" ng-model="projectName" strip-br="true"></h2>
<p>{{projectName}}</p>
</div>
I've gotten the contenteditable directive working by following this tutorial: https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
If I understand the docs correctly then Angular is not going to use the model I want it to. Instead its going to create a new model w/ scope local to the contenteditable directive. I know that I can add an isolate scope to the directive but I don't know how to use the model passed to the isolate scope within the link function.
I have tried something like the following which didn't work ...
<h2 contenteditable item="projectName"></h2>
--- directive code ---
scope: {
item: '=item'
}
link: function(){
...
item.$setViewValue(...)
...
}
--- my original directive call --
<div class="column" ng-controller="MainController">
<h2 contenteditable name="myWidget" ng-model="projectName" strip-br="true"></h2>
<p>{{projectName}}</p>
</div>
--- my original controller and directive ---
app.controller("MainController", function($scope){
$scope.projectName = "LifeSeeds";
});
app.directive('contenteditable', function(){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel){
console.log(ngModel);
if(!ngModel) return;
console.log(ngModel.$viewValue);
ngModel.$render = function(){
element.html(ngModel.$viewValue || '');
};
element.on('blur keyup change', function(){
scope.$apply(read);
});
read();
function read(){
var html = element.html();
if(attrs.stripBr && html == '<br>'){
html = '';
}
ngModel.$setViewValue(html);
}
}
};
});
You can use ng-model with your own directive. To make sure it is included, you can use the attribute require like this:
app.directive("myDirective", function(){
return {
require:"ngModel",
link: function(scope, element, attr, ngModel){
console.log(ngModel);
}
}
});
Then, you can code whatever behavior your want of ng-model within your directive.
Working solution: http://plnkr.co/edit/Lu1ZG9Lpx2sl8CYe8FCx?p=preview
I mentioned that I tried using an isolate scope in my original post.
<h2 contenteditable item="projectName"></h2>
This was actually the correct approach so ignore my full original example using the model argument of the directive's link function.
link: function(scope, element, attrs, ngModel)
The reason the isolate scope approach did not work was because $scope.projectName stored a primitive instead of an object. I did not understand some javascript basics. Mainly, I did not know that primitive types were passed to functions by value.
Primitives are passed by value in javascript. Consequently, changes made to primitive values within a function do NOT change the value of the variable passed to the function.
function changeX(x){
x = 5;
}
x = 4;
changeX(x);
console.log(x) // will log 4 ... Not 5
However, objects passed to functions in javascript are passed by reference so modifications to them within the function WILL be made to the variable passed into the function.
My problem was in how I declared the scope within the MainController.
I had:
$scope.projectName = "LifeSeeds";
That's a primitive. When I passed projectName to the directive I was passing a primitive.
<h2 contenteditable item="projectName"></h2>
Thus, changes to the editable element were being made to the value within the directive but not to the value stored in the MainController's scope. The solution is to store the value within an object in the MainController's scope.
// correct
$scope.project = {
html: "Editable Content"
};
// wrong
$scope.projectName = "Editable Content"

Resources