angular.js two directives, second one does not execute - javascript

I have two directives defined in an angular.js module. The HTML element that is declared first executes its directive, but the second HTML element that uses the other directive does not execute it.
Given this HTML:
<div ng-app="myApp">
<div ng-controller="PlayersCtrl">
<div primary text="{{primaryText}}"/>
<div secondary text="{{secondaryText}}"/>
</div>
</div>
and this angular.js code:
var myApp = angular.module('myApp', []);
function PlayersCtrl($scope) {
$scope.primaryText = "Players";
$scope.secondaryText = "the best player list";
}
myApp.directive('primary', function(){
return {
scope: {
text: '#'
},
template: '<h1>{{text}}</h1>',
link: function(scope, element, attrs){
console.log('primary directive');
}
};
});
myApp.directive('secondary', function(){
return {
scope: {
text: '#'
},
template: '<h3>{{text}}</h3>',
link: function(scope, element, attrs){
console.log('secondary directive');
}
};
});
The resulting HTML is only the "primary" directive, and the "secondary" directive does not render:
<div ng-app="myApp" class="ng-scope">
<div ng-controller="PlayersCtrl" class="ng-scope">
<div primary="" text="Players" class="ng-isolate-scope ng-scope">
<h1 class="ng-binding">Players</h1>
</div>
</div>
</div>
The console output verifies this as well, as only the "primary directive" text is output.
Then if I switch the order of the primary and secondary elements, the secondary directive is executed and the primary directive is not:
<!-- reversed elements -->
<div secondary text="{{secondaryText}}"/>
<div primary text="{{primaryText}}"/>
<!-- renders this HTML (secondary, no primary) -->
<div ng-app="myApp" class="ng-scope">
<div ng-controller="PlayersCtrl" class="ng-scope">
<div secondary="" text="the best player list" class="ng-isolate-scope ng-scope">
<h3 class="ng-binding">the best player list</h3>
</div>
</div>
</div>
Why is this? What am I doing wrong?

div's are not void elements and require a closing tag.
<div ng-app="myApp">
<div ng-controller="PlayersCtrl">
<div primary text="{{primaryText}}"></div>
<div secondary text="{{secondaryText}}"></div>
</div>
</div>
Example

Related

Angularjs custom component vs ngSwitch directive compilation order

There is a following code snippet:
<my-header></my-header>
<div ng-switch="$ctrl.page">
<div ng-switch-when="1"><component1></component1></div>
<div ng-switch-when="2"><component2></component2></div>
<div ng-switch-when="3"><component3></component3></div>
</div>
I want that component myHeader would be constructed before ngSwitch directive takes an action. Now component1 is constructed before myHeader.
Routing represents following code:
$stateProvider
.state({
name: 'myApp',
url: '/',
component: 'loader',
})
.state({
name: 'myApp.pages',
url: 'pages/{id:int}',
component: 'loader'
});
$urlRouterProvider.otherwise('/pages/1');
You can achieve this by exposing your controller in the link function inside the myHeader directive.
With that, you can easily add variables to the controller and control the visibility of the ng-switch div with ng-if. Check the code snippet down here.
Ah, don't forget to add ng-cloak to the div containing the ng-switch directive.
angular
.module('app', [])
.controller('TestController', function($scope) {
this.page = 1;
})
.directive('myHeader', function () {
return {
link: function (scope, element, attrs) {
// With element.controller() we can reach the controller that is wrapping our directive. Then, we can simply set the headerIsLoaded variable to true.
element.controller().headerIsLoaded = true;
},
scope: true,
templateUrl: 'my-header.html'
}
});
<div ng-controller="TestController as ctrl">
<my-header></my-header>
<!-- Add a visual feedback so user knows the components are being loaded -->
<div ng-if="!ctrl.headerIsLoaded">
Loading...
</div>
<!-- If ctrl.headerIsLoaded is set to true, the ng-switch will appear -->
<div ng-if="ctrl.headerIsLoaded"
ng-cloak>
<div ng-switch="ctrl.page">
<div ng-switch-when="1">
Page 1
</div>
<div ng-switch-when="2">
Page 2
</div>
<div ng-switch-when="3">
Page 3
</div>
</div>
</div>
</div>

