AngularJS ng-click not firing after compile attempt - javascript

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>

Related

Why won't $compile work with service using a directive?

Please have a look at this example, since it is the best way to explain the problem.
In this example if you click the directive link, it does not compile the template, but instead displays it as "{{1+1}}".
On the other hand if you click the "Simple link" it compiles the template and displays "2" instead.
angular.module('myApp', [])
.provider('$popup', function() {
var service = {};
this.$get = ['$compile', '$rootScope', function($compile, $rootScope) {
var template = $('<div>{{1+1}}</div>');
service.go = function() {
$(document.body).append(template);
$compile(template)($rootScope);
}
return service;
}];
})
.directive('popupLink', ['$popup', function($popup) {
return {
restrict: 'A',
scope: {},
link: function link(scope, element, attrs) {
element.click(function() {
$popup.go();
return false;
});
}
};
}])
.controller('mainCtrl', ['$scope', '$popup', function($scope, $popup) {
$scope.go = function() {
$popup.go();
};
}])
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
<div ng-app="myApp" ng-controller="mainCtrl">
<a ng-href="/test" popup-link>Directive link</a>
Simple link
</div>
My question is why isn't the template compiling with the directive? (but it does in the controller)
And how do I fix it so that it compiles in the directive also?
P.S. Here is the jsbin link in case you want to play around with the code:
http://jsbin.com/vuzutipedu/edit?html,js,output
The directive needs to do scope.$apply():
link: function link(scope, element, attrs) {
element.click(function() {
$popup.go();
//ADD apply
scope.$apply();
return false;
});
The click event handler executes outside the AngularJS framework. A framework digest cycle needs to be performed to execute the watcher for the {{1+1}} interpolation.
It works with the ng-click directive because that directive includes scope.$apply.
For more information, see
AngularJS Developer Guide - Integration with the browser event loop
DEMO
angular.module('myApp', [])
.provider('$popup', function() {
var service = {};
this.$get = ['$compile', '$rootScope', function($compile, $rootScope) {
var template = $('<div>{{1+1}}</div>');
service.go = function() {
$(document.body).append(template);
$compile(template)($rootScope);
}
return service;
}];
})
.directive('popupLink', ['$popup', function($popup) {
return {
restrict: 'A',
scope: {},
link: function link(scope, element, attrs) {
element.click(function() {
$popup.go();
//ADD apply
scope.$apply();
return false;
});
}
};
}])
.controller('mainCtrl', ['$scope', '$popup', function($scope, $popup) {
$scope.go = function() {
$popup.go();
};
}])
<script src="//unpkg.com/jquery"></script>
<script src="//unpkg.com/angular/angular.js"></script>
<div ng-app="myApp" ng-controller="mainCtrl">
<a ng-href="/test" popup-link>Directive link</a>
Simple link
</div>
Try this in $get, instead of $compile(template)($rootScope)
$compile(angular.element(template))(angular.element(template).scope());
Let me know if it works

How to call the controller method after directive render using $timeout?

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.

Passing data from custom directive into parent controller

I have created custom angular directive. For an example:
Custom directive:
var app=angular.module('app',[]);
app.directive('customDirective',function(){
return{
restrict:A,
controller:customDirectiveController,
scope:{
someArray:"="
}
}
})
Custom directive controller:
app.controller('customDirectiveController',function(scope){
scope.someArray=[];
scope.someArray.push(1);
scope.someArray.push(2);
scope.someArray.push(3);
});
Parent controller:
app.controller('parentCtrl',function($scope){
$scope.result=[];
});
HTML:
<div data-ng-controller="parentCtrl">
<div data-custom-directive="result">
</div>
How can I get value of this someArray from custom directive into Parent controller( result variable should in Parent controller be same as someArray from custom directive controller)?
Here is jsfiddle http://jsfiddle.net/mehmedju/RmDuw/302/
Thanks
You can apply a '$watch' on the array like this:
In the controller:
app.controller('MainCtrl', function($scope) {
$scope.someArray = [];
})
In the HTML:
<div custom-directive arr="someArray">
</div>
In the directive:
app.directive('customDirective', function(){
return {
scope: {
arr: '='
}
}
link: function(scope, element, attrs) {
scope.$watch('arr', function(newVal, oldVal) {
//do your array manipulation here
}
}
})
Alternatively, if you just want to send data back, here's the method:
In the controller, create a function which will accept the value returned from the directive, example:
app.controller('MainCtrl', function(){
$scope.watchVal = function(val) {
//do array manipulation
$scope.apply(); //to update the scope
}
})
In the HTML:
<div custom-directive data-method="watchVal">
</div>
In the directive:
app.directive('customDirective', function(){
return {
scope: {
sendVal: '&method'
},
link: function(scope, element, attrs){
scope.updateVal = function(){
var func = scope.sendVal();
func(scope.someArray);
}
}
}
})
Let's just say you are using tempController
So your code should be
app.controller('tempController', function($scope) {
scope.someArray = []
});
Html Code for this is
<div ng-controller="tempController">
<div custom-directive some-array="someArray">
</div>
You have here a very interesting article about watchers
http://teropa.info/blog/2014/01/26/the-three-watch-depths-of-angularjs.html
You need use a watchCollection
And, If you are playing creating the array and change the reference inside the directive you can lost the reference inside the watcher. This mean don't create o change the array reference inside.
Another interesting way is use ngModel
http://jsfiddle.net/ta66J/
var app = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.formVals = {
dirVals: [
{val: 'one'},
{val: 'two'}
]
};
}
app.directive('dir', function($compile) {
return {
restrict: 'E',
compile: function(element, attrs) {
var html = "<input id='inputId' type='text' ng-model='" + attrs.dirModel + "' />";
element.replaceWith(html);
return function(scope, element, attrs, ngModel) {
$compile(angular.element(element))(scope);
};
},
};
});
The jsfidler is from this interesting thread:
https://groups.google.com/forum/#!topic/angular/QgcRBpjiHAQ
Here is the fixed issue.
<div custom-directive="" data-some-array="result"></div> {{result}}
[http://jsfiddle.net/mehmedju/RmDuw/304/][1]

Angular ng show does not update while the scope does

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'};
}]);

AngularJS How: Scope hierarchy for Directive

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

Categories

Resources