AngularJS: Objects inside nested directives getting undefined? - javascript

So, I have two directives, one that has a template file, which contains another directive of the same type.
The first directive looks like:
.directive('billInfo', function () {
return {
// scope: true,
scope: {
obj: '='
},
restrict: 'E',
templateUrl: 'views/templates/bill-info.html',
link: function (scope, element, attrs) {
scope.status = scope.obj.getStatus();
scope.bill = scope.obj;
}
}
})
And the template is pretty simple, something like;
<h4>
<span class="glyphicon glyphicon-cutlery">
{{bill.getTable()}}
</span>
<small><span class="time"></span></small>
<div class="btn-group bill-btn">
<bill-btns billobj="bill"></bill-btns>
</div>
</h4>
The directive for billBtns looks like:
.directive('billBtns', function () {
return {
scope: {
billobj: '='
},
restrict: 'E',
template: '<div><div>koko{{status}}</div></div>',
link: function (scope, element, attrs) {
console.log(scope, scope.billobj);
scope.status = scope.billobj.getStatus();
}
}
})
The problem is unexpected: scope.billobj turns out to be undefined. When I console log scope from within the link function of the billBtns directive, all seems ok: I can see billobj inside scope.
What is going on here? Am I doing something fundamentally wrong here?
EDIT: Template for billInfo
<div draggable ng-repeat="(index, bill) in getEnq()" bill="bill" id="bill-{{bill.orderCode}}" class="container panel panel-default bill float-{{index%2}}" style="width:300px;" data-created="{{bill.getCreatedOn()}}">
<bill-info obj="bill"></bill-info>
</div>

I believe I've come to a solution, but I'm uncertain as to if this is the right practice. Here's what the new billBtns directive looks like:
.directive('billBtns', function () {
return {
restrict: 'E',
template: '<div><div>koko{{status}}</div></div>',
link: function (scope, element, attrs) {
console.log(scope, scope.$parent.obj);
scope.status = scope.$parent.bill.getStatus();
}
}
})
And that solves the problem. My suspicion is this, if we look at the billInfo directive again, I do something like:
scope.bill = scope.obj; // woah?
I'd like to understand more about why this happens and why I can access scope.$parent.bill from a nested directive but not scope.$parent.obj without getting typeerrors. Or maybe thats just the way to cascade scopes.

Related

Angular | Directive not passing correct value back