ngRepeat on custom directive tags

I have a list of custom directives which are different widgets on my dashboard. The directives are defined as follows:
angular.module('core').directive('graphCardWidget', function() {
return {
restrict: 'E',
replace: true,
scope: {
target: '=target'
},
templateUrl: 'modules/core/widgets/graph-card.client.widget.html'
};
});
angular.module('core').directive('pieChartWidget', function() {
return {
restrict: 'E',
replace: true,
scope: {
target: '=target'
},
templateUrl: 'modules/core/widgets/pie-chart.client.widget.html'
};
});
In my controller, I have a list of widgets to be displayed. The list is as follows:
$scope.dashboardWidgets = [
{
directive : 'graph-card-widget',
target : 'widgets.dashboards.activeDevicesCard'
},
{
directive : 'graph-card-widget',
target : 'widgets.dashboards.activeSessionsCard'
},
{
directive : 'pie-chart-widget',
target : 'widgets.dashboards.devices'
},
{
directive : 'pie-chart-widget',
target : 'widgets.dashboards.sessions'
}
];
Now in my view, I use ng-repeat to iterate this array and display the items. Here is the code of my view:
<div layout="row" layout-wrap layout-align="center" layout-xs="column" ng-drop="true">
<div ng-repeat='widget in dashboardWidgets'>
<{{widget.directive}} ng-drag="true" flex='45' target='{{widget.target}}'>
</{{widget.directive}}>
<span flex='5'></span>
</div>
</div>
But the browser renders this as text. Here is what I get in my DOM:
<div layout="row" layout-wrap="" layout-align="center" layout-xs="column" ng-drop="true" class="layout-wrap layout-xs-column layout-align-center-stretch layout-row">
<div ng-repeat="widget in dashboardWidgets" class="ng-binding ng-scope">
“<graph-card-widget ng-drag="true" flex='45' target='widgets.dashboards.activeDevicesCard’>”
<span flex="5" class="flex-5"></span>
</div>
<div ng-repeat="widget in dashboardWidgets" class="ng-binding ng-scope">
“<graph-card-widget ng-drag="true" flex='45' target='widgets.dashboards.activeSessionsCard’>”
<span flex="5" class="flex-5"></span>
</div>
<div ng-repeat="widget in dashboardWidgets" class="ng-binding ng-scope">
“<pie-chart-widget ng-drag="true" flex='45' target='widgets.dashboards.devices’>”
<span flex="5" class="flex-5"></span>
</div>
<div ng-repeat="widget in dashboardWidgets" class="ng-binding ng-scope">
“<pie-chart-widget ng-drag="true" flex='45' target='widgets.dashboards.sessions’>”
<span flex="5" class="flex-5"></span>
</div>
</div>
So what can I do to make the directive rendered as a tag and not plain text?
I saw a couple of questions similar to these such as, question 1, question 2 but they all are adding the dynamic directives in standard HTML tags. What I need is a dynamic tag.
Update:
After following #cnexans answer, I got it working partially. The widget is drawn but the target attribute is not evaluated which leads to a blank widget.
Here's the plunkr with the issue: https://plnkr.co/edit/BGN6C4LAHguWthU4fGy0?p=preview
You can use ng-if inside ng-for in order to switch the name "directive" within each iteration to show one or another directive.
<div ng-repeat="data in dashboardWidgets">
<div ng-if="data.directive == 'graph-card-widget'">
<graph-card-widget ng-drag="true" flex='45' target=data.target>
</graph-card-widget>
<span flex='5'></span>
</div>
<div ng-if="data.directive == 'pie-chart-widget'">
<pie-chart-widget ng-drag="true" flex='45' target=data.target>
</pie-chart-widget>
<span flex='5'></span>
</div>
</div>
You can create a container directive to take care of this logic too, so you can share this functionality to other pages if needed.
<widgets-container widgets-list=data></widgets-container>
Working example: https://codepen.io/anon/pen/jBzEpe?editors=1010#0
Edit:
Checking the example you gave, you need to pass a Widget object to the directive, and you are passing a string. Here is a working example forked from the plunkr given
https://plnkr.co/edit/3Oxxmp?p=preview
It has a function namespaceToObject which transform the string into the desired object from $scope.
don't know whether this will match your requirement. but i did some modification to your array.
I did this using ng-bind-html and when your are binding custom element like directive you need to compile it again. for that i create(actually borrow) this directive.
.directive('compileTemplate', function($compile, $parse){
return {
link: function(scope, element, attr){
var parsed = $parse(attr.ngBindHtml);
function getStringValue() { return (parsed(scope) || '').toString(); }
//Recompile if the template changes
scope.$watch(getStringValue, function() {
$compile(element, null, -9999)(scope); //The -9999 makes it skip directives so that we do not recompile ourselves
});
}
}
});
I changed the ng-repeat like this
<div ng-repeat='widget in dashboardWidgets' compile-template ng-bind-html="trust(widget.directive)">
<span flex='5'></span>
</div>
trust function will return trusted html
$scope.trust = function(someHTML){
return $sce.trustAsHtml(someHTML);
}
modify the array like this
$scope.dashboardWidgets = [
{
directive : '<graph-card-widget ng-drag="true" flex="45" target="widget.target"></ graph-card-widget>',
target : 'widgets.dashboards.activeDevicesCard'
},
{
directive : '<graph-card-widget ng-drag="true" flex="45" target="widget.target"></ graph-card-widget>',
target : 'widgets.dashboards.activeSessionsCard'
},
{
directive : '<pie-chart-widget ng-drag="true" flex="45" target="widget.target"></ pie-chart-widget>',
target : 'widgets.dashboards.devices'
},
{
directive : '<pie-chart-widget ng-drag="true" flex="45" target="widget.target"></ pie-chart-widget>',
target : 'widgets.dashboards.sessions'
}
];
Demo

