Angular - Can't access controller methods after compiling directive - javascript

I am heavily modifying a WYSIWYG js plugin, and am inserting my own custom elements into it.
To insert the custom elements, I am using directives so that they can be easily maintained if I need to make a change. Here is an example of my code so far:
The initial load of the wysiwyg editor:
<div ng-controller="wyswiygCtrl">
<textarea wysiwyg-editor ng-model="content"></textarea>
</div>
Here is an example of a custom element (directive) I am inserting into the wysiwyg content:
<wysiwyg-element class="custom_element" name="awesome" type="checkbox" checked="true"></wysiwyg-element>
I am using the following code within the directive's initialisation, to compile any custom elements (directives) within it:
var e = angular.element(wysiwygEditor.get(0).innerHTML);
$compile(e.contents())(scope);
wysiwygEditor.html(e);
It compiles the directives just as I need it to, but here comes the tricky part. I need to be able to call a function within the 'wysiwygCtrl' from OUTSIDE angular. I am able to do this before compiling, but for some reason, after using angular's compile function, I can't access the scope on the elements.
Here is the code which works before a $compile:
angular.element($('.custom_element')).scope().wysiwygModal();
angular.element($('.custom_element')).scope().$apply();
I get the following error after trying to call the wysiwygModal function after a $compile:
Uncaught TypeError: Object #<Object> has no method 'wysiwygModal'
What am I doing wrong?

I was able to access the scope() and its variables on a compiled element. Could you edit this plunkr or create your own if this doesn't help?
http://plnkr.co/edit/kvbvKXeVjEhmeE7257ly?p=preview
script.js
var myApp = angular.module('myApp', []);
myApp.directive('myEditor', function () {
return {
restrict: 'E',
scope: {
},
template:
'<div>' +
'<h3>Raw HTML</h3>' +
'<textarea rows=5 cols=40 ng-model="text"></textarea>' +
'</div>' +
'<hr />' +
'<div>' +
'<h3>Compiled HTML</h3>' +
'<div my-preview></div>' +
'</div>' +
'<hr />' +
'<div>' +
'<h3>Messages generated during compilation</h3>' +
'<div ng-repeat="message in messages">[{{message}}]</div>' +
'</div>' +
'</div>',
controller: function ($scope) {
$scope.messages = [];
this.getText = function () {
return $scope.text;
};
this.addMessage = function (message) {
$scope.messages.push(message);
};
this.clearMessages = function () {
$scope.messages.length = 0;
};
},
link: function (scope, element, attrs) {
scope.text = '<div ng-init="a = 2" class="my-selector">\n scope.a : [{{a}}]\n</div>';
}
};
});
myApp.directive('myPreview', function ($compile) {
return {
require: '^myEditor',
link: function (scope, element, attrs, myEditorController) {
scope.$watch(myEditorController.getText, function (newValue) {
if (newValue !== undefined) {
var e = angular.element('<div>' + newValue + '</div>');
$compile(e)(scope);
element.html(e);
myEditorController.addMessage(
'The value of "a" on the scope of the compiled element is: ' +
angular.element($('.my-selector')).scope().a)
}
});
}
};
});
index.html
<!DOCTYPE html>
<html>
<head>
<script data-require="jquery#*" data-semver="2.0.3" src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script data-require="angular.js#*" data-semver="1.2.0-rc3-nonmin" src="http://code.angularjs.org/1.2.0-rc.3/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<div ng-app="myApp">
<my-editor></my-editor>
</div>
</body>
</html>

Related

jQuery doesn't select the element with dynamic ID, how to do that?

I am having a problem when I pass the ID through a directive. I can't obtain the element using jQuery inside the Link function, and the element is using the correct dynamic ID coming as parameter:
The Directive:
(function(angular) {
var app = angular.module('pi.core');
app.directive('piSearch', function() {
return {
restrict: 'E',
transclude: true,
replace: true,
scope: {
idelement: '#'
},
link: function(scope, element, attrs, controller, transcludeFn) {
var idelement = scope.idelement;
console.log('idElement: ' + idelement);
console.log($('#' + idelement + ' .typeahead'));
},
template: '<div id="{{idelement}}"></div>'
};
});
})(angular);
var myApp = angular.module('piCore', []);
myApp.directive("piSearch", function() {
return {
restrict: 'E',
transclude: true,
replace: true,
scope: {
idelement: '#'
},
link: function(scope, element, attrs, controller, transcludeFn) {
var idelement = scope.idelement;
scope.elementSelected = $('#' + idelement + ' .typeahead');
console.log('idElement: ' + idelement);
console.log($('#' + idelement + ' .typeahead'));
},
template: '<div id="{{idelement}}"></div>'
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body ng-app="piCore">
<pi-search></pi-search>
{{$scope.elementSelected}}
</body>
Any hints? Thanks in advance for your help!
I'll refactor your link function, remember angular has their own life cycle, and you need to make sure that your template compiles when your model has a value, wrap your logic in a watch
app.directive('piSearch', function() {
return {
restrict: 'E',
transclude: true,
replace: true,
scope: {
idelement: '#'
},
link: function(scope, element, attrs, controller, transcludeFn) {
var idelement = scope.idelement;
scope.$watch('idelement',function(){
scope.elementSelected = $('#' + idelement + ' .typeahead');
console.log('idElement: ' + idelement);
console.log($('#' + idelement + ' .typeahead'));
});
},
template: '<div id="{{idelement}}"></div>'
};
});
try to use angular.element("#"+ idelement);
and also make sure this template: '<div id="{{idelement}}"></div>' is not generated multiple times
One thing you must know regarding dynamic ID's and elements in global:
1) First you must generate your dynamic HTML inside javascript
2) Print it, append or just insert somwhere in the DOM
3) Now to can access to that new element you must use document to read that new element like this:
document.getElementById('new-generic-id');
or
$(document).on('click touchstart select', '#new-generic-id', function(){});
I hope you understand.