Hi i have created a directive and it does not pass back the correct message.
The directive is used to pass tool-tips back to the html page
this is what the html looks like
<info-text info-msg="Adding another applicant might help you to get approved."></info-text>
below is the directive
(function(){
angular.module('mainApp').directive('infoText', [function () {
return {
scope: { infoMessage: '&infoMsg' },
restrict: 'E',
replace: true,
template: '<p class="info-text"><i class="fa fa-info-circle"></i> {{infoText}}</p>',
link: function(scope, elem, attrs) {
$(elem).prev().hover(function(){
$(elem).addClass('info-hover');
}, function(){
$(elem).removeClass('info-hover');
});
}
};
}]);
}());
the message i get rendered on the page is as follows (it does send the glyphicon):
{{infoText}}
Any ideas,
thanks. Kieran.
You should not use & for this sort of binding, basically it is used for expression binding. I think one way binding (#) is efficient for what you are doing.
Also you should change directive template {{infoText}} to {{infoMessage}}
Markup
<info-text
info-msg="{{'Adding another applicant might help you to get approved.'}}"></info-text>
Directive
angular.module('mainApp').directive('infoText', [function () {
return {
scope: { infoMessage: '#infoMsg' },
restrict: 'E',
replace: true,
template: '<p class="info-text"><i class="fa fa-info-circle"></i> {{infoMessage}}</p>',
link: function(scope, elem, attrs) {
$(elem).prev().hover(function(){
$(elem).addClass('info-hover');
}, function(){
$(elem).removeClass('info-hover');
});
}
};
}]);
And making more cleaner and readable html you could place that string into some scope variable and pass that scope variable in info-msg attribute

Angular directive's link function not being called

I've got a problem with AngularJS directive link function. It's not beeing called and it doesn't throw any error. Also the template in directive's return is not rendering :( Where should be problem? Thank you for answers!
angular.module('sampleApp.game').directive('gameCanvas', function($injector) {
console.log('Directive is working'); // this works,
function linkFn(scope, ele, attrs) {
console.log('Link function doesnt working :('); // but this not :(
};
return {
scope: {},
template: '<div class="blabla"></div>',
link: linkFn
}
});
My html template file
<div class="jumbotron text-center">
<h1>Play a game!</h1>
<p>{{ tagline }}</p>
<div class="game-canvas"></div>
</div>
By default, directives are for Element and Attribute ('EA') only. Define the restrict attribute as 'C'. Best practice is to always define it explicitly.
angular.module('sampleApp.game').directive('gameCanvas', function($injector) {
console.log('Directive is working'); // this works,
function linkFn(scope, ele, attrs) {
console.log('Link function doesnt working :('); // but this not :(
};
return {
scope: {},
restrict: 'C', //'EA' by default
template: '<div class="blabla"></div>',
link: linkFn
}
});
Documented by Angular here - https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object.

AngularJs: Iterating over array inside directive in order to create nested set of directives

Inside angularJS directive I'm trying to iterate over array and based on values I would like to create nested list of directives.
Current version of directive
Directive type
.directive("type", function($compile, $log){
return{
restrict: "E",
replace: true,
transclude: true,
scope: {
type: '='
},
template: "<div></div>",
link: function(scope, element, attrs){
if (angular.isArray(scope.type)){
angular.forEach(scope.type, function(value, index){
$log.error(value);
element.append("<type type='scope.type['"+index+"]'></type>");
});
} else if (angular.isObject(scope.type)){
element.append("OBJECT")
} else {
element.append("<div>{{scope.type}}</div>")
}
$compile(element.contents())(scope)
}
};
})
I also tried to use above directive with next version of link function:
if (angular.isArray(scope.type)) {
element.append("<div ng-repeat='element in scope.type'><type type='element'></type></div>");
} else if (angular.isObject(scope.type)) {
element.append("OBJECT")
} else {
element.append("<div>{{scope.type}}</div>")
}
$compile(element.contents())(scope)
}
None of provided codes solve my issue.
Below you will find example explaining on specific case:
Let's say that I had next object in the scope.type = [null,"int"]. Now I would like to use <type type='type'><type> and as a result of first evaluation I want to have sth like:
<type type='type[0]'></type><type type='type[1]'></type>
Further evaluation of those values should lead to some simpler form but right now it is not important.
How I can achieve sth like this?
Edit
I tried even to exctract part of the code responsible for iteration to the seperate directive but it still does not work. Code:
Update link function in type directive:
link: function(scope, element, attrs) {
if (angular.isArray(scope.type)) {
element.append("<typeb type='scope.type'></typeb>")
} else if (angular.isObject(scope.type)) {
element.append("OBJECT")
} else {
element.append("<div>{{scope.type}}</div>")
}
$compile(element.contents())(scope)
}
New directive:
.directive("typeb", function($compile, $log){
return{
restrict: "E",
replace: true,
transclude: true,
scope: {
type: '='
},
template: "<div ng-repeat='t in type'>{{t}}</div>",
};
})
Problem still occurs but generated html contains only next pieces as a result of typeb directive:
<!-- ngRepeat: t in type -->
The problem you are getting is <!-- ngRepeat: t in type --> this is because your type didn't contains any value, when it is inside typeb directive
Your directive shouldn't be use scope.variable on view.
Scope variable will be directly accessible by their name like
{{type}} or <typeb type='type'></typeb>
Change your link code to below.
Directive link
link: function(scope, element, attrs) {
if (angular.isArray(scope.type)) {
element.append("<typeb type='type'></typeb>")
} else if (angular.isObject(scope.type)) {
element.append("OBJECT")
} else {
element.append("<div>{{type}}</div>")
}
$compile(element.contents())(scope)
}
Thanks.
Use an ng-repeat in the template
<your-directive attr="item.someattr" ng-repeat="item in items"></your-directive>
.directive("type", function($compile, $log){
return{
restrict: "E",
replace: true,
transclude: true,
scope: {
type: '='
},
template: "NG REPEAT HERE",
...
})

Using & bindings in nested AngularJS directives

I'm trying to bind to ng-change on an element created by a directive up through two other directives that wrap it to a method on the controller using & bindings in an isolate scope, but I can't figure out how to get arguments to pass all the way through. Here's a plunk that demonstrates the problem.
In short, I have an HTML structure like this:
<body ng-app="ExampleApp">
<div ng-controller="Controller">
<button ng-click="doSomething('Called directly')">Call Function Directly</button>
<br />
<outer on-outer-model-changed="doSomething('Called from Outer in HTML')"></outer>
</div>
</body>
The controller:
var app = angular.module('ExampleApp', []);
app.controller('Controller', ['$scope',
function($scope) {
$scope.doSomething = function(one, two, three) {
console.log(arguments);
};
}
]);
The outer directive:
app.directive('outer', function($compile) {
return {
restrict: 'E',
scope: {
outerModelChanged: '&onOuterModelChanged'
},
link: function(scope, element, attrs) {
var innerElement = angular.element('<inner></inner>');
innerElement.attr('on-inner-model-changed', 'outerModelChanged(\'Called from Outer\')');
element.after(innerElement);
$compile(innerElement)(scope);
console.log(arguments);
}
}
});
And the inner directive that the outer directive creates:
app.directive('inner', function() {
return {
scope: {
innerModelChanged: '&onInnerModelChanged'
},
restrict: 'E',
template: '<button ng-click="innerModelChanged(\'Called from Inner\')">Call from Inner</button>'
}
});
I understand that I'm getting the output ["Called from Outer in HTML"] because this is hardcoded into the <outer> tag. What I don't understand is how to pass arguments all the way up from the inner directive.
I'm not sure I 100% get what you want to accomplish but this is how you would make the ["Called from Inner"] message appear.
Change the html so the on-outer-model-changed expression does not use a hardcoded string.
<body ng-app="ExampleApp">
<div ng-controller="Controller">
<button ng-click="doSomething('Called directly')">Call Function Directly</button>
<br />
<outer on-outer-model-changed="doSomething(outerParam)"></outer>
</div>
</body>
Then change the outer directive to call outerModelChanged with a parameter. And set the outerParam to the innerParam.
app.directive('outer', function($compile) {
return {
restrict: 'E',
scope: {
outerModelChanged: '&onOuterModelChanged'
},
link: function(scope, element, attrs, controller) {
var innerElement = angular.element('<inner></inner>');
innerElement.attr('on-inner-model-changed', 'outerModelChanged({outerParam:innerParam})');
element.after(innerElement);
$compile(innerElement)(scope);
console.log(arguments);
}
}
});
Finally call the innerModelChanged from the inner directive with the innerParam set to your message.
app.directive('inner', function() {
return {
scope: {
innerModelChanged: '&onInnerModelChanged'
},
restrict: 'E',
template: '<button ng-click="innerModelChanged({innerParam:\'Called from Inner\'})">Call from Inner</button>'
}
});
Here is a plunk to the above code.

Dynamic NG-Controller Name

I want to dynamically specify a controller based on a config that we load. Something like this:
<div ng-controller="{{config.controllerNameString}}>
...
</div>
How do I do this in angular? I thought this would be very easy, but I can seem to find a way of doing this.
What you want to do is have another directive run before anything else is called, get the controller name from some model remove the new directive and add the ng-controller directive, then re-compile the element.
That looks like this:
global.directive('dynamicCtrl', ['$compile', '$parse',function($compile, $parse) {
return {
restrict: 'A',
terminal: true,
priority: 100000,
link: function(scope, elem) {
var name = $parse(elem.attr('dynamic-ctrl'))(scope);
elem.removeAttr('dynamic-ctrl');
elem.attr('ng-controller', name);
$compile(elem)(scope);
}
};
}]);
Then you could use it in your template, like so:
<div dynamic-ctrl="'blankCtrl'">{{tyler}}</div>
with a controller like this:
global.controller('blankCtrl',['$scope',function(tyler){
tyler.tyler = 'tyler';
tyler.tyler = 'chameleon';
}]);
There's probably a way of interpolating the value ($interpolate) of the dynamic-ctrl instead of parsing it ($parse), but I couldn't get it to work for some reason.
I'm using it in ng-repeat, so this is improved code for loops and sub objects:
Template:
<div class="col-xs6 col-sm-5 col-md-4 col-lg-3" ng-repeat="box in boxes">
<div ng-include src="'/assets/js/view/box_campaign.html'" ng-dynamic-controller="box.type"></div>
</div>
Directive:
mainApp.directive('ngDynamicController', ['$compile', '$parse',function($compile, $parse) {
return {
scope: {
name: '=ngDynamicController'
},
restrict: 'A',
terminal: true,
priority: 100000,
link: function(scope, elem, attrs) {
elem.attr('ng-controller', scope.name);
elem.removeAttr('ng-dynamic-controller');
$compile(elem)(scope);
}
};
}]);
Personally the 2 current solutions here didn't work for me, as the name of the controller would not be known when first compiling the element but later on during another digest cycle. Therefore I ended up using:
myapp.directive('dynamicController', ['$controller', function($controller) {
return {
restrict: 'A',
scope: true,
link: function(scope, elem, attrs) {
attrs.$observe('dynamicController', function(name) {
if (name) {
elem.data('$Controller', $controller(name, {
$scope: scope,
$element: elem,
$attrs: attrs
}));
}
});
}
};
}]);

Categories