ngDirective is not watching

I am trying to make angular works with semantic ui, specifically dropdown searching box. I have tried multiple things and I found that because the input type is set to be hidden, ng-model is not going to work. And because the change to the element is not directly triggered by the user, the ng-change also won't work.
My idea is to use a directive and try to monitor the input element's attribute, because after selecting something, the value attribute changes. I think ngDirective should be able to implement the ng-model and ng-change by using scope.$watch
I tried this but the scope value has not been changed.
var app = angular.module('myApp', [
'my.controllers'
]);
var controllers = angular.module('my.controllers', []);
controllers.controller('MyController', function($scope) {
$scope.checked = true;
});
app.directive("ngSearch", function(){
function link($scope, element, attrs, ctrl) {
console.log($scope.value);
$scope.$watch("value", function(newValue, oldValue){
console.log(newValue, oldValue);
console.log("value updated");
ctrl.$setViewValue($scope.value);
}, true);
setTimeout(function() {console.log(attrs.value);}, 6000);
}
return {
restrict: 'A',
require: 'ngModel',
scope: {
"value": "#",
},
link: link,
}
})
$(".ui.dropdown").dropdown();
<link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.2/semantic.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.2/semantic.min.js"></script>
<div ng-app="myApp">
<div ng-conroller="MyController">
<div class="ui fluid multiple search normal selection dropdown">
<input type="hidden" name="country" value="test1" ng-search ng-model="users">
<i class="dropdown icon"></i>
<div class="default text">Select testing</div>
<div class="menu">
<div class="item" data-value="test1">test1</div>
<div class="item" data-value="test2">test2</div>
<div class="item" data-value="test3">test3</div>
</div>
</div>
</div>
</div>
same version in jsfiddle: http://jsfiddle.net/e5aes1fe/1/

AngularJs, accessing parent scope from directive inside a nested ng-repeat

