Evaluating Custom Directive after jQuery replaceWith - angularjs

I have the following code:
<div ng-app="myApp" ng-controller="AngularCtrl">
Click here
</div>
<script>
jQuery("#click_btn").click(function(){
jQuery(this).replaceWith('<p>Hello {{student.name}}</p><div my-repeater></div>');
});
</script>​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​
Here is my angular code:
var myApp = angular.module('myApp',[]);
myApp.directive('myRepeater', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var myTemplate = "<div>{{rating}}</div>";
for(var i=0;i<scope.items.length;i++)
{
var myItem = scope.items[i];
var text = myTemplate.replace("{{rating}}",myItem.rating);
element.append(text);
}
}
};
});
function AngularCtrl($scope) {
$scope.student = {id: 1, name: 'John'};
$scope.items = [{id: 1, ratings: 10}, {id: 2, ratings: 20}];
}
Here, whenever i click on the button, the element is just getting replaced and not evaluated. I tried with "angular.bootstrap(document);" after document is ready.
But that just evaluates the angular objects. But still the custom directive "my-repeater" is not getting evaluated. Any help on how can i get this done?

First of all, I suppose this is test code, since angular has ng-repeat, which fits your needs.
There are several issues with your code:
1) You shouldn't use myTemplate.replace, but use the $compile service. Inject the $compile service into your directive (add as function param) and use it:
var text = $compile(myTemplate)(scope);
2) Items on the controller will not by accessible in your directive. Add it as a value to your my-repeater attribute:
<div my-repeater='{{items}}'>
In your directive you need to evaluate my-repeater:
var items = scope.$eval(attrs.myRepeater);
3) jQuery(this).replaceWith will not kickoff angular, since it it out of its scope. You need to do it manually by using scope.$apply. But is better to add the click event in the directive link function:
link: function(scope, element, attrs) {
element.on('click', function() {
...
scope.$apply();
});
Edit: Here is a working example.

Related

In a custom Angular directive, pass the innerHTML as an attribute to another directive

I am trying to take the innerHTML of an Angular directive, and pass that as an attribute of another directive. So, let's say that I have:
<js-code>This is some text</js-code>
my jsCode directive looks like this:
prettifyModule.directive('jsCode', function() {
return {
restrict: 'E',
compile: function($element, $scope) {
$scope.codeText = $element.html();
$element.replaceWith("<code-mirror model='codeText'></code-mirror>");
}
};
});
The goal being to pass a variable containing the string, "This is some text" as the model attribute of the code-mirror directive. For the most part, this seems to work. I can see in the elements that a directive appears that looks like:
<code-mirror model='codeText'></code-mirror>
However, the controller for the code-mirror directive does not, at that point, initialize.
If anyone could point out what I am doing wrong, or if there is a better way to do this entirely, it would be appreciated.
My limitations are:
I cannot alter the code-mirror directive.
I cannot statically manipulate the text that is being sent to the jsCode directive.
I have changed the directive code to
myApp.directive('myDirective', function($compile) {
return {
restrict: 'E',
link: function($scope, $element){
$scope.codeText = $element.html();
var template = "<second-directive model='codeText'></second-directive>";
var linkFn = $compile(template);
var content = linkFn($scope);
$element.replaceWith(content);
},
controller: function($scope) {
$scope.test = "Text from controller";
}
};
});
Here is the updated fiddle http://jsfiddle.net/xw7ms0xd/2
This is the first time, I used $compile service. Got the reference from here
http://odetocode.com/blogs/scott/archive/2014/05/07/using-compile-in-angular.aspx

AngularJS - Compile with ng-include bypasses internal controller in directive

I'd like to have a directive with multiple templates as in this SO
When using compile in a directive as in this jsfiddle
the the ng-include uses the external controller and the
and the internal controller is not available to the scope of the template
example
function someDirective(){
return {
scope:{
...
},
compile: function(element, attrs) {
var type = "extended"; //default
if(typeof attrs.type !== 'undefined')
type = attrs.type;
element.append('<div ng-include="\'myproj/views/templates/group/groups-' + type + '.html\'"></div>');
},
//templateUrl: 'myproj/views/templates/group/groups-sideMenu.html',
controller:function($scope, $attrs, $rootScope, UtilsSrvc){
// ... the template won't use this controller
}
}
}
how to fix this problem?
EDIT
After some headbang something got clearer
In this fiddle (by Alessandro Cifani) the script works either for Angular 1.0, Angular 1.1 and Angular 1.2
The problems start when trying to isolate the scope:
this fiddle only works with Angular <= 1.1, with Angular >= 1.2 is not working
Things change when an empty 'templateUrl' is added as shown in this fiddle: it starts to be compliant to all versions
???????????????
I would use a different approach:
1) yoy may use the $compile service
2) you should avoid to use ng-include in favor of custom directives.
Following an example of what I'm saying:
(function () {
'use strict';
angular.module('myApp', [])
.directive('user',
function user($compile, $window) {
return {
scope: {
role: '#',
name: '#'
},
restrict: 'EA',
link: function link(scope, elem) {
var roles = {
SUPERADMIN: '<button ng-click="doSomething()" class="btn">Do something</button>',
STUDENT: '<div class="alert alert-success">You are a student</div>',
OTHER: '<div>Guest users have no options</div>'
};
// Create HTML elements in according with the role (SUPERADMIN, STUDENT, OTHER)
var role = roles[scope.role] || roles.OTHER;
var html = '<div><h2>' + scope.name + '</h2>' + role + '</div>';
// Step 1: parse HTML into DOM element
var template = angular.element(html);
//console.log (html);
// Step 2: compile the template
var linkFn = $compile(template);
//console.log (linkFn);
// Step 3: link the compiled template with the scope.
var element = linkFn(scope);
console.log (element[0]);
// Step 4: Append to DOM (optional)
elem.append(element);
scope.doSomething = function doSomething() {
$window.alert('doSomething');
};
}
};
}
);
})();
USAGE:
<user name="fabio" role="SUPERADMIN"></user>
<user name="paolo" role="STUDENT"></user>
<user name="marco"></user>
NOTE: in the previous example you could replace my "SUPERADMIN", "STUDENT" and "OTHER" templates with your own custom directives, ie: , ,
and here a JSBin: https://jsbin.com/mumahobuwu/edit?html,js,output