variable is not acting independently when using multiple copies of same angular directive

I'm trying to create a directive with 2 radio button binding with update variable. Now I planned to use the same directive in once again. Now the problem is the scoped variable update is not acting independently.
Below is the mock example I have created,
var myApp = angular.module('myApp',['ngRoute']);
myApp.directive('myDirective', function () {
return {
restrict: 'A',
scope: false,
template: '<div><span>A new directive</span>'
+ '<input name="updated" type="radio" ng-model="con.isUpdate" ng-checked="con.isUpdate" />'
+ '<input name="updated" type="radio" ng-model="con.isUpdate" ng-checked="!con.isUpdate" />'
+ '</div>',
controller: controller,
controllerAs: 'con'
};
function controller ($scope) {
$scope.isUpdate = true;
}
});
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular-route.js"></script>
<script src="index.js"></script>
</head>
<body ng-app="myApp">
<div my-directive></div>
<br/>
<div my-directive></div>
</body>
</html>
Running the code snippet will give a clear picture of the problem.
The radio inputs have the same name so the browser is "grouping" them if you will and applying updates to the radios based on name.
An easy way to fix this is to pass the name to the directive to apply to the radios, or (as shown below) create different directives.
function Directive(name) {
return {
restrict: 'A',
scope: false,
template: '<div><span>A new directive</span>'
+ '<input name="' + name + '" type="radio" ng-model="con.isUpdate" ng-checked="con.isUpdate" />'
+ '<input name="' + name + '" type="radio" ng-model="con.isUpdate" ng-checked="!con.isUpdate" />'
+ '</div>',
controller: controller,
controllerAs: 'con'
};
function controller ($scope) {
$scope.isUpdate = true;
}
}
myApp.directive('myDirective', new Directive('updated'));
myApp.directive('myDirective2', new Directive('updated-alt'));

ng-click in directive not calling function defined in controller scope

