Passing ng-repeat context to child directive - angularjs

In parts of my application I have used a directive in this format:
<child-directive ng-repeat="item in vm.items"></child-directive>
This one has access to {{item}} from within child-directive without having to do anything.
Now I want to use the same directive along side other directives that all work with the same context data:
<div ng-repeat="item in vm.items">
<child-directive></child-directive>
<other-directive></other-directive>
</div>
The child directive does not need to alter the context data, it only needs the information from inside to display a widget.
I've tried using scope in the directive in this format:
angular
.module('myapp.dashboard')
.directive('childDirective', childDirective);
function childDirective() {
var directive = {
restrict: 'E',
templateUrl: 'client/components/child-directive.ng.html',
controller: 'ChildDirectiveController',
controllerAs: 'vm',
scope: {
item: '='
}
};
return directive;
}
and
<child-directive ng-attr-item="{{item}}"></child-directive>
Within the ng-repeat section. However that just throws an error.
I've also tried ng-bind with no luck.
Any suggestions?

You're using bi-directional scope binding rather than an interpolated property. You can read up on this more in the angular docs
Change your scope object to be:
scope: {
item: '#'
}
OR, change your template to:
<child-directive ng-attr-item="item"></child-directive>

Related

Not able to access angular directive's isolated scope

First Directive:
app.directive("myDirectiveOne", function($rootScope){
return {
templateUrl : "/custom-one-html.html",
restrict: "AE",
replace:true,
scope: {
somedata: "=",
flags: "=",
functionone: "&"
}
,controller: function($rootScope,$scope, $element) {
$scope.firstFunction = function(){
console.log("First function is getting called")
}
$scope.$on('firstBroadcast',function(event, data){
$rootScope.$broadcast('secondBroadcast', data)
});
}
Second Directive:
app.directive("myDirectiveTwo", function($rootScope){
return {
templateUrl : "/custom-two-html.html",
restrict: "AE",
replace:true,
scope: {
data: "=",
functiontwo: "&"
}
,controller: function($rootScope,$scope, $element) {
$scope.secondFunction = function(){
console.log("Second function is getting called")
$rootScope.$broadcast('firstBroadcast', {})
}
$scope.$on('secondBroadcast',function(event, data){
$scope.callSomeFunctionWithData(data);
});
$scope.secondFunction();
$scope.editFunction = function(x){
console.log("This is the edit function", x);
}
Parent Controller:
$scope.parentFuntion = function(){
console.log("No trouble in calling this function")
}
So, the problem is when I try calling a function from a myDirectiveTwo html template, the controller which is active is the parent controller and not the isolated one.
May be it has something to do with the broadcasts I am using?
Html Code:
<div ng-repeat="x in data">
<h5>{{x.name}}</h5>
<button ng-click="editFunction(x)">Edit</button>
</div>
The strange thing is I get data values and ng-repeat works fine on load. But, when I click on the button it doesnt do anything. And if I add the same function in the parent controller, it gets called.. :(
How do I make the isolated scope controller active again..?
The problem is that ng-repeat creates a child scope, therefore the editFunction ends up being on the parent scope.
From docs
... Each template instance gets its own scope, where the given loop variable is set to the current collection item ...
Docs here
You can verify that this is the issue by getting your button element's scope and checking the $parent, as such angular.element(document.getElementsByTagName("button")).scope()
Although considered code smell, you can append $parent to your function call in order to access it, though keep in mind this now places a dependency on your HTML structure.
<button ng-click="$parent.editFunction(x)">Edit</button>
The issue was that I was using a deprecated method replace:true. This caused the unexpected scenarios.. As #Protozoid suggested, I looked at his link and found the issue.. To quote from the official documentation:
When the replace template has a directive at the root node that uses transclude: element, e.g. ngIf or ngRepeat, the DOM structure or scope inheritance can be incorrect. See the following issues: Incorrect scope on replaced element: #9837 Different DOM between template and templateUrl: #10612
I removed replace:true and its fine now :)
This is the link:
Here

Is there a way to alias scope variable for a html node?

I Have a directive myDirective which has a template and transcended html (transclude: true)
myDirective template has a repeater ng-repeat="item in transformSourceData"and transcluded template uses item to render page, now my question is how to use item in transcluded html without saying $parent.item
I want something like init-my-scope="item=$parent.item" so that I do not need to use $parent.item in my transcluded html.
The reason to do that is I need my transcluded html to preserve its origin scope since it has bindings to its originated scope and only uses item from parent, and also it interpolate some string from a model which is binded to its scope which evaluate to !item.property
Here is the structure:
myModule.directive('myDirective', function() {
var directive = {
bindToController: true,
controller: myDirectiveController,
controllerAs: 'vm',
restrict: 'E',
transclude: true,
scope: {
data: '=sourceData',
},
template: '<div ng-repeat="item in transformSourceData"ng-transclude></div>'
};
return directive;
});
and in my template:
<my-directive source-data="someData">
<span>{{$parent.item.property}}</span>
<!--This is what I want to use-->
<!--<span>{{item.property}}</span>-->
<span ng-if="{{originScope.someCondition}}>{{originScope.data}}</span>
</my-directive>
in above example originScope.someCondition will evaluate to !item.property but item.property wont be available and the value can be retrieved using $parent.item.property
so I need to somehow tell my template when ever you encounter item refer to $parent.item and all the places which is binded to $parent.item.xyz will be rather bind to item.xyz.

Pass rendered string into Angular directive

I am trying to make an angular directive that renders dynamic content urls based on an attribute placed on the directive. Example:
Directive:
angular
.module('myModule')
.directive('myContent', directive);
function directive() {
return {
replace: true,
templateUrl: function (elem, attrs) {
return attrs.contentUrl;
}
};
}
HTML:
<div my-content content-url="url/to/my-content.html"></div>
However what I would like is for the content-url attribute to be populated by a string from the controller. So let's say the controller is using the "controllerAs" syntax with the name "home", I would like the html to read:
<div my-content content-url="{{home.myContent.url}}"></div>
However within the directive's templateUrl function, the contentUrl attribute is being sent literally as "{{home.myContent.url}}". How can I get this value to evaluate before running the templateUrl function? Or, is there a better way to have simple, dynamic content available from a directive?
The answer is provided by #crhistian-ramirez:
Just use ng-include
<div ng-include="vm.myContent.url"></div>

how can i use ngModel inside bindings of components angularjs

angular.module('RatingModule').component('starRating', {
templateUrl: 'rating.html',
controller: RatingCtrl,
bindings: {
maxStars: '=',
ngModel : '='
//above is the component where i want to use ngModel inside bindings of coponents
function RatingCtrl() { i want to use ngModel here }
// below is the index.html where ng-model is binding a value from the app controller
<body ng-app="RatingModule" ng-controller="appCtrl as ctrl">
<star-rating ng-model="ctrl.rating.number" max-stars="5"></star-rating>
the angular ngModel directive binds an input,select, textarea (or
custom form control) to a property on the scope using
NgModelController, which is created and exposed by this directive.
This sould not work, plus you should not use a angular name for a custom property.
you can do
angular.module('RatingModule').component('starRating', {
templateUrl: 'rating.html',
controller: RatingCtrl,
bindings: {
maxStars: '=',
myModel : '='
then
function RatingCtrl() { $scope.myModel }
and
<star-rating my-model="ctrl.rating.number" max-stars="5"></star-rating>
Well, actually it's possible to use ngModel, but it's 100% worthless.
You can set ng-model on component, then require: 'ngModel' and manually set ngModelCtrl.$setViewValue on 'click' for example. But as I said before, it's ill wrong approach.
Here is a Plunker, basically, you can set model as you want
element.on('click', function(){
ngModelCtrl.$setViewValue(scope.index);
});
$setViewValue(value, trigger); Update the view value.
This method should be called when a control wants to change the view
value; typically, this is done from within a DOM event handler. For
example, the input directive calls it when the value of the input
changes and select calls it when an option is selected.
DMCISSOKHO raised a good point, you couldn't use ng-model here. Try this instead:
angular.module('RatingModule').component('starRating', {
templateUrl: 'rating.html',
controller: RatingCtrl,
bindings: {
maxStars: '=',
ratingNumber : '='
and in your view use
<star-rating rating-number="ctrl.rating.number" max-stars="5"></star-rating>

Accessing ng-repeat scope on a custom directive

I'm having a go at a directive which will dynamically load a template based on a scope value passed into it.
I am using ng-repeat on my directive, and am using the iterated item as an attribute property:
<my-form-field ng-repeat="field in customFields" field="field">
In my directive I have the following code to set the template being used.
(function() {
'use strict';
angular
.module('app')
.directive('myFormField', myFormField);
function myFormField() {
var directive = {
restrict: 'E',
scope: {
field: '='
},
link: function(scope){
scope.getContentUrl = function() {
return 'app/modules/form_components/form_field/' + scope.field.type + '-template.html';
}
},
template: '<div ng-include="getContentUrl()"></div>'
};
return directive;
}
})();
Whilst the above works (which I found from other posts), I wonder if there is a better way.
For example I have seen examples of calling a function on the templateUrl config option instead, and then in that function access the scope attributes being passed in. When I tried this way, my field attribute was a literal 'field' string value (they are objects in my customFields array), so I think at that point the scope variables had not yet been evaluated.
With this current solution I am using, all of my templates get wrapped in an extra div since I am using ng-include, so I am just trying to make the rendered markup more succinct.
Any suggestions\advice is appreciated.
Thanks

Resources