Acessing form from directive's link function - angularjs

In my custom directive I'm declaring new form <div ng-form="inputForm"></div> with input in it.
How can I access to this form within link function? scope.inputForm is undefined:/
Edit: code sample
.directive('ifInput', ['$system', function ($system) {
return {
restrict: "E",
replace: true,
scope: {},
link: function (scope, element, attrs) {
scope.temp = function () {
console.log(scope.inputForm);
}
},
templateUrl: "template/if-input-pola/index.html"
};
}])
.run(["$templateCache", function ($templateCache) {
$templateCache.put("template/if-input-pola/index.html",
"<div ng-form=\"inputForm\" class=\"form-group ng-fadeInLeft\" ng-class=\"{'has-error': inputForm[kolumna.id].$invalid && inputForm.$dirty}\">" +
"{{inputForm|json}}"+ //here is correct object (with $error and so on)
"<a ng-click=\"temp()\">a</a>" + //here is undefined
"</div>"
);
}])
EDIT 2:
Problem was that I wan's using object like this ng-form="model.inputForm" - now everything is ok

You can just define ng-model e.g.
<input ng-model="myInput"> and
link: function (scope, element) {
console.log(scope.myInput);
}

According to the documentation, using ng-form="inputForm" should post the controller on the local scope. If you cannot see it, you're either looking at a nested scope with that property overwritten, or at an ancestor scope. Keep in mind, that some directives, such as ng-repeat or ng-if, create local scopes, so in the following code:
<div ng-controller="myController">
<div ng-if="someCondition">
<div ng-form="myInput">
...
</div>
</div>
</div>
myInput would NOT be visible on the scope managed by myController.
When accesing the scope property within a link function, it also depends where your linked directive is, as the property may not be assigned to the scope yet. Try $watching the property and you just might find out it's being assigned later.

Related

Angular scope function parameter return undefined

I'm calling a controller function from directive but the function parameter returns undefined when I console.log to check the value. Wondering what I'm doing wrong or maybe a step I forgot. I actually hard coded a value to see if this shows but only get undefined in the console. NOTE: The custom directive template is coming from external file so the function parameter is not being past to the controller. It only works if the custom directive element has the value attached. Should work with the inside directive html.
//******************** Directive ********************//
app.directive('customdir', [function() {
return {
restrict: "E",
template : "<div>Get product<button ng-click="addToCart(56)">Add to Cart</button></div>",
scope: {
addToCart:"&"
},
link: function(scope, el, attrs) {
}
};
}]);
//******************** Controller ********************//
app.controller('mycontroller', function($scope) {
$scope.addToCart = function(thumbProductId){
$scope.thumbProductId = thumbProductId;
console.log("thumbProductId =" + $scope.thumbProductId); // Returns Undefined
};
});
//******************** Html ********************//
<html>
<div ng-controller="mycontroller">
<custom-dir add-to-cart="addToCart(thumbProductId)"> </custom-dir>
</div>
</html>
There were a couple things wrong in the code, the first being "customdir" not having a "-" between it, as there's no capital. You also need to escape certain characters in your html, such as quotations and forward slashes. Here's a plunkr of your example:
http://plnkr.co/edit/FYUGBfIPtrl6Q7GWd597?p=preview
And the directive now looks:
myApp.directive('customdir', [function() {
return {
restrict: "E",
template : "<button ng-click=\"addToCart(thumbProductId)\">Add to Cart<\/button>",
scope: {
addToCart: "&"
}
};
}]);
Exposing Local Values to Parent Scope with Directive Expression Binding
To use Expression Binding to pass data from directive scope to parent scope, invoke the expression with a locals object.
myApp.directive('customdir', function() {
return {
restrict: "E",
template : "<button ng-click='addToCart({$id: 56})'>Add 56 to Cart</button>",
scope: {
addToCart: "&"
}
};
});
The above directive invokes the Angular Expression defined by the add-to-cart attribute with the value 56 exposed as $id.
The HTML:
<div ng-controller="mycontroller">
<customdir add-to-cart="thumbProductId = $id"> </customdir>
ThumbProductId => {{thumbProductId}}
</div>
When the user clicks on the button in the customdir element, the Angular Expression invoked will set thumbProductId to the value exposed by $id which in this case is 56.
To invoke a parent function with a local, simply make the Angular Expression a function:
<customdir add-to-cart="parentFn($id)"> </customdir>
The DEMO on PLNKR.

$scope.$apply() is not updating the parent scope. Is this a binding issue?