Everything seems to render properly but my ng-click="check($event)" is not firing when I click on one of the four buttons. I even added an alert to the check($event) function but it's not coming up. Heres the codepen http://codepen.io/theMugician/pen/MKOzzV?editors=101
simonApp.directive('pads', ['lightIt', 'soundIt', function(lightIt,soundIt) {
var template = '';
template += '<button ng-click="check($event)" id=' + '{{index}} ' + 'class="pad pad-' + '{{color}}' + '">';
template += '<div class="ripple js-ripple">';
template += '<span class="ripple-circle ' + 'ripple-' + '{{color}}' + '"></span></div>';
template += '</button>';
return {
restrict: 'E',
replace: true,
scope: {
color: '#color',
index: '#index'
},
link: function(scope, element, attrs) {
scope.createRipple = function(e){
lightIt.createRipple(e, this);
}
//scope.$emit('index', scope.index);
/*
var index = parseInt(scope.index);
scope.playSound = function() {
soundIt(1);
}*/
console.log("scope.index: " + scope.index);
//console.log("scope.playSound: " + scope.playSound);
element.on('click', scope.createRipple);
//element.on('animationend', scope.endRipple);
},
template: template
};
}]);
Here is the check() function in the controller
$scope.check = function(e) {
alert("check works");
var id = e.currentTarget.id;
if($scope.init){
if(sequence[turn] === id){
turn++;
if(turn === sequence.length){
$scope.step++;
setTimeout(function(){
newRound();
}, 1000);
}
}else{
if($scope.isStrict){
setTimeout(function(){
alert('game over');
$scope.reset();
}, 300);
}else{
$scope.displaySeq(sequence);
}
}
}
}
You cannot call controller method from directive like that.
In case you need scope isolation you have to use expression binding ( '&' scope parameters) or turn off scope isolation all together ( remove scope key from directive definition )
See excellent video from John Lindquist for example of using scope expression binding.
var app = angular.module('app', [])
app.directive("pads", function() {
return {
restrict: "E",
scope: {
check: "&"
},
template: '<div class="button" ng-click="check({message: value})">click</div></div>',
link: function(scope) {
scope.value = 'message to controller'
}
};
});
app.controller('myCtrl', function() {
this.doCheck = function(message) {
alert(message) // alerts "message to controller"
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='app' ng-controller='myCtrl as ctrl'>
<pads check="ctrl.doCheck(message)"></pads>
</div>

angularjs - $destroy isn't called on custom compiled html

I made a directive that compiles a 'div' with an ng-include and ng-controller, and appends it to the element. It works fine, but for some reason the "$destroy" event isn't called on the controller's scope when it is destroyed.
Is what I'm doing correct?
This is the html:
<my-Directive tr-model="someBindedObject"></my-Directive>
And this is the JS:
angular.module('myModule').
directive('myDirective', function ($compile, controllerMapper) {
function compileTemplate(scope, element, attrs) {
var controller = controllerMapper.getController(scope.model);
innerHtml = '<div ng-include src="' + "'" + controller.templateUrl + "'" + '" ng-controller="' + controller.controllerName + '" ></div>';
var innerElement = $compile(innerHtml)(scope);
element.empty();
element.append(innerElement);
};
return {
scope: {
model: "=trModel"
},
link: compileTemplate
}
}).
controller('myController', function($scope){
$scope.$on('$destroy', function () {
//this is never called
});
});

Is the way of creating a directive scope the same in versions 1.3 and 2 of AngularJS?

I have the following directive that my application is using. I was under the impression that my application was working fine with AngularJS 1.3 but after a lot of changes including a move to the latest version, the removal of jQuery, and also the use of controller as then now this directive is giving me errors:
app.directive('pagedownAdmin', function ($compile, $timeout) {
var nextId = 0;
var converter = Markdown.getSanitizingConverter();
converter.hooks.chain("preBlockGamut", function (text, rbg) {
return text.replace(/^ {0,3}""" *\n((?:.*?\n)+?) {0,3}""" *$/gm, function (whole, inner) {
return "<blockquote>" + rbg(inner) + "</blockquote>\n";
});
});
return {
require: 'ngModel',
replace: true,
scope: {
modal: '=modal'
},
template: '<div class="pagedown-bootstrap-editor"></div>',
link: function (scope, iElement, attrs, ngModel) {
var editorUniqueId;
if (attrs.id == null) {
editorUniqueId = nextId++;
} else {
editorUniqueId = attrs.id;
}
var newElement = $compile(
'<div>' +
'<div class="wmd-panel">' +
'<div data-ng-hide="modal.wmdPreview == true" id="wmd-button-bar-' + editorUniqueId + '"></div>' +
'<textarea data-ng-hide="modal.wmdPreview == true" class="wmd-input" id="wmd-input-' + editorUniqueId + '">' +
'</textarea>' +
'</div>' +
'<div data-ng-show="modal.wmdPreview == true" id="wmd-preview-' + editorUniqueId + '" class="pagedownPreview wmd-panel wmd-preview">test div</div>' +
'</div>')(scope);
iElement.html(newElement);
var help = function () {
alert("There is no help");
}
var editor = new Markdown.Editor(converter, "-" + editorUniqueId, {
handler: help
});
var $wmdInput = iElement.find('#wmd-input-' + editorUniqueId);
var init = false;
editor.hooks.chain("onPreviewRefresh", function () {
var val = $wmdInput.val();
if (init && val !== ngModel.$modelValue) {
$timeout(function () {
scope.$apply(function () {
ngModel.$setViewValue(val);
ngModel.$render();
});
});
}
});
ngModel.$formatters.push(function (value) {
init = true;
$wmdInput.val(value);
// editor.refreshPreview();
return value;
});
editor.run();
}
}
});
Can someone explain to me what the following is doing:
scope: {
modal: '=modal'
},
and also the
)(scope);
Here is how I am calling this directive:
<textarea id="modal-data-text"
class="pagedown-admin wmd-preview-46"
data-modal="modal"
data-pagedown-admin
ng-model="home.modal.data.text"
ng-required="true"></textarea>
If anyone can see anything that may not work in 2 then I would much appreciate some help. In particular it seems that the following code returns null:
var $wmdInput = iElement.find('#wmd-input-' + editorUniqueId);
You dropped jQuery, so your code now relies on jQLite. Functions of element objects support less functionality when using jqLite. See the full details in the doc:
https://docs.angularjs.org/api/ng/function/angular.element
var $wmdInput = iElement.find('#wmd-input-' + editorUniqueId);
Under jqLite, the find function only support searching by tag names, ids will not work. You can use the following tricks from ( AngularJS: How to .find using jqLite? )
// find('#id')
angular.element(document.querySelector('#wmd-input-' + editorUniqueId))
$compile is a service that will compile a template and link it to a scope.
https://docs.angularjs.org/api/ng/service/$compile
scope: {
modal: '=modal'
}
allows you to define a isolated scope for the directive with some bindings to the scope in which the directive is declared. '=' is used for two-way data bindings. Other options are '# and &' for strings and functions.
https://docs.angularjs.org/guide/directive

Categories

Resources