How to include data/scope from controller in a dynamically added directive? - angularjs

I'm trying to figure out how to include scope with a directive that I add to the dom on a click event in a controller.
Step 1. On a click event, I call a function in my controller that adds a directive like this
$scope.addMyDirective = function(e, instanceOfAnObjectPassedInClickEvent){
$(e.currentTarget).append($compile("<my-directive mydata='instanceOfAnObjectPassedInClickEvent'/>")($scope));
}
//I'm trying to take the `instanceOfAnObjectPassedInClickEvent` and make it available in the directive through `mydata`
The above, part of which I got from this SO answer, successfully adds the directive (and the directive has a template that gets added to the dom), however, inside the directive, I'm not able to access any of the scope data mydata it says it's undefined.
My directive
app.directive('myDirective', function(){
return {
restrict: 'AE',
scope: {
mydata: '='
//also doesn't work if I do mydata: '#'
},
template: '<div class="blah">yippee</div>',
link: function(scope,elem,attrs) {
console.log(scope) //inspecting scope shows that mydata is undefined
}
}
}
Update
I changed the name of datafromclickedscope in the OP to make it more clear. In the controller action addMyDirective (see above) instanceOfAnObjectPassedInClickEvent is an instance of an object passed into the controller method on a click event that I try to pass into the directive as mydata='instanceOfAnObjectPassedInClickEvent'. However, even if I change = to # in the directive and I try to access scope.mydata in the link function of the directive, it just shows a string like this "instanceOfAnObjectPassedInClickEvent", not the actual object data that is available to me in my method that handles the click event

When you use mydata='instanceOfAnObjectPassedInClickEvent' in a template you need instanceOfAnObjectPassedInClickEvent to defined in $scope. So before compiling you should assign a variable in $scope. I will rename this variable in code below, so that same names would not confuse you and it would be clear that a formal parameter of a function cannot be visible in a template.
$scope.addMyDirective = function(e, instanceOfAnObjectPassedInClickEvent){
$scope.myEvent = instanceOfAnObjectPassedInClickEvent;
$(e.currentTarget).append($compile("<my-directive mydata='myEvent'/>")($scope));
}
EDIT: slightly adapted jsfiddle not using JQuery no manipulate DOM

Related

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!

How to refactor directive and change functionality by checking parent scopes?

I have a form with a ton of duplicate functionality in 2 different Controllers, there are slight differences and some major ones in both.
The form sits at the top of a products view controller, but also inside of the products modal controller.
Test plunker: http://plnkr.co/edit/EIW6xoBzQpD26Wwqwwap?p=preview
^ how would you change the string in the console.log and the color of the button based on parent scope?
At first I was going to create a new Controller just for the form, but also the HTML was being duplicated, so decided to put that into a Directive, and just add the Controller code there.
My question now is this: How would I determine which parent scope the form-directive is currently being viewed in? Because depending on the parent scope the functions/methods behave differently.
So far I've come up with this:
.directive('productForm', function() {
return {
templateUrl: "views/products/productForm.html",
restrict: "E",
controller: function($scope) {
console.log('controller for productForm');
console.log($scope);
console.log($scope.$parent);
/*
If parent scope is the page, then this...
If parent scope is the modal then this instead...
*/
}
}
});
However it's giving me back $parent id's that look like 002 or 00p. Not very easy to put in if / else statements based on that information.
Have you guys run into this issue before?
You can define 'saveThis' in your controller and pass it to directive using '&'
scope: {
user: '=',
saveThis : '&'
},
please see demo here http://plnkr.co/edit/sOY8XZtEXLORLmelWssS?p=preview
That gives you more flexibility, in future if you want to use saveThis in another controller you can define it inside controller instead adding additional if statement to directive.
You could add two way binding variables in the directive scope, this allows you to specify which Ctrl variable gets bound to which directive variable
<my-directive shared="scopeVariable">
this way you achieve two way binding of the scopeVariable with the shared directive variable
you can learn more here
I advice against this practice and suggest you to isolate common logics and behaviours in services or factories rather than in directives
This is an example of a directive that has isolated scope and shares the 'title' variable with the outer scope.
You could declare this directive this way:
now inside the directive you can discriminate the location where the directive is defined; just replace the title variable with a location variable and chose better names.
.directive('myPane', function() {
return {
restrict: 'E',
scope: {
title: '#'
},
link: function(scope, element, attrs, tabsCtrl) {
},
templateUrl: 'my-pane.html'
};
});

Common directive ng-click guidance needed

I have a directive which consists of a form text element and a continue button along with the associated controller etc. This directive is going to be used in about 5 different pages, but on each page it is used the continue button will do something different.
My question is where can/should I put the code for the continue button if it does different things for each page?
Since its a directive I cant simply pass a different function into ng-click depending on what page im on (ie, if i simply replicated the code on each page it is used I could simply change the function called on ng-click and have that function in each of the page controllers.
Hopefully Im not being too vague with my question and you can make sense of what im asking. If not just say so and ill try to explain in more detail.
I would really appreciate some guidance on this matter.
Thanks.
There are two ways that you can do it. If you are creating your directive as a true component you can use isolated scope with & binding that binds to an expression.
Assume your directive looks like
<div do-work on-click="save()"></div>
and the generated html
<div>
<input ...>
<button ng-click="doAction()"><button>
</div>
The directive scope will be defined
scope:{
onClick:'&'
}
In your directive controller or link function you need to implement the button doAction, which in turns evaluates the onClick action
scope.doAction=function() {
scope.onClick({//if params are required});
}
Now you have linked the parent through the direct onClick reference. One thing to remember here is that this creates a directive with isolated scope.
In case you do not want isolated scope created you need to use
scope.$eval(attr.onClick); // this evaluates the expression on the current scope.
Hope this helps.
Ideally you should not create directives which are not re-usable.
In your case, you may do it like following -
create an isolated scope in the directive
add a function to be called and pass the page/ page id as parameter
call functions in controller based on parameter
Directive
myApp.directive('someDirecive', function () {
return {
// restrict options are EACM. we want to use it like an attribute
restrict: 'A',
// template : <inline template string>
// templateUrl = path to directive template.
// templateUrl: '',
scope: {
onButtonClick : '&'
},
controller: function ($scope, $element, $attrs, $transclude) {
$scope.onButtonClick = function(pageId) {
if (pageId == 1) {
// do something
}
else if (pageId == 2) {
// do something
}
}
},
//link: function (scope, iElement, iAttrs) {
//}
};
});
HTML
<div some-directive on-button-click="DoSomething(1)" />

How do I assign an attribute to ng-controller in a directive's template in AngularJS?

I have a custom attribute directive (i.e., restrict: "A") and I want to pass two expressions (using {{...}}) into the directive as attributes. I want to pass these attributes into the directive's template, which I use to render two nested div tags -- the outer one containing ng-controller and the inner containing ng-include. The ng-controller will define the controller exclusively used for the template, and the ng-include will render the template's HTML.
An example showing the relevant snippets is below.
HTML:
<div ng-controller="appController">
<custom-directive ctrl="templateController" tmpl="template.html"></custom-directive>
</div>
JS:
function appController($scope) {
// Main application controller
}
function templateController($scope) {
// Controller (separate from main controller) for exclusive use with template
}
app.directive('customDirective', function() {
return {
restrict: 'A',
scope: {
ctrl: '#',
tmpl: '#'
},
// This will work, but not what I want
// Assigning controller explicitly
template: '<div ng-controller="templateController">\
<div ng-include="tmpl"></div>\
</div>'
// This is what I want, but won't work
// Assigning controller via isolate scope variable from attribute
/*template: '<div ng-controller="ctrl">\
<div ng-include="tmpl"></div>\
</div>'*/
};
});
It appears that explicitly assigning the controller works. However, I want to assign the controller via an isolate scope variable that I obtain from an attribute located inside my custom directive in the HTML.
I've fleshed out the above example a little more in the Plunker below, which names the relevant directive contentDisplay (instead of customDirective from above). Let me know in the comments if this example needs more commented clarification:
Plunker
Using an explicit controller assignment (uncommented template code), I achieve the desired functionality. However, when trying to assign the controller via an isolate scope variable (commented template code), it no longer works, throwing an error saying 'ctrl' is not a function, got string.
The reason why I want to vary the controller (instead of just throwing all the controllers into one "master controller" as I've done in the Plunker) is because I want to make my code more organized to maintain readability.
The following ideas may be relevant:
Placing the ng-controller tags inside the template instead of wrapping it around ng-include.
Using one-way binding ('&') to execute functions instead of text binding ('#').
Using a link function instead of / in addition to an isolate scope.
Using an element/class directive instead of attribute directive.
The priority level of ng-controller is lower than that of ng-include.
The order in which the directives are compiled / instantiated may not be correct.
While I'm looking for direct solutions to this issue, I'm also willing to accept workarounds that accomplish the same functionality and are relatively simple.
I don't think you can dynamically write a template key using scope, but you certainly do so within the link function. You can imitate that quite succinctly with a series of built-in Angular functions: $http, $controller, $compile, $templateCache.
Plunker
Relevant code:
link: function( scope, element, attrs )
{
$http.get( scope.tmpl, { cache: $templateCache } )
.then( function( response ) {
templateScope = scope.$new();
templateCtrl = $controller( scope.ctrl, { $scope: templateScope } );
element.html( response.data );
element.children().data('$ngControllerController', templateCtrl);
$compile( element.contents() )( templateScope );
});
}
Inspired strongly by this similar answer.

Directive scope is not refreshing when provided value will be assign to directive scope

I created my own directive in angularjs and I noticed that directive scope is not refreshing when I change scope in main controller.
I made simple example which after 3s changing scope value, but the value is not changed in the directive. The issue exists only if I assign directive provided value to the directive scope.
Entire example is available here: jsfiddle
My directive:
myApp.directive('textPresenter', function() {
return {
transclude : false,
restrict: 'E',
scope: {
adfName : '='
},
template: '{{xxx}}' ,
controller: function($scope){
$scope.xxx = $scope.adfName; //this is the issue
}
};
});
$scope.xxx should refresh, after scope change in main controller.
The scope value adfName in the template is bound to the one in your main controller by Angular, and will change when that one changes. If you change the template to:
template: '{{adfName}}'
as in http://jsfiddle.net/6bp7c/1/ , then you can see this.
However, from your comment, you say you can't do this. Then one possibility would be setup a watcher in your directive, to watch adfName, and set xxx to be equal to it when it changes:
$scope.$watch('adfName', function(newAdfName) {
$scope.xxx = newAdfName;
});
as can be seen at http://jsfiddle.net/7V9FV/ . This is necessary because
$scope.xxx = $scope.adfName
just copies the contents of adfName and put it into xxx. If adfName later changes, then the contents of xxx are unaffected.
I am curious as to why you have to copy the variable though, and not just change the template to use the one passed into the directive.

Resources