Ive gotten a problem with ng-transcule orphan issue, wich directed me to this link:
https://docs.angularjs.org/error/ngTransclude/orphan?p0=%3Cng-transclude%3E
This happened when trying to implement a tab directive I saw on thinkster.io, into the code of the shaping up with Angularjs course of codeschool.
I've must have donse something wrong but can't figure out what exactly.
I implemented it creating a new module called tab and making it a dependency on the product-store module, this is part of the code:
index.html:
<body ng-controller="StoreController as store">
<h1 ng-bind="'Welcome' | capitalize"></h1>
<ul class="list-group">
<li class="list-group-item" ng-repeat="product in store.products | filter: store.search | orderBy: '-price'" ng-hide="product.soldOut">
<h3>
<product-title product="product"></product-title>
</h3>
<product-panels product="product"></product-panels>
</li>
</ul>
</body>
</html>
codeschoolapp.js:
(function(){
angular.element(document).ready(function() {
angular.bootstrap(angular.element("body")[0], ['store'], {
strictDi: true
});
});
var store = angular.module('store', ['store-products']);
store.controller('StoreController', ['$http', function($http){
var vm = this;
vm.products = [];
$http.get('json/products.json').success(function(data){
vm.products = data;
});
}]);
store.filter('capitalize', function(){
return function (text) {
return text.toUpperCase();
};
});
})();
product.js:
(function(){
var store = angular.module('store-products', ['tab']);
store.directive("productPanels", function(){
return {
restrict: 'E',
templateUrl: "product-panels.html",
scope: { "product" : "=" }
};
});
store.directive("productDescription", function(){
return {
restrict: 'E',
scope: { "product" : "=" },
templateUrl: "product-description.html"
};
});
store.directive("productSpecs", function(){
return {
restrict: 'E',
scope: { "product" : "=" },
templateUrl: "product-specs.html"
};
});
store.directive("productReviews", function(){
return {
restrict: 'E',
scope: { "product" : "=" },
templateUrl: "product-reviews.html"
};
});
store.directive("productTitle", function(){
return {
restrict: 'E',
scope: { "product" : "=" },
templateUrl: "product-title.html"
};
});
})();
tab.js:
(function(){
var tab = angular.module('tab', []);
tab.directive('tab', function(){
return {
restrict: 'E',
transclude: true,
templateUrl: 'tab.html',
require: '^tabset',
scope: {
heading: '#'
},
link: function(scope, elem, attr, tabsetCtrl) {
scope.active = false;
tabsetCtrl.addTab(scope);
}
};
});
tab.directive('tabset', function() {
return {
restrict : 'E',
tranclude : true,
scope: {
type: '#'
},
templateUrl: 'tabset.html',
bindToController: true,
controllerAs: 'tabset',
controller: function() {
var self = this;
self.tabs = [];
self.classes = {};
if(self.type === 'pills') {
self.classes['nav-pills'] = true;
}
else {
self.classes['nav-tabs'] = true;
}
self.addTab = function (tab){
self.tabs.push(tab);
if(self.tabs.length === 1) {
tab.active = true;
}
};
self.select = function(selectedTab) {
angular.forEach(self.tabs, function(tab) {
if(tab.active && tab !== selectedTab) {
tab.active = false;
}
});
selectedTab.active = true;
};
}
};
});
})();
relevant templates:
tabset.html:
<div role="tabpanel">
<ul class="nav" ng-class="tabset.classes" role="tablist">
<li role="presentation"
ng-repeat="tab in tabsett.tabs"
ng-class="{'active': tab.active}">
<a href=""
role="tab"
ng-click="tabsett.select(tab)">{{tab.heading}}</a>
</li>
</ul>
<ng-transclude>
</ng-transclude>
</div>
tab.html:
<div role="tabpanel" ng-show="active" ng-transclude></div>
product-panels.html:
<tabset type="pills">
<tab heading="Description">
<product-description product="product"></product-description>
</tab>
<tab heading="Specifications">
<product-specs product="product"></product-specs>
</tab>
<tab heading="Reviews">
<product-reviews product="product"></product-reviews>
</tab>
</tabset>
Also here is a plunker with everything
The error's link says that I must be missing a transclude : true, but the DDOs do have that, and haven't been able to find much on the issue.
Thanks for the help
There is just a minor spelling error in tab.js : tranclude : true, should be transclude : true,
Related
My Main.Html which contains the tab directives.
<div class="modal-header">
<span class="modal-title">Add Report Template</span>
<div class="closeButton right" ng-click="closeModal()"></div>
</div>
<div class="modal-body">
<tab-set>
<tab heading="1st Tab">
This is the content for 1st Tab
</tab>
<tab heading="2nd Tab">
This is the content for 2nd tab
</tab>
<tab heading="3rd Tab">
This is the content for 3rd tab.
</tab>
</tab-set>
</div>
<div class="modal-footer">
<div>
<button type="text" class="grayButton right" ng-click="goToNextTab()">Next</button>
<button type="text" class="grayButton left" ng-click="goToPreviousTab()">Back</button>
</div>
</div>
My Main.controller where i need the define the function for Next and Back Button
(function () {
'use strict';
angular
.module('myApp')
.controller('Controller', ['$scope', function($scope,) {
var vm = this;
/////////////// Function to Change tab on click of the Back/Next Button ///////////////
$scope.goToNextTab = function() {
};
$scope.goToPreviousTab = function() {
};
}]);
})();
My TabSet directive that displays the 3 tabs.
angular
.module('myApp')
.directive('TabSet', function() {
return {
restrict: 'E',
transclude: true,
scope: { },
templateUrl: 'tabset.html',
bindToController: true,
controllerAs: 'tabs',
controller: function($scope) {
var self = this;
self.tabs = [];
self.addTab = function addTab(tab) {
self.tabs.push(tab);
if(self.tabs.length === 1) {
tab.active = true;
}
};
self.select = function(selectedTab) {
angular.forEach(self.tabs, function(tab) {
if(tab.active && tab !== selectedTab) {
tab.active = false;
}
});
selectedTab.active = true;
};
}
};
});
Tabset Html for the corresponding tabset directive.
<div role="tabpanel" class="tabsets">
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" ng-repeat="tab in tabs.tabs" ng-class="{'active': tab.active}">
{{tab.heading}}
</li>
</ul>
<div ng-transclude></div>
</div>
This is the Tab directive for creating the individual tabs.
angular
.module('myApp')
.directive('Tab', function() {
return {
restrict: 'E',
transclude: true,
template: `<div role="tabpanel" ng-show="active"><div ng-transclude></div></div>`,
require: '^TabSet',
scope: {
heading: '#'
},
link: function(scope, elem, attr, tabs) {
scope.active = false;
tabs.addTab(scope);
}
}
});
I am not too sure what I am missing, but for the given structure I want to switch tabs based on click of the Next as well as Back Button defined in main.html.
I see your code is wrong on link function of Tab directive.
link: function(scope, elem, attr, reporttabs) {
scope.active = false;
tabs.addTab(scope);
}
You need to change tabs.addTab to reporttabs.addTab
And here is the solution for the feature you want to implement. You need to add a selectedTabIndex property into scope of Tabset. So you can use scope.$watch function and when the selectedTabIndex has changed you can call scope.select(selectedTab). Here code example:
angular
.module('myApp')
.controller('Controller', ['$scope', function($scope,) {
var vm = this; vm.selectedTabIndex = 0;
$scope.goToNextTab = function() {
vm.selectedTabIndex += 1;
};
$scope.goToPreviousTab = function() {
vm.selectedTabIndex -= 1;
};
}]);
angular
.module('myApp')
.directive('TabSet', function() {
return {
restrict: 'E',
transclude: true,
scope: { 'selectedTabIdex': '=' },
templateUrl: 'tabset.html',
bindToController: true,
controllerAs: 'tabs',
controller: function($scope) {
var self = this;
self.tabs = [];
self.addTab = function addTab(tab) {
self.tabs.push(tab);
if(self.tabs.length === 1) {
tab.active = true;
}
};
self.select = function(selectedTab) {
angular.forEach(self.tabs, function(tab) {
if(tab.active && tab !== selectedTab) {
tab.active = false;
}
});
selectedTab.active = true;
};
$scope.$wacth('selectedTabIndex', function(newValue, oldValue) {
var index = newValue;
if(index >= self.tabs.length) {
return $scope.selectedTabIndex = 0;
}
if(index < 0) {
return $scope.selectedTabIndex = self.tabs.length - 1;
}
self.select(self.tabs[index]);
});
}
};
});
I have a problem where getCurrentOption is always returning undefined, and I can't for the love of coding figure out why. Because I have 2 other functions (coForms.toggleSelect and coForms.isSelectToggled) identical in functionality and they work fine.
For some reason select.currentOption is set correctly in coForms.selectOption but when I try and fetch the value in select.getCurrentOption it's not there anymore.. Is it losing a reference or something somewhere? I'm not too familiar with the controller as syntax nor hooking it up to a directive like this.
What is happening?
CoFormsCtrl:
var coForms = this;
coForms.toggleSelect = function(select) {
select.isToggled = !select.isToggled;
};
coForms.isSelectToggled = function(select) {
return select.isToggled ? true : false;
};
coForms.selectOption = function(select, option) {
select.currentOption = option;
};
/* This is always returning undefined atm, still search for the cause */
coForms.getCurrentOption = function(select) {
return select.currentOption;
};
Directive:
coForms.directive('coSelect', [function() {
return {
restrict: 'E',
scope: {
default: '=',
list: '=',
label: '#'
},
controller: 'CoFormsCtrl',
controllerAs: 'co',
templateUrl: 'app/views/components/co-forms/co-select.html',
link: function(scope, element, attrs) {
}
};
}]);
Directive template:
<div class="co-select-form-control">
<ul class="list-reset co-select">
<li ng-click="co.toggleSelect(this)" class="co-select-option clickable">
<span ng-bind="co.getCurrentOption(this) || default"></span>
<ul ng-show="co.isSelectToggled(this)" class="list-reset bg-light co-select-dropdown">
<li ng-repeat="option in list" ng-if="option !== co.getCurrentOption(this)"
ng-click="co.selectOption(this, option)" ng-bind="option" class="co-select-option"></li>
</ul>
</li>
</ul>
<span class="co-select-icon">
<i class="icon icon-keyboard-arrow-{{co.isSelectToggled(this) ? 'up' : 'down'}}"></i>
</span>
</div>
Then I just use it like this:
<co-select list="video.config.upload.status" default="video.config.upload.status[0]"></co-select>
Change this:
/* This is always returning undefined atm, still search for the cause */
coForms.getCurrentOption = function(select) {
return select.currentOption;
};
To this:
coForms.getCurrentOption = function(select) {
return select.currentOption || coForms.default;
};
In your directive, you have bound the default attribute of the directive's HTML to the default property of your controllers scope.
coForms.directive('coSelect', [function() {
return {
restrict: 'E',
scope: {
default: '=',
list: '=',
label: '#'
},
controller: 'CoFormsCtrl',
controllerAs: 'co',
templateUrl: 'app/views/components/co-forms/co-select.html',
link: function(scope, element, attrs) {
}
};
}]);
I'm using a directive to insert a youtube player in my templates,
app.directive('youtube', function($window, youTubeApiService) {
return {
restrict: "E",
scope: {
videoid: "#"
},
template: '<div></div>',
link: function(scope, element, attrs, $rootScope) {
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var player;
youTubeApiService.onReady(function() {
player = setupPlayer(scope, element);
});
function setupPlayer(scope, element) {
return new YT.Player(element.children()[0], {
playerVars: {
autoplay: 0,
html5: 1,
theme: "light",
modesbranding: 0,
color: "white",
iv_load_policy: 3,
showinfo: 1,
controls: 1
},
videoId: scope.videoid,
});
}
scope.$watch('videoid', function(newValue, oldValue) {
if (newValue == oldValue) {
return;
}
Player.cueVideoById(scope.videoid);
});
}
};
});
My html looks something like this,
<div class="container-info">
<ul class="trailers">
<li>ePbKGoIGAXY</li>
<li>KlyknsTJk0w</li>
<li>nyc6RJEEe0U</li>
<li>zSWdZVtXT7E</li>
<li>Lm8p5rlrSkY</li>
</ul>
<div class="container-trailers">
<youtube videoid="ePbKGoIGAXY"></youtube>
</div>
</div>
What I want to do is click on one of the links and then change the value of videoid so a different youtube link is rendered.
<div class="container-info">
<ul class="trailers">
<li>ePbKGoIGAXY</li>
<li>KlyknsTJk0w</li>
<li>nyc6RJEEe0U</li>
<li>zSWdZVtXT7E</li> <-- clicked element
<li>Lm8p5rlrSkY</li>
</ul>
<div class="container-trailers">
<youtube videoid="zSWdZVtXT7E"></youtube> <-- new youtube link
</div>
</div>
You can use $watch and ng-click:
JSFiddle
HTML:
<div ng-app="app">
<div class="container-info">
<ul class="trailers">
<li>ePbKGoIGAXY</li>
<li>KlyknsTJk0w</li>
</ul>
<div class="container-trailers">
<youtube videoid="videoid"></youtube>
</div>
</div>
</div>
JS:
var app = angular.module("app", []);
app.directive('youtube', function($window) {
return {
restrict: "E",
scope: {
videoid: "="
},
template: '<div></div>',
link: function(scope, element, attrs, $rootScope) {
scope.$watch('videoid', function (newVal, oldVal) {
console.log(newVal);
});
}
};
});
I have troubles to find a solution to make a fully dynamic directive. I use the angular-gridster library to make an overview of tiles for a dashboard page. I want to dynamicly load some specific tiles by a flexible directive.
<div gridster="vm.model.gridsterOptions">
<ul>
<li gridster-item="tile.tileParams.gridParams" ng-repeat="tile in vm.model.dashboards.tiles">
<div class="box" ng-controller="tileGrid_TileController">
<eg-tile template-url={{tile.tileEngine.tempUrl}}
controller-name={{tile.tileEngine.tileCtrl}}>
</eg-tile>
</div>
</li>
</ul>
</div>
I have created the egTile directive :
(function () {
function implementation() {
return {
restrict: 'E',
transclude: true,
scope: true,
controller: '#',
bindToController:true,
controllerAs: 'vm',
name: 'controllerName',
templateUrl: function (elem, attrs) {
return attrs.templateUrl || 'app/module/tileGrid/view/templates/empty.html';
}
};
}
var declaration = [implementation];
angular.module('app.tileGrid').directive('egTile', declaration);
}());
This directive will work if I use a fixed string in the egTile directive like
<eg-tile template-url="string_url" controller-name= "string_ctrl"></eg-tile>
but I want to dynamicly select the controller and templateUrl.
I already tried to use the $parse and $observe service but without succes.
Is this even possible to make the directive so flexible ?
Thanks in advance
I found a solution for my problem.....
I used 2 extra directives that will provide the controller-string and the templateUrl-string for the "flexible directive" egTile.
One for creating the controller-string :
(function () {
function implementation($compile, $parse) {
return {
restrict: 'A',
scope: true,
terminal: true,
priority: 99999,
link: function (scope, elem) {
var name = $parse(elem.attr('eg-parse-controller'))(scope);
elem.removeAttr('eg-parse-controller');
elem.attr('controller-name', name);
$compile(elem)(scope);
}
};
}
var declaration = ['$compile', '$parse', implementation];
angular.module('app').directive('egParseController', declaration);
}());
And one for creating the template-string:
(function () {
function implementation($compile, $parse) {
return {
restrict: 'A',
scope: true,
terminal: true,
priority: 99998,
link: function (scope, elem) {
var name = $parse(elem.attr('eg-parse-template'))(scope);
elem.removeAttr('eg-parse-template');
elem.attr('template-url', name);
$compile(elem)(scope);
}
};
}
var declaration = ['$compile', '$parse', implementation];
angular.module('app').directive('egParseTemplate', declaration);
}());
Than I can use it as :
<div gridster="vm.model.gridsterOptions">
<ul>
<li gridster-item="tile.tileParams.gridParams" ng-repeat="tile in vm.model.dashboards.tiles" ng-controller="tileGrid_TileController">
<eg-tile tile="tile"
eg-parse-template=tile.tileEngine.tempUrl
eg-parse-controller='tile.tileEngine.tileCtrl'>
</eg-tile>
</li>
</ul>
with the directive definition :
(function () {
function implementation($parse) {
return {
restrict: 'E',
transclude: true,
scope: {
tile : '='
},
controller: '#',
bindToController:true,
controllerAs: 'vm',
name: 'controllerName',
templateUrl: 'app/module/tileGrid/view/tileTemplate.html',
link: function(scope, element, attrs) {
scope.getContentUrl = function() {
return attrs.templateUrl || 'app/module/tileGrid/view/templates/empty.html';
}
}
};
}
var declaration = ['$parse' , implementation];
angular.module('app.tileGrid').directive('egTile', declaration);
}());
With a tileTemplate.html :
<div class="box">
<div class="box-header">
<h3>{{ vm.tile.tileParams.title }}</h3>
<div class="box-header-btns pull-right">
<a title="Remove widget" ng-click="vm.removeTile(tile)">
<i class="fa fa-trash"></i>
</a>
</div>
</div>
<div class="box-content">
<div ng-include="getContentUrl()"></div>
</div>
</div>
With this aproach I have fully access to the tile that I passed to the egTile Directive in the dynamic loaded controllers and dynamic loaded views.
All remarks are welcome.
I'm trying to watch a async data in my directive, here is my JS code:
(function(angular) {
var myApp = angular.module('testTree', []);
myApp.config(function($httpProvider) {
$httpProvider.defaults.headers.get = {};
$httpProvider.defaults.headers.get["Content-Type"] = "application/json";
});
myApp.factory('DataService', function($http) {
return { getData: function(prmParentId, prmParentSrc) {
var data = $.param({ 'parentId': prmParentId, 'parentSrc': prmParentSrc });
return $http.get("Temp.aspx/GetData", { params: { parentId: prmParentId, parentSrc: prmParentSrc }, data: '' }).
success(function(result) {
return result.d;
});
}
}
});
myApp.controller('myController', myController);
function myController($scope, DataService) {
$scope.treeNodes = [];
$scope.init = function() {
DataService.getData(0, 0).then(function(promise) {
$scope.treeNodes = promise.data.d;
});
}
$scope.focusNode = function() {
console.log("kuku2");
}
}
myApp.directive('nodes', function() {
return {
restrict: "E",
replace: true,
scope: {
nodes: '=',
clickFn: '&'
},
template: "<ul><node ng-repeat='node in nodes' node='node' click-fn='clickFn()'></node></ul>",
link: function(scope, element, attrs) {
scope.$watch('treeNodes', function(newVal, oldVal) {
console.log(scope.treeNodes);
}, true);
}
}
});
myApp.directive('node', function($compile) {
return {
restrict: "E",
replace: true,
scope: {
node: '=',
clickFn: '&'
},
template: "<li><span ng-click='clickFn()(node)'>{{node.ObjectName}}</span></li>",
link: function(scope, element, attrs) {
if (angular.isArray(scope.node.Children)) {
element.append("<nodes nodes='node.Children' click-fn='clickFn()'></nodes>");
$compile('<nodes nodes="node.Children" click-fn="clickFn()"></nodes>')(scope, function(cloned, scope) {
element.append(cloned);
});
}
}
}
});
})(angular);
And this is my HTML:
<div ng-app="testTree">
<div ng-controller="myController">
<div ng-init="init()">
<nodes node="treeNodes" click-fn="focusNode"></nodes>
</div>
</div>
</div>
The console in the directive's watch is always "undefined". What am I doing wrong here?
thanks.
Pass treeNodes as nodes in your directive. So you need to watch nodes.
scope.$watch('nodes', function(newVal, oldVal) {
console.log($scope.nodes);
}, true);
<div ng-app="testTree">
<div ng-controller="myController">
<div ng-init="init()">
<nodes nodes="treeNodes" click-fn="focusNode"></nodes>
</div>
</div>
</div>