angularJS directive with isolated scope, attribute binding doesn't work - angularjs

Please see this jsfiddle: http://jsfiddle.net/viro/DK5pC/3/
What I did looks right compared to the tutorials and replies I've found, so I'm sure I'm overlooking something trivial.
I'm trying to do a directive on a html element, that will create a sibling div to display a message associated with the original element.
for example, for this html :
<input ng-model="inp" tst-msg="message" />
I would create as a sibling element:
<div class="msg">Msg:<span ng-bind="tstMsg"></span></div>
I was hoping that tstMsg in the div's scope would be bound to message in the input's scope.
Here's what the directive looks like :
angular.module('tst', [])
.directive('tstMsg', function(){
var template = "<div class='msg' >Msg:<span ng-bind='tstMsg'></span></div>";
var link = function(scope,element,attrs) {
element.parent().append(template);
console.log("link called");
};
return {
restrict: 'A',
scope: {
tstMsg: '='
},
link: link
};
});
Well that doesn't work and I can't figure out why.

You need to $compile the template you're adding to the DOM. Angular hasn't had a chance to add it's handlers, for instance the ng-bind directive to that part of the dom.
So instead of just adding the element like this:
element.parent().append(template);
These steps will let Angular process your template and then add it.
newe = angular.element(template);
$compile(newe)(scope);
element.parent().append(newe);
Updated fiddle

Related

Run 'ng-click' inside a directive's isolated scope

