AngularJS - adding new values to directive's isolated scope - angularjs

I have a directive with isolated scope as following:
application.directive("myDirective",function(){
return {
restrict: "A",
scope: {myDirective:"="},
link : function(scope) {
console.log("My directive: ",scope.myDirective) // works fine
scope.person={name:"John",surname:"Doe"}
scope.hello=function(){
console.log("Hello world!")
}
}
}
})
Corresponding view:
<div my-directive='{testValue:3}'>
Testvalue: {{myDirective}}<br/>
Hello {{person}}
<button ng-click="hello()">Say hello</button>
</div>
And it seems that i cannot use any of the fields declared in the scope. In view the "myDirecive" and "person" fields are blank and the scope's "hello" function is not executed when i press the button.
It works fine when i pass scope="true" to the directive but does not work in isolated scope.
Am i missing something here or maybe there is no way to introduce variables to the isolated scope of a directive?
UPDATE
Updated question presenting why i would rather not to use a static template. The effect i am trying to achieve is to make directive that allows to upload any html form getting the form initial data via rest/json. The whole process is rather complex and application specific therefore i cannot use any available form libraries. I present below the simplified version of the use case:
The updated directive
application.directive("myForm",function(){
return {
restrict: "A",
scope: {myForm:"="},
link : function(scope) {
console.log("Form parameters: ",scope.myForm) // works fine
scope.formData=... // Get form initial data as JSON from server
scope.submitForm=function(){
// Send scope.formData via REST to the server
}
}
}
})
The case when i would like to use this form. Of course i would like to use this directive many times with different forms.
<form my-form='{postUrl:'/myPostUrl',getFormDataUrl:'/url/to/some/json}'>
<div>Form user: {{formData.userName}} {{formData.userSurname}}
<input type="text" ng-model="formData.userAge" />
<input type="text" ng-model="formData.userEmail" />
<button ng-click="submitForm()">Submit</button>
</form>
I hope this explains why i cannot use one static html template for this scenario.
Maybe someone can explain why this is working with scope="true" and with an isolated scope i cannot access any scoped variables?

With Angular, directives either work with a template (or templateUrl) or with transcluded content.
If you're using a template, then the template has access to the isolate scope. So, if you put {{person}} in the template it would work as expected.
If you're using transcluded content - that is, the content that is a child of the node which has the directive applied to it - then, not only would you need to set transclude: true and specify where in the template the transcluded content goes - e.g. <div ng-transclude></div> to even see the content, you would also not get the results you expect, since the transcluded content has access to the same scope variables as the parent of the directive, and not to those available in the isolate scope of the directive.
Also, you should be aware that if you pass a non-assignable object to the directive's isolate scope with "=" - like you did with my-directive="{testValue: 3", then you can't make any changes to it (and, unfortunately, even to its properties even if they are scope variables).
So, to make your specific case work, do this:
application.directive("myDirective",function(){
return {
...
template: "Testvalue: {{myDirective}}<br/> " +
"Hello {{person}} " +
"<button ng-click="hello()">Say hello</button>";
};
});
and the corresponding view:
where prop is set in the View controller to: $scope.prop = {testValue: 3};

You can always alter the default behavior of transcluded scope ( though I don't recommend it):
application.directive("myDirective",function(){
return {
tranclude: true,
scope: {myDirective:"="},
link : function(scope, element, attrs, ctrl, $transclude) {
$transclude(scope, function(clone) {
element.empty();
element.append(clone);
});
scope.person={name:"John",surname:"Doe"};
scope.hello=function(){
console.log("Hello world!");
};
}
};
});
See the docs: https://docs.angularjs.org/api/ng/service/$compile#transclusion-functions
Also take a look at ngTranslude source code: https://github.com/angular/angular.js/blob/master/src/ng/directive/ngTransclude.js

Related

ANGULAR JS dynamic data binding to template

Hello All Angular Friends
I am trying to find a way for dynamic data binding to the template.
Created a test page: http://jsbin.com/jiminey/edit?html,js,output.
Currently I have my HTML
<banner compSrc="banner1"></banner>
<banner compSrc="banner2"></banner>
And the Data
$scope.bannerData ={
"banner1": {
"heading": "Hero Test"
},
"banner2": {
"heading": "Page Title (h1)"
}
};
Template
template: '<div>BannerHeading - {{bannerData.banner2.heading}}</div>'
How can I make this template dynamic based on the compSrc attribute?
I am looking for something like below So I dont have to update the template.
template: '<div>BannerHeading - {{heading}}</div>'
Thank you.
You can use isolated scope for directives. Take in account the name normalization.
Here is fixed JSBin
Create a directive for your template, and use function as value for your compile property of DDO
Plz refer this question on SO: What are the benefits of a directive template function in Angularjs?
app.directive('myDirective', function(){
return {
// Compile function acts on template DOM
// This happens before it is bound to the scope, so that is why no scope
// is injected
compile: function(tElem, tAttrs){
// This will change the markup before it is passed to the link function
// and the "another-directive" directive will also be processed by Angular
tElem.append('<div another-directive></div>');
// Link function acts on instance, not on template and is passed the scope
// to generate a dynamic view
return function(scope, iElem, iAttrs){
// When trying to add the same markup here, Angular will no longer
// process the "another-directive" directive since the compilation is
// already done and we're merely linking with the scope here
iElem.append('<div another-directive></div>');
}
}
}
});

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)" />

Function arguments in directive with templateUrl

