Angular - Directive - retain parent scope plus attribute value - angularjs

So, I am looking to write a directive that displays a given structure. These structures can be embedded inside one another so the directive simply needs to read in the structure so that it can find all of its children and display them accordingly. If one of those children is another structure, then the directive is called again with the new structure.
This is pretty standard, however I want to do not want to create a new scope since I need to call functions from the main scope.
Can I retain the original scope and still pass in information through the attributes? Is there some other way of doing this?
What I have done is below:
Call the structureContent directive with the original structure.
The directive is a template that displays the properties of the structure depending on its type. It then gets its children and if one of those children is a structure then the directive is called again.
Like I said, this isn't the problem. The problem is that I need to retain the original scope as well since I need to call functions from there.
I cannot simply call the parent as there could be multiple levels of embedded structures.
Thanks
HTML
<div layout="row" flex="100" layout-align="center start" layout-fill>
<div flex="75" class="borderRight" layout-fill>
<div layout="column" layout="start center" class="fullWidth">
<structure-content struct="vm.currentItem"></structure-content>
</div>
</div>
</div>
</md-content>
Directive
(function ()
{
'use strict';
angular
.module('app.core')
.directive('structureContent', structureContent);
/** #ngInject **/
function structureContent($templateRequest, $compile)
{ return {
restrict : 'E',
controller : ["$scope", "$element", "$attrs",
function($scope, $element, $attrs) {
$scope.$watch($attrs.struct,
function(){
$scope.structObj = $attrs.struct;
}
);
}
],
link : function(scope, element, attrs){
$templateRequest("app/main/apps/document/worksheet/views/templates/multSpec/structureContentSection.html").then(function(html){
var template = angular.element(html);
element.html(template);
$compile(template)(scope);
});
}
}
};
})();
TEMPLATE CALLED
<div layout="column"
flex="100"
class="itemListSection" >
<div class="md-3-line"
ng-if="structObj.vartype=='6'"
ng-click="vm.currentStructure=scope.thisObj; vm.currentSubItem=scope.thisObj;"
layout="row">
<for-loop-list structObj="structObj" layout-fill></for-loop-list>
</div>
<div class="md-3-line"
ng-if="structObj.vartype=='7'"
ng-click="vm.currentStructure=scope.thisObj; vm.currentSubItem=scope.thisObj;"
layout="row">
<while-loop-list structObj="structObj" layout-fill></while-loop-list>
</div>
<div class="md-3-line"
ng-if="structObj.vartype=='8'"
ng-click="vm.currentStructure=scope.thisObj; vm.currentSubItem=scope.thisObj;"
layout="row">
<if-else-loop-list structObj="structObj" layout-fill></if-else-loop-list>
</div>
<div
ng-repeat="thisObj in vm.cadwolf_worksheet | orderBy:'location' track by thisObj.itemid"
ng-switch on="thisObj.vartype"
ng-if="thisObj.parentid==structObj.itemid"
class="subSection"
ng-click="vm.currentSubItem=thisObj; vm.currentStructure=thisObj" >
<div ng-switch-when="3"><equation-list equationobject="structObj"></equation-list></div>
<div ng-switch-when="6"><structure-content structObject="thisObj"></structure-content></div>
<div ng-switch-when="7"><structure-content structObject="thisObj"></structure-content></div>
<div ng-switch-when="8"><structure-content structObject="thisObj"></structure-content></div>
</div>

Related

Passing variables to directive

