I am wondering, is it possible to pass an $index param into the ng-transclude? I am trying to focus into the textarea by clicking on the elements in the ng-transclude, triggering a function inside the controller that sees the textarea, but I can't get the right ID.
<div
ng-repeat="locale in $ctrl.enabled">
<table>
<tbody>
<tr>
<td flex layout="row" layout-align="start end">
<ng-transclude
ng-transclude-slot="theExtraMenu">
</ng-transclude>
</td>
</tr>
<tr>
<td>
<md-input-container
md-no-float="true">
<textarea id="{{'textarea'+$index}}">
</textarea>
</md-input-container>
</td>
</tr>
</tbody>
</table>
</div>
So in the end, I created a parent component that has the locale as an input and transcludes all of the necessary content. Then I require the controller of said component and that's how I see the value of locale.
The reason I didn't go with the scope parent access was that if the hierarchy of the scope changed, it got broken.
Thanks #georgeawg for inspiration.
EDIT: example -
<div ng-repeat="locale in $ctrl.enabled">
<translated-textarea-menu locale="locale">
<the-extra-menu>
<ng-transclude ng-transclude-slot="theExtraMenu"></ng-transclude>
</the-extra-menu>
</translated-textarea-menu>
The parent component (translated-textarea-menu)
component: {
templateUrl: 'xyz.html',
bindings: {
locale: '&',
},
transclude: {
theExtraMenu: '?theExtraMenu'
},
controller: TranslatedTextareaMenuController,
controllerAs: '$ctrl',
}
And then in the transcluded component (the extra menu component)
component: {
templateUrl: 'x.html',
bindings: {
variables: '&'
},
require: {
translatedTextareaMenu: '^translatedTextareaMenu'
},
controller: TheExtraMenuController,
controllerAs: '$ctrl',
}
And access it with this.translatedTextareaMenu.locale
Directives can require the controllers of other directives to enable communication between each other. This can be achieved in a component by providing an object mapping for the require property. The object keys specify the property names under which the required controllers (object values) will be bound to the requiring component's controller.
For more information, see
AngularJS Developer Guide - Intercomponent Communication
AngularJS Comprehensive Directive API Reference - require
Related
I've been looking all over the internet for something like this and I still can't find the answer.
I have a directive that is reused throughout my application, it allows the user to sort and search through lists of items. I have multiple kinds of items that can be passed in to the directive (html templates that I pass in) as well as multiple uses for those templates. I.e, sometimes I want a certain button on that template, but sometimes I want another. (This will make more sense in a minute).
Therefore I have created multiple directives with transclusion in order to achieve this. However, I'm having serious issues with scoping and I can't seem to figure out exactly how to pass the isolated scope to the child directive.
Below is my code:
Item List Directive
var app = angular.module('main');
app.directive('itemList', function(){
var linkFunction = function (scope, element, attributes, ctrl, transclude) {
//Things that modify the scope here. This scope is what I want to pass down to the child directives
//NOTE: I do not currently have a working transclude function which is why I didn't include it here because I have no idea what to do with it
scope.pagedItems = groupItemsToPages(items);
scope.currentPage = 0;
};
return {
restrict: 'E',
replace: 'true',
transclude: true,
templateUrl: 'partials/directives/item-list.html',
link: linkFunction,
scope: {
searchPlaceholder: "#",
items: "=",
control: "="
}
};
});
item-list.html
<div class="form-group">
<!-- I won't put all of the html here, just some to show you what i'm going for -->
<div class="search-field">
<input type="text" ng-model="query.value" placeholder="{{searchPlaceholder}}/>
</div>
<table class="table table-hover">
<tbody>
<tr ng-repeat="item in pagedItems[currentPage]">
<td ng-transclude></td>
</tr>
</tbody>
</table>
</div>
Here's the directive that simply returns the URL of whatever template is passed to it. This is so that I can add in an extra html through further nested transclusions.
item-template.js
var app = angular.module('main');
app.directive('itemTemplate', function() {
return {
restrict: 'AE',
replace: 'true',
transclude: true,
templateUrl: function(tElement, tAttrs){
return tAttrs.templateUrl;
}
};
});
Here's an example template (extremely simplified again, just to show you the layout)
profile-template.html
<div>
<p>item.name</p>
<p>item.description</p>
</div>
<div ng-transclude></div>
Here's an example of the HTML that calls this code
tab.html
<div class="tab">
<div class="available-items">
<item-list control="profileControl" search-placeholder="Search Profiles" items="profileControl.profiles">
<item-template template-url="partials/profile-template.html">
<button type="button" ng-click="launchProfile(item.id)">Launch Profile</button>
</item-template>
</item-list>
</div>
</div>
So now that you've seen the code. The issue I'm having is that my profile-template.html inclusion isn't getting the scope from the directive above it even though I've tried cloning the scope to it. All the examples I've seen require you to remove the template key from the directive and assume you're only returning the code you have in your transclusion. In my case, I have other html that I want to display in the template.
Any help would be appreciated
Instead of trying to pass the scope between your directives, you can make use of the $parent attribute and get access to higher scopes.
For instance, in your item-template controller you could gain access to the higher scope from item-list with a code like this:
if ($parent.searchPlaceholder === '') {
...
}
I have a little widget I'd like to use over and over on a single page. It has its own controller. Problem is it needs a piece of data to operate (basically a key), and each key is contained in the parent controller.
Here is an example (which is obviously wrong)
http://plnkr.co/edit/VajgOr1LqpLDnbEJcvor?p=preview
script:
angular.module('myApp', [])
.controller('ParentCtrl', ['$scope',
function($scope) {
$scope.keyForChartABC = "somekey1";
$scope.keyForChartXYZ = "somekey2";
$scope.keyForChartLALA = "somekey3";
}
])
.controller('ChartCtrl', ['$scope',
function($scope) {
//todo: have $scope.key assigned from parent somehow
//not shown: use $scope.key to pull data and format chart data
}
])
index:
<!-- ng-init like this is quite wrong -->
<div ng-init="key = keyForChartABC"
ng-include="'chartwidget.html'"></div>
<hr>
<div ng-init="key = keyForChartXYZ"
ng-include="'chartwidget.html'"></div>
<hr>
<div ng-init="key = keyForChartLALA"
ng-include="'chartwidget.html'"></div>
chartwidget:
<div ng-controller="ChartCtrl">
<p>Drawing chart for data: {{key}}</p>
<p>some chart directive here</p>
</div>
As you can see in the plunker, what I tried here with ng-init doesn't work - key for all the sub-controllers end up with the same value.
I've gotten this to work with ng-repeat and an array of data in the parent, somehow $index gets set in each child to the right index and stays that one value. But I'd like to avoid using ng-repeat in this case so I can have more control of the layout.
Creating re-usable widgets is exactly the purpose of Directives. You can create a directive which handles the output of your widget quite easily.
I forked your plunker and modified it to change it to use a directive.
Here are a few highlights:
First, your template no longer needs the controller defined within it.
<div>
<p>Drawing chart for data: {{key}}</p>
<p>some chart directive here</p>
</div>
Next, the directive is defined, with an isolate scope which is unique to each instance of the directive:
.directive('chartWidget', function(){
return {
restrict: 'E',
scope: {
key: '='
},
templateUrl : 'chartwidget.html'
}
})
Lastly, the directive is declared in the HTML. Note the camel-case name of the directive in the JavaScript, but the hyphenated name in the HTML:
<div>
<chart-widget key="keyForChartABC"></chart-widget>
<hr>
<chart-widget key="keyForChartXYZ"></chart-widget>
<hr>
<chart-widget key="keyForChartLALA"></chart-widget>
</div>
Edit
I updated the plunker to show binding the directive property to an inner controller. This method uses the ControllerAs syntax to define the controller, and binds the directive's scope to the controller scope.
Relevant changes:
.directive('chartWidget', function(){
return {
restrict: 'E',
scope: {
key: '='
},
templateUrl : 'chartwidget.html',
controller: 'chartWidgetController',
controllerAs: 'ctrl',
bindToController: true
}
})
.controller('chartWidgetController', function(){
console.log(this.key);
})
And a small change to the template to support ControllerAs:
<div>
<p>Drawing chart for data: {{ctrl.key}}</p>
<p>some chart directive here</p>
</div>
Note that trying to use ng-controller= in the template will cause the template to have a different scope object from the scope object created for the directive, and the controller would not have access to the properties defined on the directive.
Also note, bindToController is a feature of angular 1.3.x or higher. in angular 1.2.x or earlier, your only option was to use $scope.$watch to monitor the isolate scope for changes.
So I am basically creating a table with nested ng-repeat.
<div ng-controller="PresetManageController">
<table>
<in-preset ng-repeat="preset in presetManage.presets"></in-preset>
</table>
</div>
And the template of directive in-preset is
<tr>
<td>{{preset.name}}</td>
<td>一些属性</td>
<td>
编辑
删除
</td>
</tr>
Directive declaration
module.directive('inPreset', function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'scripts/directive-templates/in-preset.html'
}
});
But when I open the page, I found that all trs jumped outside of the table.
If I change table to div, then it works fine. I have checked all html tags are perfectly closed. It is damn hard to debug this issue. Any clues?
The issue is because you are using ng-repeat on a custom directive (set to replace). There are a few weird behaviors with ng-repeat and replacing directives (see related post).
Try to leave the <tr> in your html and change your directive to be on an attribute instead.
html
<table>
<tr in-preset ng-repeat="preset in presets"></tr>
</table>
js
.directive('inPreset', function() {
return {
restrict: 'A',
template: '<td>{{ preset.name }}</td>',
};
})
See the Fiddler
Let's say i have a <lightbox> directive in AngularJS. The lighbox has two params: title and image_source, which in turn are referenced in its HTML with {{title}} and {{image_source}}.
Because I use Bootstrap's modal, the directive must say on the outmost scope of the page (if I insert it in the inner DOM elements it can have display problems).
I understand that if the directive is in a "parent" controller, there are many ways to interact:
<div ng-controller="ParentCtrl">
<lightbox src='mySrc' ... >
</div>
but I am forced to pull it out:
<div ng-controller="ParentCtrl">
</div>
<lightbox ...>
(Edit: to clarify: i am forced to put the modal OUT of the controller (which is a template called by a route) because it interacts badly if it is not. Another way would be to wrap all the page in a Main controller, but I'm not sure it's elegant.)
But how do I access the internal directive scope and change dynamically the two values?
(Edit: thought of using a service, not sure if it is the right way, though)
this is method to call rootscope to directive
angular.module("directiveAPP", [])
.controller("directiveController", function ($scope, $rootScope) {
$rootScope.src = "http://picbook.in/wp-content/uploads/2014/07/image_123.jpg";
$rootScope.title = "hai";
}).directive("lightBox", function () {
return {
restrict: 'E',
template: '<div><img src="{{imageSrc}}" title="{{imageTitle}}" width="100px" style="border:solid #ddd 1px;" /></div>',
scope: {
imageSrc: '=',
imageTitle: '='
}
};
});
this one use of directive
<div ng-app="directiveAPP">
<div ng-controller="directiveController">
<light-box image-src="src" image-title="title"></light-box>
</div>
<light-box image-src="src" image-title="title"></light-box>
</div>
FIDDLE LINK
I may be a bit confused about scope (I've read a lot of tutorials about it, but at this point I don't understand how to make this thing), anyway, this is what I'm trying to do:
I have a table with various cells
<table my-table>
<tr>
<td my-table-cell>
<span ng-if="!myTableCellStatus.editing">Bla 1</span>
<input ng-if="myTableCellStatus.editing" type="text" name="value" value="Bla 1">
</td>
<td my-table-cell>
<span ng-if="!myTableCellStatus.editing">Bla 2</span>
<input ng-if="myTableCellStatus.editing" type="text" name="value" value="Bla 2">
</td>
</tr>
<tr>
<td my-table-cell>
<span ng-if="!myTableCellStatus.editing">Bla 3</span>
<input ng-if="myTableCellStatus.editing" type="text" name="value" value="Bla 3">
</td>
<td my-table-cell>
<span ng-if="!myTableCellStatus.editing">Bla 4</span>
<input ng-if="myTableCellStatus.editing" type="text" name="value" value="Bla 4">
</td>
</tr>
</table>
And my-table-cell directive
angular.module('myApp').
directive('myTableCell', [
->
require: '^myTable'
restrict: 'AC'
scope: {}
link: ($scope, element, attrs, myTable) ->
# XXX: I have some code here that allows me to access the cell from myTable
# I change the status from there
myTable.addCell(
changeStatus: (status) ->
$scope.$apply ->
$scope.myTableCellStatus.editing = status
cell: element
)
$scope.myTableCellStatus =
editing: false
])
Now, my idea was: make my-table-cell a directive with isolated scope. Then access it in its child to show/hide various elements.
Is this possible? In my code, looks like I can't in any way bind to any value of my-table-cell scope, probably because it's isolated.
Considering I'm confused, a code example about it would be lovely, I initially thought this would be the right approach, but now looks like I'm completely wrong.
Isolate scope is good most of the time, but in your case it won't help. If you want to have HTML inside your directive and you want to expose custom properties to those HTML elements (e.g. myTableCellStatus) - and since this HTML is not coming from your directive's template - you need a "normal" (i.e. non-isolate) scope:
app.directive('myTableCell', function () {
return {
...
scope: true,
link: function postLink(scope, element, attrs, myTable) {
...
scope.myTableCellStatus = {editing: false};
}
};
});
See, also, this short demo.
When you define an isolated scope for your directive, you are defining a private sandbox for your scope variables. Variables defined or referenced from the isolated scope will not use scope inheritance to resolve bindings.
To fix this, you can either pass in your models into your isolated scope from your parent scope through the element attributes. In your directive definition:
scope: {
myTableCellStatus: '='
}
This sets up a two-way model binding between the myTableCellStatus variable resolved on parent scope and the scope variable on your isolated scope with the same name.
Or you can reference $parent inside your link function:
$scope.$parent.myTableCellStatus
This works because you are referencing the parent scope from your isolated scope, which does use scope inheritance to resolve bindings.
[EDIT]
It might be obvious now that if you reference $parent, you are reaching out from your isolated scope and into your parent scope, which if shared will bind to the same model across all your directives. This would work if you're inside a an ng-repeat, since it creates a child scope. But if you're not inside a child directive, the model would be shared.