I created a custom directive with an isolated scope that binds to a function from the enclosing controller and with references to a templateUrl. Here's what my code looks like:
the html
<div ng-controller='MyCtrl as my'>
<custom-directive data='my.data' on-search="my.find(param1, param2)"></custom-directive>
</div>
the directive
app.directive('customDirective', function() {
return {
restrict : 'E',
scope : {
data : '=data'
search : '&onSearch',
},
templateUrl : 'customDirective.html'
};
});
the template
<div>
<input ng-model='data.id'>
<a ng-click='find(param1, param2)'></a>
</div>
The arguments received by function find is also stored in data. The controller data binds to the directive but not the function. My log inside the function won't even show.
It seems there are different ways to do it as I have seen in many examples (see below) but none seems to work in my case.
Example 1: pass a mapping of parameter and values in the template
<div>
<input ng-model='data.id'>
<a ng-click='find.({param1: data.value1, param2: data.value2})'></a>
</div>
Example 2: put a link in the directive
app.directive('customDirective', function() {
return {
restrict : 'E',
scope : {
data : '=data'
search : '&onSearch',
},
templateUrl : 'customDirective.html',
link : function(scope, elem, attr) {
scope.retrieve({param1: scope.data.value1,
param2: scope.data.value2});
}
};
});
Example 3 : use scope.$apply(), $parse in link but haven't tried this
Could someone show me how to do it and also explain to me the link part (I don't understand that part) and if you're feeling generous, show the working alternatives as shown by the examples. Thanks
You don't have to passe params for your function just the reference so in your html
<custom-directive data='my.data' on-search="my.find"></custom-directive>
and your template directive directly call
<div>
<input ng-model='data.id'>
<a ng-click='find(data.value1, data.value2)'></a>
</div>
I also suggest you to use $scope and not the controller. So in your controller define
$scope.data = {
id: 1,
value1: "value1",
value2: "value2"
}
$scope.find = function (param1, param2) {
//Your logic
}
And in your template put directly
<custom-directive data='data' on-search="find"></custom-directive>
I hope this answer to your question
About link this text from angular js doc is pretty clear I think
Directives that want to modify the DOM typically use the link option.
link takes a function with the following signature, function
link(scope, element, attrs) { ... } where:
scope is an Angular scope object. element is the jqLite-wrapped
element that this directive matches.
attrs is a hash object with key-value pairs of normalized attribute names and their
corresponding attribute values.
In our link function, we want to update the
displayed time once a second, or whenever a user changes the time
formatting string that our directive binds to. We will use the
$interval service to call a handler on a regular basis. This is easier
than using $timeout but also works better with end-to-end testing,
where we want to ensure that all $timeouts have completed before
completing the test. We also want to remove the $interval if the
directive is deleted so we don't introduce a memory leak.

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.

angularjs custom directive isolated scope one way data binding doesn't work

i am a new to angularjs, I read some literature and followed a lot of tutorials, but i am still have the feeling that i completely confused.
My current issue is with custom directive and isolated scopes. All i trying to do is pass "strings" with # binding to my directives that use isolated scopes and I can't understand what am i doing wrong. Specifically WHY when i use template everything just works fine and when the template already in the DOM the one way data binding doesn't work.
JSBin fiddle link
major parts from my code:
HTML
<div my-directive my-title="TITLE ONE WAY Data Binding">
<div>
<div>This directive is <span style="color:red;">NOT using template</span></div>
<div>
$scope.title = <small><pre>{{title}}</pre></small>
</div>
</div>
</div>
<div my-directive-with-template my-title="TITLE ONE WAY Data Binding"
>
<!-- this directive use a template -->
</div>
JS
var app = angular.module('app', []);
app.directive('myDirective', function() {
return {
restrict: 'AE',
scope:{
title: "#myTitle"
},
link: function(scope, ele, attrs, c) {
console.log('non template directive link:',scope.title,attrs.myTitle);
},
controller:['$scope', function($scope){
console.log('non template directive controller:',$scope.title);
}]
};
});
app.directive('myDirectiveWithTemplate', function() {
return {
restrict: 'AE',
scope:{
title: "#myTitle"
},
link: function(scope, ele, attrs, c) {
console.log('using template directive link:',scope.title,attrs.myTitle);
},
controller:['$scope', function($scope){
console.log('using template directive link:',$scope.title);
}],
template:'<div><div>This directive is using template</div><div>$scope.title = <small><pre>"{{title}}"</pre></small></div></div>',
replace:true
};
});
JSBin fiddle link
In your non-template scenario the title is not being bound to any scope and therefore not showing anything.
What you call the DOM template is really HTML outside the directive that has no access to it's isolated scope. You could embed this div inside a controller and then title could be bound to the controller's $scope.title
For what I understand it only makes sense to create an isolated scope to make it available to the directive's template.
Clarification
Isolated scopes allow the directive to have state independent of the parent scope (avoiding it's pollution) and also avoiding sharing this state with sibling directives.
Supposing you're creating this directive to reuse that piece of UI somewhere else in your code, you start by creating its template with the shared HTML.
Ok, but you need to go a bit further and parameterize it passing some data to it.
You can then use attributes on the directive to communicate with the outside (parent scope, or just to pass static data).
The directive's template can now bind to this data without needing to have any knowledge of it's "outside world", and it's done through it's isolated scope.
Conclusion, why create an isolated scope, if not to provide the template with this data?
Hope I've made this a bit clear :)
Now after thinking a bit about my affirmation... well you could also create a directive without any template, by using the compile or link function and do it manually through DOM manipulation. And in this case it might make sense to have an isolated scope for the reasons presented above :)

Resources