That's a noob question. I'm looking for the correct way to access the parent scope inside a directive in a nested ng-repeat. This is exactly what i mean:
<div ng-app="myApp" ng-controller="myCtrl">
<div ng-repeat="section in sections">
{{section.Name}}
<div ng-repeat="item in section.Items" ng-init="parent = section">
<span class="menuItem">{{item}}</span>
</div>
</div>
</div>
And the directive:
myApp.directive('menuItem', function () {
return {
restrict: 'C',
link: function (scope, element, attrs) {
console.log(scope.$parent.section.SectionId);
}
}
});
The directive is attached to an item in the inner ng-repeat, and i need to access a property of the parent object. The problem is that i cannot access directly to the parent properties (with scope.$parent), because ng-repeat creates a new scope, and i must append the name of the object i set in the ng-repeat (in this case scope.$parent.section.):
<div ng-repeat="section in sections">
console.log(scope.$parent.section.SectionId);
JsFiddle: http://jsfiddle.net/7Lra7Loy/2/
As i want the directive to be generic, so it can be used inside other ng-repeat blocks, without being forced to use the same names in the ng-repeat, the only way i found is to use an ng-init, that would be the same in all ng-repeat blocks (ng-init="parent = section"):
<div ng-repeat="section in sections">
{{section.Name}}
<div ng-repeat="item in section.Items" ng-init="parent = section">
<span class="menuItem">{{item}}</span>
</div>
</div>
myApp.directive('menuItem', function () {
return {
restrict: 'C',
link: function (scope, element, attrs) {
console.log(scope.parent.SectionId);
}
}
});
JsFiddle: http://jsfiddle.net/7Lra7Loy/1/
Is there a better way to handle this situation? Or am i just missing something? I searched a bit, but i couldn't find anything really useful.
Template:
<div ng-app="myApp" ng-controller="myCtrl">
<div ng-repeat="section in sections">
{{section.Name}}
<div ng-repeat="item in section.Items">
<span class="menuItem" section="{{section}}">{{item}}</span>
</div>
</div>
</div>
And directive:
myApp.directive('menuItem', function () {
return {
restrict: 'C',
scope: {
section: '#' // text-binding
//section: '&' //one-way binding
//section: '=' //two-way binding
},
link: function ($scope, element, attrs) {
console.log($scope.section);
}
}
});
JsFiddle: https://jsfiddle.net/nrkmn/26zhqbjg/

angularjs template binding

I have developed a transclude directive and I set it to use angularjs template script everything works fine but I still cannot access the bind data.
My code:
index.html
<div side-element="box" title="Links">
<ul>
<li>Link 1</li>
<li>Link 2</li>
</ul>
</div>
<script id="box" type="text/ng-template">
<div class="side-box">
<div class="content">
<h2 class="header">Box {{ title }}</h2>
<span class="content" ng-transclude></span>
</div>
</div>
</script>
<script id="ad" type="text/ng-template">
<div class="side-ad">
<div class="content">
<h2 class="header">AD {{ title }}</h2>
<span class="content" ng-transclude></span>
</div>
</div>
</script>
app.js:
angular.module('myApp.directives')
.directive('sideElement', function ($templateCache, $log) {
return {
scope: {
title: '#'
},
transclude: 'element',
link: function(scope, element, attrs, ctrl, transclude){
var template = $templateCache.get(attrs.sideElement);
var templateElement = angular.element(template);
$log.info(scope.title);//Output the title i put in the html which is (Links)
transclude(scope, function(clone){
element.after(templateElement.append(clone));
});
}
};
});
the scope inside the link function(....) displaies the correct title but it doesn't work in the html:
Box {{ title }}
Link 1
Link 2
I think I missed one thing but I can't figure it out, I need your help to complete the cycle.
Thanx in advance,
The template element that contains the angular binding expressions must be compiled first, and then linked. The compilation phase prepares the template, while the linking phase sets up your $watchers for your binding expressions.
Demo Here
Here is a compile function that should work:
.directive('sideElement', function ($templateCache, $compile, $log) {
return {
restrict: 'A',
transclude: true,
scope:'#',
compile: function(element, attrs) {
var template = $templateCache.get(attrs.sideElement);
var templateElement = angular.element(template);
element.append(templateElement);
return function(scope, element, attr, ctrl, transclude) {
$log.info(scope.title);
}
}
}

Categories