Thanks in advance for taking the time to look into this question
I have serverside generated code that renders a directive wrapped around pre-rendered content.
<serverside-demo color="blue">
<p><strong>Content from Server, wrapped in a directive.</strong></p>
<p>I want this color to show: <span ng-style="{color: color}">{{color}}</span></p>
<button ng-click="onClickButtonInDirective()">Click Me</button>
</serverside-demo>
This means that 1.) the directive tag, 2.) the content inside the directive tag, 3.)the ng-click and 4.) The curly braces syntax are all generated by the server.
I want AngularJs to pick up the generated code, recompile the template and deal with the scope.
The problem is that I am having trouble getting it working. I understand that because the ng-click is inside the controller block, it is picked up not by the directive isolated scope, but the parent controllers. Instead I want the opposite... to pick up the onClickButtonInDirective scope function inside the serversideDemo link
I have created a jsfiddle best explaining my problem, which aims to clearly demonstrate the working "traditional" way of loading the template separately (which works) comparing it to the server-side way.
https://jsfiddle.net/stevewbrown/beLccjd2/3/
What is the best way to do this?
Thank you!
There are two major problem in your code
1- directive name and dom element not matched, - missing in dom element
app.directive('serverSideDemo', function() {
use <server-side-demo color="blue"> instead of <serverside-demo color="blue">
2- you need to compile the html code of server-side-demo dom with directive scope in link function
$compile(element.contents())(scope);
Working jsfiddle
Use templateUrl instead of template to fetch the content of directive from server:
app.directive('serverSideDemo', function() {
return {
restrict: 'AE',
scope: {
color: '='
},
templateUrl: 'link/that/returns/html/content',
link: function(scope, element, attrs) {
scope.onClickButtonInDirective = function() {
console.log('You clicked the button in the serverside demo')
scope.color = scope.color === 'blue' ? 'red' : 'blue';
}
}
};
});
Have a look at angular docs for more details

Angular Animation Directive

I'm trying to write an angular directive that will animate a list of words similar to http://codepen.io/rachsmith/pen/BNKJme . However, I'm needing to load the text from a json file and then select a random sentence to apply the animation to. I have this part working, but am having trouble accessing the directive's child elements. I am assuming this is because the directive is being called before the elements are rendered, but using scope.$on($viewContentLoaded, function... has not made a difference.
I have jQuery and Underscore available.
Here is my code:
Controller
Data.sentences().then(function (response) {
var sentences = response.data;
$scope.sentence = _.sample(sentences);
});
View
<div class="rotator">
<p>{{sentence.static}}</p>
<text-rotator>
<span class="word" ng-repeat="item in sentence.options">{{item}}</span>
</text-rotator>
</div>
Directive
app.directive('textRotator', function () {
return {
restrict: 'E',
link: function (scope, el, attrs) {
var words = el.children('.word');
//cannot access array of items with class of word
}
};
});
Your assumption is correct, the ng-repeat-ed words are not yet in the DOM at the time the link function of the directive is executed. The sentence object is fetched asynchronously.
Listening on $viewContentLoaded won't help: this is an event fired by ngRoute module when the content of the ngView is loaded. After a digest cycle followed by DOM updates due to a change on the model, this event is not fired.
Actually, I think you're creating yourself troubles as the data could be (should be) passed as a parameter to the directive. The child word elements would be the template of the directive. I suggest something like the following:
app.directive('textRotator', function () {
return {
restrict: 'E',
scope: {
options: '='
},
templateUrl: 'words.html',
link: // ...
}
});
Template:
<text-rotator options="sentence.options"></text-rotator>
This fiddle might help you. The animation part has been replaced by a simple toggling of the opacity. Also, the words are mocked in the controller, you should make sure they are resolved by the router in the definition of the route / state, or otherwise you would have to add a watcher in the directive.

Angularjs - Directive Two-way binding Isolated Scope Issue

I'm building a SPA based on AngularJS. In one component of the SPA I have a document upload system, which is built via a custom directive below called docmgmt. Within the component docmgmt I have an another custom directive component called modalCompanyQuery. It is a modal window that searches the company database and returns matching company results. Upon the finding the right company the user clicks on the company name which is then passed back to the parent directive docmgmt called modalOutput.
The issue I have is that despite using two way binding '=' a new scope for modalOutput (output) is created in modalCompanyQuery. How can I pass the modalCompanyQuery search result (modalOutput) back to the parent directive docmgmt? Any help on the simplest way to return the results would be great. Thank you in advance!
Here is my code simplified
modalCompanyQuery Template
<div modal-company-query dialog-show="modalCompanyQuery.isShow" dialog-name ="Select Company" dialog-class="modalSelectCompany" dialog-icon ="fa fa-building" dialog-header="modalSelectCompany-header" company-type = "srchCompanyTypeList" output-select="modalOutput">
</div>
Directive docmgmt
angular.module("docmgmt", [])
.directive("docmgmt",['$http','sessionService','Upload','docService', function($http,sessionService,Upload,docService){
return{
link: function(scope,element,attrs){
scope.docRecord = {};
scope.rightPane = {showAction:true, showInsert:false,showUpdate:false, showRead:false};
scope.progressBar = 0;
scope.submit =[{}];
//modal company search and linking search output results to docmgmt scope
scope.modalCompanyQuery = {isShow:false};
scope.modalOutput={};
scope.test=function(){
console.log(scope.modalOutput);
}
},//return
restrict:"A",
replace:true,
templateUrl:"partials/docmgmt/docmgmt.html",//template
transclude:true,
scope:{
}
}//return
}]);
Directive modalCompanyQuery
angular.module("company", [])
.directive("modalCompanyQuery",['$http','companyService', function($http,companyService){
return{
link: function(scope,element,attrs){ // normal variables rather than actual $scope, that is the scope data is passed into scope
//Read Company
scope.getRecord = function(result){
scope.output={id:result.cs_id, type:result.type,name:result.name, active: result.active};
console.log(scope.output);
scope.isShow = false;
}//getRecord
/*AJAX search functions go here*/
},//return
restrict:"A", //assign as attribute only ie <div my-modal> Content </div>
replace:true,//replaces div with element, note if this is the case must all template must be wrapped within one root element. eg button is within ul otherwise get an error.
templateUrl:"partials/company/tpl/desktop/modal-company-query-desktop.html",//template
transclude:true, //incorporate additional data within
scope:{
isShow:"=dialogShow",//two way binding
name:"#dialogName",//name to be in header
dialogClass:"#dialogClass",// style of the dialog
dialogHeader:"#dialogHeader",//color of the dialogHeader
dialogIcon:"#dialogIcon",//font awesome icon
output:"=outputSelect"
//selectCompany:"=selectCompany",//company to be selected from search and passed back to main window
} //If on this should mean the html input is not binded to custom directive
}//return
}]);
alright, in your docmgmt directive, I see you have made the scope of the directive empty doing:
scope: {}.
I think you should do it like:
scope: {
modalOutput: "="
}
btw doing above expects an attribute in your directive template with name modal-output which must be an object type.
Try it...
After some research I found the solution. The following two links really helped me understand the problem and solution.
Understanding $emit, $broadcast and $on in AngularJS
Communication between nested directives
So I end up using $emit and $on. Outcome as follows:
Directive modalCompanyQuery
scope.getRecord = function(result){
scope.output={id:result.cs_id, type:result.type,name:result.name, active: result.active};
scope.$emit('companyRecord', {record:scope.output});
scope.isShow = false;
}//getRecord
Directive docmgmt
scope.$on('companyRecord', function (event, args) {
scope.modalOutput = args.record;
console.log('Success');
console.log(scope.modalOutput);
});
Hope this helps other people that have come across the same brickwall!

Insert directive dynamically and compiling

I have a scenario when a user click a link, I would like to insert a custom element into the DOM for example
//user clicks
$scope.click = function () {
var el = $compile("<my-directive></my-directive>")($scope);
$element.after(el);
};
The my-directive.... directive has an html template.. say for example (template1.html)
<p>My Template for my-directive</p>
{{SomeProperty}}
my-directive is defined like this
module.directive('myDirective', ['$compile', function ($compile) {
return {
restrict: 'E',
replace: true,
templateUrl: '/template1.html',
scope: true
};
}]);
If we assume the scope in myDirective acutally has a value for SomeProperty after running this code I will indeed have the my-directive inserted into the DOM and replaced by the template - template1.html however the {{SomeProperty}} has not been replaced at all! How do I do this??
See Plunkr for more details
You did $compile("<my-directive></my-directive>")($scope.$parent); in your Plunkr. Remove .$parent
Chenge template to:
<p>My Template for my-directive</p>
{{d.SomeProperty}}
As you did d in data.
It works then :)
I'm not sure if you had any other errors, but I found that removing replace:true made it work for me.
I'm not sure exactly what is happening here, but somehow the interaction of you adding the directive to the dom + replacing it immediately causes it not to work.
Another thing I noticed was that by using $element.after(el); you are ending up with an element that is OUTSIDE the controller's scope. It's not in the div that the controller has scope for. Unfortunately, I only saw this in MY plunkr, so don't know if this affected you also.
Plunkr here

Angular binding inside an inline ckeditor

I'm using inline editing with CKEditor, and I'd like to bind an element to an angular scope value.
<div contentEditable="true">
<p>Here is the value: {{testval}}</p>
</div>
testval should update in the same manner as it would outside the editor.
To protect this text in the editor, I'd like to do something similar to the placeholder plugin. In other words I plan to have a placeholder, dynamically displaying the final text rather than just the placeholder.
I've seen several examples of how to bind the entire contents with angular, but not individual elements. I'm still fairly new to both angular and ckeditor, so any help or pointers would be much appreciated.
It sounds to me like you will need to use a directive for what you want. I might be soewhat off because I'm not completely familiar, but goig by what you've provided, let's assume this example.
html
<body ng-app="myApp">
<div content-editable content="content"></div>
</body>
javascript
angular.module('myApp', [])
.directive('contentEditable', function() {
restrict: 'A',
replace: true,
scope: {
// Assume this will be html content being bound by the controller
// In the controller you would have:
// $scope.content = '<div>Hello World</div>'
content: "="
},
template: '<div contentEditable="true"><p>Here is the value {{ content }}</p></div>'
});
Still not sure if I completely comprehend, but let me know if I'm getting closer.
I assume that you want to bind the HTML text in model to the element. I used ng-bind-html to render what is in the model and I created the directive ck-inline to add the inline feature and bind the model to the changes that happen in the inline editor. This directive requires a ng-bind-html to work and you also need to have ngSanitize added to your module. Add directive ck-inline to your element and
I also use $timeout because I noticed that if I don't the text is rendered and then ckeditor somehow deletes all the values which messes up the model (this does not happen with the non-inline option). Here is the code.
yourModule.directive('ckInline', ['$sce', '$timeout', function($sce, $timeout){
return{
require : '?ngBindHtml',
scope:{value:"=ngBindHtml"},
link : function(scope, elm, attr, ngBindHtml)
{
$timeout(function()
{
var ck_inline;
elm.attr("contenteditable", "true");
CKEDITOR.disableAutoInline = true;
ck_inline = CKEDITOR.inline(elm[0]);
if (!attr.ngBindHtml)
return;
ck_inline.on('instanceReady', function()
{
ck_inline.setData(elm.html());
});
function updateHtml()
{
scope.$apply(function()
{
scope.value = $sce.trustAsHtml(ck_inline.getData());
});
}
ck_inline.on('blur', updateHtml);
ck_inline.on('dataReady', updateHtml);
});
}
};
}]);

Resources