Perhaps this is a poor way of doing this, so I'm open to changing this.
I am implementing a drag and drop function through a <drag-item> directive and a <drop-target> directive. The <drop-target> directive successfully calls this function in the parent scope:
$scope.testAddSet = function(){
console.log($scope.workoutItem); // gives me the last value
$scope.thisMonth[0].sets.push($scope.workoutItem);
$scope.$apply(); // needed to update the view, but uses the old value for workoutItem "test"
};
$scope.workoutItem = "test"; // value declared here
but my <drag-item> directive doesn't seem to update the value of $scope.workoutItem,
View:
<div ng-repeat="exercise in exercises">
<drag-item workout-item="workoutItem" exercise="exercise"></drag-item>
</div>
<drag-item> directive:
angular.module('app.directives.dragItem', [])
.directive('dragItem', function(){
return {
restrict: 'E',
scope: {
exercise: '=',
workoutItem: '='
},
templateUrl: "templates/dragTile.html",
link: function(scope, element, attrs){
element.on('dragstart', function (event) {
scope.workoutItem = scope.exercise.name; //correctly updates the local scope, but not the parent scope
console.log(scope.workoutItem);
});
}
};
});
How do I fix this?
Looks like the problem is that you are defining $scope.workoutItem in your parent scope and then directive is creating another workoutItem property at directive's scope level. To avoid this, it is recomended that you use dots(.) to define your models. Please try something like:
$scope.workout = {item: undefined}
and then in your view:
<div ng-repeat="exercise in exercises">
<drag-item workout-item="workout.item" exercise="exercise"></drag-item>
</div>
I think this should solve your problem without modifying the directive itself.

Being wrapped by a directive, how can I access its scope?

How can I access the directive's isolate scope in the directive's body? My DOM looks like this:
<div ng-app="app">
<directive>
<p>boolProperty: {{boolProperty|json}}</p>
</directive>
</div>
The boolProperty is assigned inside the directive's link function:
angular.module("app", []).directive("directive", function() {
return {
scope: {},
link: function($scope) {
$scope.boolProperty = true;
}
};
});
The problem is, the child <p> inside the directive binds to the directive's parent scope, not the directive's isolated scope. How can I overcome this?
Click here for jsFiddle.
There are couple of problems in your code.
The default restrict option is A for attribute so anyways your directive will not be compiled because you are using it as an element. Use restrict: 'E' to make it work.
As per the documentation, the scope of the transcluded element is not a child scope of the directive but a sibling one. So boolProperty will always be undefined or empty. So you have to go up the scope level and find the proper sibling.
<div ng-app="app">
<directive>
<p>boolProperty: {{$parent.$$childHead.boolProperty}}</p>
</directive>
</div>
and need to use transclusion in the directive as:
angular.module("app", []).directive("directive", function() {
return {
restrict: 'E',
scope: {},
transclude: true,
template: '<div ng-transclude></div>',
link: function(scope) {
scope.boolProperty = true;
}
};
});
However, this approach is not advisable and break later If you add a new controller before the directive because transcluded scope becomes 2nd sibling unlike 1st as before.
<div ng-app="app">
<div ng-controller="OneCtrl"></div>
<directive>
<p>boolProperty: {{$parent.$$childHead.boolProperty || $parent.$$childHead.$$nextSibling.boolProperty}}</p>
</directive>
</div>
Here is the Working Demo. The approach I mentioned is not ideal so use at your own risk. The #CodeHater' s answer is the one you should go with. I just wanted to explain why it did not work for you.
You forgot about two things:
By default AngularJS uses attrubute restriction, so in your case in directive definition you should specify restrict: "E"
You should use child scope, but not isolated. So set scope: true to inherit from parent view scope.
See updated fiddle http://jsfiddle.net/Y9g4q/1/.
Good luck.
From the docs:
As the name suggests, the isolate scope of the directive isolates everything except models that you've explicitly added to the scope: {} hash object. This is helpful when building reusable components because it prevents a component from changing your model state except for the models that you explicitly pass in.
It seems you would need to explicitly add boolProperty to scope.
<div ng-app="app" ng-controller="ctrl">
<directive bool="boolProperty">
<p>boolProperty: {{boolProperty|json}}</p>
</directive>
</div>
JS
angular.module("app", []).controller("ctrl",function($scope){
$scope.boolProperty = false;
}).directive("directive", function() {
return {
restrict:"E",
scope: {boolProperty:'=bool'},
link: function($scope) {
$scope.boolProperty = "i'm a boolean property";
}
};
});
Here's updated fiddle.

Angular - condition, transclude

