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

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

Related

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>

What Scope is being used in Angular's $mdDialog

I have been playing around with Angular Material and I wanted to create a $mdDialog that allows a user to enter in information that when saved, will update an object tied to a ng-repeat.
While trying to get this to work and trying out different parameters for mdDialog.show() I was confused about what scope is being used when/why.
This is the first implementation:
(function () {'use strict';
angular.
module('myApp', ['ngMaterial']).
controller('AppCtrl', AppCtrl);
function AppCtrl($mdDialog, $scope) {
$scope.lister = [{name:'Matt'},{name:'Steve'}];
$scope.showDialog = showDialog;
function showDialog(evt) {
$scope.obj = {name:'default'};
$mdDialog.show({
targetEvent: evt,
scope: $scope.$new(),
template:
'<md-dialog>' +
' <md-content><md-input-container>'+
' <label>Name</label>'+
' <input ng-model="obj.name">' +
' </md-input-container></md-content>' +
' <div class="md-actions">' +
' <md-button ng-click="close(obj)">' +
' Save' +
' </md-button>' +
' </div>' +
'</md-dialog>'
}).then(function(objs){$scope.lister.unshift(objs)});
}
$scope.close = function(objs){
$mdDialog.hide(objs);
}
}
}());
The behavior of the above code is mdDialog will open with "default" in the Name input field, but if I change the show() parameters to below (key difference is swapping out "scope:" for "controller:"):
function showDialog(evt) {
$scope.obj = {name:'default'};
$mdDialog.show({
targetEvent: evt,
controller: 'AppCtrl',
template:
'<md-dialog>' +
' <md-content><md-input-container>'+
' <label>Name</label>'+
' <input ng-model="obj.name">' +
' </md-input-container></md-content>' +
' <div class="md-actions">' +
' <md-button ng-click="close(obj)">' +
' Save' +
' </md-button>' +
' </div>' +
'</md-dialog>'
}).then(function(objs){$scope.lister.unshift(objs)});
}
The behavior of the second implementation is that the mdDialog will open with a blank for the Name input field.
This is a long setup for this question: Why does the dialog template recognize $scope.obj when "scope: $scope.$new()", but it is not recognized when "controller: 'AppCtrl'"? I thought both implementations are providing AppCtrl's scope to the dialog.
Dialog is always given an isolated scope
You can pass data to dialog from parent controller using dependency injection.
function AppController($scope, $mdDialog) {
var message='message from parent';
$scope.showDialog = showDialog;
$scope.items = [1, 2, 3];
function showDialog($event) {
var parentEl = angular.element(document.body);
$mdDialog.show({
parent: parentEl,
targetEvent: $event,
templateUrl:'templateFile.html',
locals: {
items: $scope.items
},
message:message
controller: DialogController
});
function DialogController($scope, $mdDialog, items,message) {
$scope.items = items;
$scope.message = message;
$scope.closeDialog = function() {
$mdDialog.hide();
}
}
}
In your first case, you are adding an object to isolated scope of your dialog using
$scope.obj = {name:'default'} and its available as obj.name on yr view.
In your second case, you are declaring controller for your dialog as 'AppCtrl', but you have not defined it anywhere inside your parent controller, so you are not getting anything on view. AppCtrl is not defined.
if you want to use calling scope; you can pass 'isolateScope' parameter like this:
$mdDialog.show({
....,
isolateScope: false
});

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

How to get an HTML element that called a function in AngularJS ?

I have an AngularJS directive, which needs to be appended after an HTML element which called it. The elements structure can have many nested buttons on different levels of DOM structure.
Right now a directive gets appended in the wrong place instead of a container element that contains the button, which called the append function.
It looks like this:
The text should be appended after a button, which was clicked.
Directive:
app.directive('recursiveFields', function ($compile, $http) {
return {
scope: {
field: '=field',
model: '=model'
},
restrict: 'E',
replace: true,
controller: "httpPostController",
template: '<div ng-repeat="nestedField in field.nestedFields"><div ng-show="{{!nestedField.isEntity && !nestedField.isEnum}}">' + '<p ng-show={{nestedField.isRequired}}>{{nestedField.name}}*: </p>' + '<p ng-show={{!nestedField.isRequired}}>{{nestedField.name}}: </p>' + '<input type="text" ng-model="model[nestedField.name]" ng-change="getCreateEntityAsText()"' + 'class="form-control" placeholder="{{parseClassName(nestedField.type)}}">' + '</div>' + '<div ng-show="{{nestedField.isEnum}}">' + '<p ng-show={{nestedField.isRequired}}>{{nestedField.name}}*: </p>' + '<p ng-show={{!nestedField.isRequired}}>{{nestedField.name}}: </p>' + '<select ng-model="model[nestedField.name]" ng-change="getCreateEntityAsText()" class="form-control">' + '<option></option>' + '<option ng-repeat="enumValue in nestedField.enumValues" label={{enumValue.name}}>{{enumValue.ordinal}}</option>' + '</select>' + '</div>' +
'<div ng-show="{{nestedField.restResourceName != null}}">' + '<accordion close-others="oneAtATime">' + '<accordion-group heading={{nestedField.name}} is-open="false">' + /*'<recursive-fields model="createEntityResource" field="field"></recursive-fields>'*/
'<button type="button" ng-click="appendDirective()">I should append a "recursiveFields" directive</button>' + '</accordion-group>' + '</accordion>' + '</div>' + '</div>',
link: function (scope, element, attrs) {
console.log("1");
if (scope.field.restResourceName != null) {
$http.get(CONSTANTS.EXPLAIN_URL + "/" + scope.field.restResourceName)
.success(function (data, status) {
scope.field.nestedFields = [];
data.content.resource.fields.forEach(function (field) {
if (field.isEnum) {
$http.get(CONSTANTS.ENUMS_URL + scope.$root.parseClassName(field.type)).success(function (data, status) {
field.enumValues = [];
for (var index in data.content.values) {
field.enumValues.push(data.content.values[index]);
}
})
}
scope.field.nestedFields.push(field);
})
})
}
scope.appendDirective = function () {
var recursiveFields = $("<p>Insert me</p>");
recursiveFields.insertAfter(element[0]);
$compile(recursiveFields)(scope);
}
}
}
})
Does anyone know how to solve this issue with Angular? Every useful answer is highly appreciated and evaluated.
Thank you.
ngClick has access to the $event that can be passed to your method like this:
<button type="button" ng-click="appendDirective($event)"
That event has a property target.
Check this: https://docs.angularjs.org/api/ng/directive/ngClick
and this: https://docs.angularjs.org/guide/expression#-event-

Angular - Can't access controller methods after compiling directive

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>

Categories

Resources