I'm hoping someone can help me get my head around passing a variable into a directive. I've looked at other answers to what seem like similar questions to mine, but none seem to apply directly and/or I don't understand them.
So, my problem is that I have a toolbar that I want to be able to have access to variables found in different controllers. Not sure if this is even possible.
This is my directive (the scope and link options are all wrong, so they are here just for show):
.directive('toolbar', function(){
return {
restrict: 'E',
scope: {
page: '='
},
templateUrl: '/templates/toolbar.html',
link: function(scope, elem, attrs) {
scope.page = vm.page;
},
replace: false
};
})
I want to use it once in index.html, like this:
<body ng-app="app" ng-cloak layout="column">
<div layout-align="center center" layout-margin flex="50">
<img src="/images/logo.png" class="logo">
</div>
<toolbar page="{{vm.page}}"></toolbar>
<md-content>
<div ui-view ng-view></div>
</md-content>
</body>
where vm.page is a variable found in the controller that drives the ui-view, which is set up like this...
angular
.module('app')
.controller('dogsCtrl', function(dogsFactory, sessionService,
searchService, locationService, adoptableService, toastService,
errorHandlerService, $document, $mdSidenav, $scope, $state, $q) {
var vm = this;
vm.page = 'Home';
vm.currentUser = sessionService.getUser(); ....
I need to be able to access vm.page and the vm.currentUser object in a sub-directive on the toolbar. The toolbar template looks like this:
<md-toolbar class="md-menu-toolbar" hide show-gt-xs>
<div layout="row" layout-align="space-between center">
<div class="page-title" flex hide show-gt-md>
{{ vm.page }}
</div>
<div class="main-menu">
<md-menu-bar>
<menu></menu>
</md-menu-bar>
</div>
<md-input-container class="search">
<md-select name='breed' ng-model="breed._id" placeholder="Select a breed" ng-change="vm.getDogDetail(breed._id)">
<md-option value="{{breed._id}}" ng-repeat="breed in vm.dbBreeds"> {{ breed.breed }}</md-option>
</md-select>
</md-input-container>
</div>
As it stands now, I have to repeat the toolbar directive on every page, but I would rather not do it that way. Is this possible?
It should be like this
<toolbar page="vm.page"></toolbar>
because the page is two way binding.
OR
change the page scope type to this
restrict: 'E',
scope: {
page: '#'
},

AngularJs - Directive - Elements are with the curly braces

I have created the directive with the isolated scope.
In view, I am using controller as property.
When I call the directive with the ng repeat and the isolated scope, the elements are with the curly braces. Not updating the element with the model value.
When I debug the directive, I am getting the same curly braces.
<ul id="ul-data-repeat" my-directive>
<li title='test'>
<span>{{homeCtrl.ngRepeatDatasource.name}}</span>
</li>
<li ng-repeat='item in homeCtrl.ngRepeatDatasource.values'>
{{item.name}}
</li>
</ul>
Just for the sample I have added this code. This is the logic.
In my actual scenario, I will call the jquery plugin.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://code.angularjs.org/1.5.8/angular.js"></script>
<script>
angular.module('myApp', [])
.controller('homeController',function($scope){
var vm=this;
vm.data={
name:'Name'
, values:[{val:'values1'},{val:'values2'}]
}
}).directive('myDirective',['$injector',function($injector){
return{
scope: {
data:'=data'
},
link: function ($scope, $element, $attributes) {
debugger
$($element).html($($attributes.myDirective).html());
}
}
}]);
</script>
<div ng-app='myApp'>
<div ng-controller='homeController as homeCtrl'>
<div>
<h1>Without directive</h1>
<div id='data'>
{{homeCtrl.data.name}}
<div ng-repeat='item in homeCtrl.data.values'>
{{item.val}}
</div>
</div>
</div>
<div>
<h1>With directive</h1>
<div my-directive='#data'>
</div>
</div>
</div>
</div>
how to get the updated value?
If you are using scope you can send your data to directive. And you want to use your data without braces you need to compile it with your current scope. This means you need to send your scope to directive again.
1. Data Send
<div my-directive='item'>
</div>
link: function ($scope, $element, $attributes) {
console.log($scope);
}
2. Compile data
<div my-directive='item'>
</div>
link: function ($scope, $element, $attributes) {
$compile($($attributes.myDirective).html())($scope);
}
3. Bind data before run directive
<div ng-bind="item.val" id="#data" myDirective="#data">
</div>
data:'#',
link: function ($scope, $element, $attributes) {
$($element).html($($attributes.myDirective).html());
}

angular material flex in ng-repeat element

I am constructing a dynamic table using angular material row and column layouts. I am using ng-repeat to construct the columns based on pre defined columns in an array of objects called scope.tableLayout. Each object has a flex value. What I would like to do is set the table column width based on the flex value in the object. Is there a way to do this?
My JSfiddle of the table attached where I've tried to set the flex as flex="col.flex" with no luck. Any help is appreciated.
My Code in JSfiddle
<div ng-app="myApp" ng-controller="mainCtrl">
<script type="text/ng-template" id="/template">
<button ng-click="testFn()">Test</button>
<div layout="row">
<div flex='col.flex' ng-repeat="col in column"><span>HEADER{{$index}}</span>
<div layout="column">
<div flex style="border: 1px solid black;" ng-repeat="row in [1,2,3]">{{$index}}</div>
</div>
</div>
</div>
</script>
<form-table table-layout=tableLayout|filter:{table_id:1}></form-table>
</div>
var app = angular.module('myApp', ['ngMaterial']);
app.controller('mainCtrl', function($scope) {
$scope.tableLayout =[{"head_id":"GAP Assessment","table_id":"1","table_name":"GAP Table","element_id":"0","element_name":"Action Reference","sort_order":"0","is_multirow":"1","flex":"30","element_sort_order":"4","is_show":"0"},{"head_id":"GAP Assessment","table_id":"1","table_name":"GAP Table","element_id":"1","element_name":"Audit Criteria","sort_order":"0","is_multirow":"1","flex":"30","element_sort_order":"0","is_show":"1"},{"head_id":"GAP Assessment","table_id":"1","table_name":"GAP Table","element_id":"3","element_name":"Document Reference","sort_order":"0","is_multirow":"1","flex":"10","element_sort_order":"3","is_show":"1"}]
});
app.directive('formTable', function() {
return {
scope:{tableLayout:'&'},
link: function(scope,element,attrs){ // normal variables rather than actual $scope, that is the scope data is passed into scope
scope.column=scope.tableLayout();
scope.testFn=function(){
console.log(scope.tableLayout());
}
//function and scopes go here
},//return
transclude:true,
templateUrl: '/template',
restrict: 'E'
}
})
As soon as I posted this problem I found my issue - need to use handlebars!
flex = "{{col.flex}}"
It now works.
Working Fiddle

AngularJS: Access models using dynamic names in template

I have a template with a lot of what is essentially duplicate code. I'd like to use a directive to include a partial template which I can manipulate for each "block" of duplicate code.
The template currently looks something like this:
<div class="column book">
<div class="header">
<input type="text" id="book_query" ng-model="book_query.name" />
</div>
<div class="content">
<div class="row" ng-repeat="book in books | filter:book_query">
{{book.name}}
</div>
</div>
</div>
....
<div class="column game">
<div class="header">
<input type="text" id="game_query" ng-model="game_query.name" />
</div>
<div class="content">
<div class="row" ng-repeat="game in games | filter:game_query">
{{game.name}}
</div>
</div>
</div>
....
And the controller just gets the data and adds it to the scope e.g.
$scope.books = data.books;
$scope.games = data.games;
What I started doing was using a directive which takes in an argument (e.g. book, game etc) so I then knew which model(s) to use. The problem I have is how to use the argument to access the model in the template? The directive itself is, currently, very simple:
<div item-column item="book"></div>
<div item-column item="game"></div>
app.directive('itemColumn', function() {
return {
scope: {
item: '#'
},
replace: true,
templateUrl: 'item_column.html'
};
});
In item_column.html, I was hoping I could just substitute the item argument, which works fine for displaying the value of the arg but not for replacing where 'book' or 'game' is used for the models e.g.
<div class="column {{item}}">
<div class="header">
<input type="text" id="{{item}}_query" ng-model="{{item}}_query.name" />
</div>
<div class="content">
<div class="row" ng-repeat="item in ??? | filter:{{item}}_query">
{{item.name}}
</div>
</div>
</div>
Can someone show me the best way of doing this? I don't doubt I'm going the complete wrong way about it!
EDIT: The original issue above is now pretty much fully solved using JoseM's answer below. The one outstanding issue is the on-click functions on each element no longer firing the parent scope from within the isolated scope.
My controller is laid out like so:
app.controller('ItemsCtrl', ['$scope', '$http', 'CONFIG', function($scope, $http, CONFIG) {
var items = ['books', 'games'];
items.forEach(function(item) {
$scope[item] = [];
$scope['selected_'+item] = null;
})
$scope.getItem = function(item) {
$http.get('?action=get_item&id='+item.id+'&type='+item.type)
.success(function(data) {
// update model
})
.error(function(data, status) {
// do something
});
}
}]);
$scope.getItem is no longer accessible when clicking on the item in the view, which looks similar to the following after implementing JoseM's answer:
<div class="row" ng-repeat="item in array | filter:query">
<div class="text" ng-click="getItem(item)">
{{item.name}}
</div>
</div>
Is there a simple way of making the parent scope functions available from within the isolated scope? Or is there a better place for these functions? Apologies for (what I feel are) the very basic questions - I'm still trying to get my head around Angular!
One way to accomplish what you want is by using a child scope in your directive and then doing your own "linking" of the parent scope variables using a watch on the parent scope value.
in your directive:
app.directive('itemColumn', function() {
return {
scope: true,
templateUrl: 'item_column.html',
link: function(scope,elem,attrs) {
var varName = scope.varName = attrs.item;
var parScope = scope.$parent;
parScope.$watch(varName + 's', function(newVal){
scope.theArray = newVal;
});
parScope.$watch(varName + '_query', function(newVal){
scope.theQuery = newVal;
});
}
};
});
in your template:
<div class="column {{varName}}">
<div class="header">
<input type="text" ng-attr-id="{{varName}}_query" ng-model="theQuery.name" />
</div>
<div class="content">
<div class="row" ng-repeat="item in theArray | filter:theQuery">
{{item.name}}
</div>
</div>
</div>
If you want to use an isolated scope, you could it, but then you would have to supply at least 3 attributes if you are using the same properties as above. I personally believe that using an isolated scope is a better way of doing it. See below how the isolated version is simpler:
isolated version of directive
app.directive('itemColumn2', function() {
return {
scope: {
label: '#',
array: '=',
query: '='
},
templateUrl: 'item_column2.html'
};
});
isolated version of template
<div class="column {{label}}">
<div class="header">
<input type="text" ng-attr-id="{{label}}_query" ng-model="query.name" />
</div>
<div class="content">
<div class="row" ng-repeat="item in array | filter:query">
{{item.name}}
</div>
</div>
</div>
usage
<div item-column2 label="book" array="books" query="book_query"></div>
<div item-column2 label="game" array="games" query="game_query"></div>
And finally here is a sample plunker: http://plnkr.co/edit/OyEHR4ZhzYKvs4jeDfjD?p=preview

AngularJS directive binding issue

I have created an AngularJS directive on one of the pages and it's not rendering. I don't get any errors either. Following is the directive template and directive binding code. Appreciate if someone can help.
directive.js
var cardCollapsiblePanelModule = angular.module('cardCollapsiblePanelModule',[]);
cardCollapsiblePanelModule.directive('cardCollapsiblePanel', function() {
return {
restrict: 'A',
templateUrl: 'scripts/virtualserver/virtualserverfeatures/monitoringprobes/views/directive- templates/monitoring-probe-card-collapsible-panel-template.html',
scope: {
cardName: '#cardName'
},
controller: function ($scope, $element, $attrs) {
// update dependent scope.
},
link: function (scope, $element, $attrs) {}
}
});
directive HTML:
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title clearfix" data-toggle="collapse" data-parent="#accordion" href="#collapseOne">
<div class="pull-left">{{ cardName }}</div>
<div class="pull-right" ><span class="iconSmall iconExpand"></span></div>
</h4>
</div>
<div id="collapseOne" class="panel-collapse collapse">
<div class="panel-body">
<!-- load the capture card settings directive here -->
</div>
</div>
</div>
directive is being used as follows:
<div class="panel-group" id="accordion">
<!-- accordion directive for collapsible panel -->
<div card-collapsible-panel card-index="{{$index}}" ng-repeat="card in monitoringProbeCards">
</div>
</div>
My objective is to bind the headers of collapsible panels with the card name through the directive. Can someone point out what's wrong with the code?
My REST service response is as follows:
[{"id":1,"cardName":"Card 0 : PIST-16-TDM-PCI-Basic-32 - SN: GPER190201064","interfaceType":"TDM","probeServerId":2},{"id":2,"cardName":"Card 1 : PIST-16-TDM-PCI-Basic-32 - SN: GPER190201065","interfaceType":"TDM","probeServerId":2}]
Try this...
<div class="panel-group" id="accordion">
<!-- accordion directive for collapsible panel -->
<div ng-repeat="card in monitoringProbeCards">
<div card-collapsible-panel="{{$index}}"></div>
</div>
</div>
I think you need to add card-name to the directive Div:
<div card-collapsible-panel card-index="{{$index}}" ng-repeat="card in monitoringProbeCards" card-name="{{card.cardName}}" >
Binding to {{cardName}} from the directive HTML, binds to the cardName field on the isolated scope which in turn via the # sign followed by cardName binds to the card-name attribute on the directive using one-way data binding which means that the value of that attribute needs to be evaluated by Angular, hence the {{card.cardName}}.

Resources