I've written a sample directive with a conditional content (component.html):
<div class="panel panel-default">
<div class="panel-heading">{{title}}</div>
<div class="panel-body">
<p ng-show="loadingVisible()" class="text-center">Loading...</p>
<div ng-show="!loadingVisible()" ng-transclude></div>
</div>
Directive code (component.js):
app.directive('component', function () {
return {
restrict : 'A',
transclude : true,
replace : true,
templateUrl : 'component.html',
require : '^title',
scope : {
title : '#',
loadingVisible : '&'
},
controller : [ '$scope', function ($scope) {
if (!$scope.loadingVisible) {
$scope.loadingVisible = function () {
return false;
};
}
} ]
};
});
The main use of this directive is something like this (sample.html):
<div component title="Title">
<div id="t"></div>
</div>
And the controller code (sample.js):
app.directive('sample', function () {
return {
restrict: 'A',
templateUrl: 'sample.html',
controller: [ '$scope', function ($scope) {
$('#id');
} ]
};
});
0
The problem is that the div aquired by using jQuery selector isn't visible. I guess it's because the loadingVisible method (conditional content) hides that div in the construction phase. So when the sample directive tries to get it it fails. Am I doing something wrong? What's the coorect resolution of this problem? Or maybe my knowledge of directives is wrong?
I'll appreciate any help :)
if you're trying to interact with the DOM (or the directive element itself), you'll want to define a link function. The link function gets fired after angular compiles your directive and gives you access to the directives scope, the element itself, and any attributes on the directive. so, something like:
link: function (scope, elem, attrs) {
/* interact with elem and/or scope here */
}
I'm still a little unclear about what your directive is trying to accomplish though, so it's tough to provide much more help. Any additional details?
so if you want to ensure that a title is specified, you can just check for the presence of the title scope var when the directive gets linked, then throw an error if it's not there.
also, is there any reason loadingVisible needs to be an expression? (using '&' syntax). If you just need to show/hide content based on this value, you could just do a normal one-way '#' binding. so overall, something like:
app.directive('component', function () {
return {
restrict : 'A',
transclude : true,
replace : true,
templateUrl : 'component.html',
scope : {
title : '#',
loadingVisible : '#'
},
link : function (scope, elem, attrs) {
if (!scope.title) {
throw 'must specify a title!';
}
if (!attrs.loadingVisible) {
scope.loadingVisible = false;
}
}
};
});
If you need to get access to any of your transcluded content, you can use the elem value that gets injected into your link function, like so:
elem.find('#a');
an (updated) working plnkr: http://embed.plnkr.co/JVZjQAG8gGhcV2tz1ImK/preview
The problem is that directive structure is like this:
<div component>
<div id="a"></div>
</div>
The directive is used somewhere like this:
asd
The test directive uses a "a" element in its controller (or link) function. The problem is that the test controller code is run before the div is transcluded and it cannot see the content :/ A simple workaround is that the component should be before the test directive. Do you have any other solutions to this problem?

How can I pass a model into custom directive

My goal is to pass the projectName model from my MainController to my custom contenteditable directive.
I have the following controller:
app.controller("MainController", function($scope){
$scope.projectName = "Hot Air Balloon";
});
Here is how I'm calling the directive:
<div class="column" ng-controller="MainController">
<h2 contenteditable name="myWidget" ng-model="projectName" strip-br="true"></h2>
<p>{{projectName}}</p>
</div>
I've gotten the contenteditable directive working by following this tutorial: https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
If I understand the docs correctly then Angular is not going to use the model I want it to. Instead its going to create a new model w/ scope local to the contenteditable directive. I know that I can add an isolate scope to the directive but I don't know how to use the model passed to the isolate scope within the link function.
I have tried something like the following which didn't work ...
<h2 contenteditable item="projectName"></h2>
--- directive code ---
scope: {
item: '=item'
}
link: function(){
...
item.$setViewValue(...)
...
}
--- my original directive call --
<div class="column" ng-controller="MainController">
<h2 contenteditable name="myWidget" ng-model="projectName" strip-br="true"></h2>
<p>{{projectName}}</p>
</div>
--- my original controller and directive ---
app.controller("MainController", function($scope){
$scope.projectName = "LifeSeeds";
});
app.directive('contenteditable', function(){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel){
console.log(ngModel);
if(!ngModel) return;
console.log(ngModel.$viewValue);
ngModel.$render = function(){
element.html(ngModel.$viewValue || '');
};
element.on('blur keyup change', function(){
scope.$apply(read);
});
read();
function read(){
var html = element.html();
if(attrs.stripBr && html == '<br>'){
html = '';
}
ngModel.$setViewValue(html);
}
}
};
});
You can use ng-model with your own directive. To make sure it is included, you can use the attribute require like this:
app.directive("myDirective", function(){
return {
require:"ngModel",
link: function(scope, element, attr, ngModel){
console.log(ngModel);
}
}
});
Then, you can code whatever behavior your want of ng-model within your directive.
Working solution: http://plnkr.co/edit/Lu1ZG9Lpx2sl8CYe8FCx?p=preview
I mentioned that I tried using an isolate scope in my original post.
<h2 contenteditable item="projectName"></h2>
This was actually the correct approach so ignore my full original example using the model argument of the directive's link function.
link: function(scope, element, attrs, ngModel)
The reason the isolate scope approach did not work was because $scope.projectName stored a primitive instead of an object. I did not understand some javascript basics. Mainly, I did not know that primitive types were passed to functions by value.
Primitives are passed by value in javascript. Consequently, changes made to primitive values within a function do NOT change the value of the variable passed to the function.
function changeX(x){
x = 5;
}
x = 4;
changeX(x);
console.log(x) // will log 4 ... Not 5
However, objects passed to functions in javascript are passed by reference so modifications to them within the function WILL be made to the variable passed into the function.
My problem was in how I declared the scope within the MainController.
I had:
$scope.projectName = "LifeSeeds";
That's a primitive. When I passed projectName to the directive I was passing a primitive.
<h2 contenteditable item="projectName"></h2>
Thus, changes to the editable element were being made to the value within the directive but not to the value stored in the MainController's scope. The solution is to store the value within an object in the MainController's scope.
// correct
$scope.project = {
html: "Editable Content"
};
// wrong
$scope.projectName = "Editable Content"

Resources