Using angular directive to watch for change values with bootstrap-select

I am new to angular and after having researched for 2 days, I still haven't come up with a solution that will work yet.
I have a select item that will have its options updated and am also using bootstrap-select.js. I can get either or to work on their own (angular items updating dynamically as expected in a standard select list or the bootstrap-select item to work with static options). If someone could provide some guidance as to what I am doing wrong, it would be greatly appreciated! Here is my code:
HTML:
<div ng-app="app">
<div ng-controller="ctrl">
<selectpicker data-array="users" data-selected="info.selected"></selectpicker>
<button ng-click="add()">Add</button>
</div>
</div>
JS:
var app = angular.module('app', []);
app.controller('ctrl', ['$scope', function($scope)
{
$scope.info = {selected: 1};
$scope.users=[];
$scope.users.splice(0);
$scope.users = [{name: "Bob", id: "1"},{name:"Tom", id: "2"}];
$scope.add = function () {
$scope.users.push({name: "John", id: "3"});
};
}]);
app.directive('selectpicker', function($timeout)
{
return {
restrict: 'E',
replace:true,
scope: {
selected: '=',
array: '=',
class: '='
},
template: '<select class="selectpicker" multiple data-selected-text-format="count" ng-model="currentName" ng-options="user.name for user in array">' +
'</select>',
replace:true,
link: function(scope, el, attrs) {
$timeout(function () {
scope.$watch('array', function (newVal) {
console.log(scope.array);
var select = $(el).selectpicker();
select.change(function(evt) {
var val = $(el).selectpicker('val');
$scope.selected = val;
$scope.$apply();
});
}, true);
});
}
};
});
So when I click the Add button, I can see the scope.array value
updates from the console output, but the dropdown itself won't update. I've tried piecing together solutions from similar answers but nothing has yielded results so far.
According to the documentation of Bootstrap-select, you could use refresh() method to update the UI when an underlying select tag has been changed like this:
$(el).selectpicker('refresh');
But lets look further how to improve the directive:
the element variable el is already a jQuery object, no need to wrap it again as $(el).
a $timeout is become unnecessary after the UI refresh is properly handled.
move the change event binding out of $watch, otherwise the handler will be fired multiple times per a change.
$watchCollection is enough if the options will be only add/remove i.e. an individual option will not be changed.
The final result would look like this:
link: function(scope, el, attrs) {
var select = el.selectpicker();
select.change(function (evt) {
scope.selected = el.selectpicker('val');
scope.$apply();
});
scope.$watchCollection('array', function(newVal) {
console.log(scope.array);
el.selectpicker('refresh');
});
}
Example Plunker: http://plnkr.co/edit/Kt0V0UBuHaRYMjKbI5Ov?p=preview

Angular - function inside directive scope gets called when it shouldn't

Hi I am struggling with the following:
http://jsfiddle.net/uqZrB/9/
HTML
<div ng-controller="MyController">
<p>Button Clicked {{ClickCount}} Times </p>
<my-clicker on-click="ButtonClicked($event)">
</my-clicker>
</div>
JS
var MyApp = angular.module('MyApp',[]);
MyApp.directive('myClicker', function() {
return {
restrict: 'E',
scope: {
onClick: "="
},
link: function($scope, element, attrs) {
var button = angular.element("<button>Click Me</button>");
button.bind("mousedown", $scope.onClick);
element.append(button);
}
};
});
MyApp.controller("MyController", function ($scope) {
$scope.ButtonClicked = function($event) {
$scope.ClickCount++;
};
$scope.ClickCount = 0;
});
(using angular1.2 rc : https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular.js)
The custom directive "myClicker" should insert a button into the tag, and bind its mousedown event to a function supplied in the directive scope...
I.e. i can pass the a function from the controller, to execute when the button is clicked.
As you can see, when you run the fiddle, the bound event gets run 11 times, on load.... i.e. before the button has event been clicked.
Running its 11 times causes the "10 $digest() iterations reached. Aborting!" error.
Then, when I click the button I get "Cannot call method 'call' of undefined", as if the method was not declared in the scope.
Why does angular try and run the method on loading?
Why is the "onClick" method not available in the scope?
I think I am misunderstanding something about the directive's isolated scope.
Thanks in advance!
The onClick: "=" in your scope definition expects a two-way data-binding, use onClick: "&" to bind executable expressions into an isolate scope. http://docs.angularjs.org/guide/directive
please changes your code in controller as below
var MyApp = angular.module('MyApp',[]);
MyApp.directive('myClicker', function() {
return {
restrict: 'E',
scope: {
onClick: "&"
},
link: function($scope, element, attrs) {
var button = angular.element("<button>Click Me</button>");
button.bind("mousedown", $scope.onClick);
element.append(button);
}
};
});
MyApp.controller("MyController", function ($scope) {
$scope.ButtonClicked = function($event) {
$scope.ClickCount++;
};
$scope.ClickCount = 0;
});

AngualarJS Directives - Waiting for a directive template to finish rendering?

I'm trying to write a directive that has a template. The template is rendering some DOM elements I want to retrieve. However, when I try to retrieve my DOM elements in the linking function, the DOM elements are not found. If I add a window.setTimeout method before selecting the elements they are found. How can I wait for a template to finish rendering before trying to manipulate the DOM in the linking function?
Here is the directive code for what I'm trying to do:
module.directive('testLocationPicker', function() {
var linkFn = function(scope, element, attrs) {
console.log('in linking function');
window.setTimeout(function() {
var positions = $('.position');
console.log('number positions found: ' + positions.length);
positions.click(function(e) {
console.log('position clicked');
scope.$apply(function() {
scope.selectedPosition = $(e.currentTarget).html();
});
});
}, 500);
};
return {
link: linkFn,
restrict: 'E',
template: 'Choose a position: <div class="position" ng-repeat="position in positions">{{position}}</div>',
}
});
I have a JS Fiddle of what I'm trying to do: http://jsfiddle.net/bdicasa/XSFpu/42/
I would recommend doing something like this instead:
var module = angular.module('test', []);
module.controller('TestController', function($scope) {
$scope.positions = [
'Test Position 1',
'Test Position 2',
'Test Position 3'
];
$scope.selectedPosition = '';
$scope.handleClick = function (index) {
$scope.selectedPosition = $scope.positions[index];
}
});
module.directive('testLocationPicker', function() {
return {
restrict: 'E',
template: 'Choose a position: <div class="position" ng-repeat="position in positions" ng-click="handleClick($index)">{{position}}</div>',
}
});
Instead of trying to search through the dom and add a click event, just modify your template like this:
template: 'Choose a position: <div class="position" ng-repeat="position in positions" data-ng-click="positionClick($index)">{{position}}</div>',
And then create a positionClick function in the linking function:
var linkFn = function(scope, element, attrs) {
scope.positionClick = function(index){
scope.selectedPosition = index;
}
};
Working jsFiddle: http://jsfiddle.net/XSFpu/77/
The reason your method is not working is because the ng-repeat hasn't fired after the template has loaded. So it's loaded the directive in, and the link function has been hit, but the ng-repeat actually hasn't started repeating yet. This is why I'm suggesting moving some of your code around to accomdate that.

Resources