I have a partial view which is included on multiple pages. That Partial view has it's on NgController.
Is there a way I can access a scope variable of that specific nested controller when I'm inside of another?
For example:
<div ng-controller="fooController">
<a ng-click="changeScopeVariableOfBarController()">Click!</a>
<div ng-controller="barController">
{{ thisHasToChangePlease }}
</div>
</div>
Is that possible? I couldn't figure out how. I include the partial view with php's include, so I don't work with templates.
If you have a reusable "component" (template + functionality) you could refactor that into a directive instead of template + controller. Then, using an isolated scope with parameters you could have the outside world change the inside of the directive that way.
http://plnkr.co/edit/6JYVucuizmPkyt2CLQtr?p=preview
index.html
<div ng-controller="fooController">
<div>variable: {{ variable }}</div>
<button ng-click="changeScopeVariableOfBarController()">Click to increment variable on both scopes!</button>
<bar this-has-to-change-please="variable"></bar>
</div>
bar.html
<div>
thisHasToChangePlease: {{ thisHasToChangePlease }}
</div>
app.js
app.controller('fooController', function($scope) {
$scope.variable = 1;
$scope.changeScopeVariableOfBarController = function () {
$scope.variable++;
};
});
app.controller('barController', function ($scope) {
});
app.directive('bar', function () {
return {
restrict: 'E',
scope: {
thisHasToChangePlease: '='
},
controller: 'barController',
templateUrl: 'bar.html'
}
});
Related
I have created a ng-transclude sample which is not working when I use ng-include inside directive template property.Below are the code I have tried
angular.module("app",[]);
angular.module("app").controller('appController', function ($scope) {
$scope.message="Message from controller";
});
angular.module("app").directive('myFrame', function () {
return {
restrict: 'E',
template : '<div ng-include=\"getTemplateUrl()\"></div>',
controller:function($scope){
$scope.hidden=false;
$scope.close=function(){
$scope.hidden=true;
}
$scope.getTemplateUrl=function(){
//dynamic url
return "frame.html";
}
$scope.message="message from directive";
},
transclude:true,
}
});
angular.module("app").directive('mychildFrame', function () {
return {
restrict: 'E',
templateUrl:"childframe.html",
controller:function($scope){
},
}
});
childFrame.html
<h2>I am from Child</h2>
<div></div>
frame.html
<div class="well" style="width:350px;" ng-hide="hidden">
<div style="float:right;margin-top:-15px">
<i class="glyphicon glyphicon-remove" ng-click="close()" style="cursor:pointer"></i>
</div>
<div ng-transclude>
/*frame content goes here*/
</div>
</div>
index.html
<my-frame>
<mychild-frame></mychild-frame>
</my-frame>
https://plnkr.co/edit/O58voI?p=preview
When I change to the property template to templateUrl="frame.html" it's working fine. But the problem is, this HTML page inside the template is dynamic. So I end up with ng-include.
Could you please provide any possible workaround?
When using the templateUrl property you can also pass it a function to dynamically load a template.
There's no need to put the function in a controller, especially since it doesn't really belong there anyway: the controller is supposed to contain view logic, not a function to load the view itself.
I added the function in the templateUrl instead in your plunkr:
templateUrl : function() {
return 'frame.html';
}
https://plnkr.co/edit/HQHI9hkTojkZFK2Gjxfw?p=preview
As you can see this gives you the desired behavior
My main view makes use of a directive called fileUploader. The fileUploader directive uses an ng-repeat to represent a list of objects (file details). I want this file-uploader directive to be reusable, so I wish to place use-specific UI in the view that is using it.
Here is my main view. In this case I want to see the standard file-uploader's template for each item plus two additional pieces of meta data I will find on the object (reference / destinationFolder).
<file-uploader class="col-md-10 file-uploader" file-uploader-upload-url="/x/upload">
<dl>
<dt>Reference</dt>
<!-- Note the binding on the next line, how do I evaluate it to the "item" in the directive's ng-repeat? -->
<dd>{{ item.reference }}</dd>
<dt>Folder</dt>
<dd>{{ item.destinationFolder }}</dd>
</dl>
</file-uploader>
My directive uses ng-transclude to include the <dl> contents above. It seems the {{ item.reference }} is evaluated in the main view and then inserted many times, what I want is for it to transclude it as-is and then evaluate the expression within the context of the directive's ng-repeat. The transclude etc is working correctly, but the binding is not working as I wish.
<ul class="file-upload-list">
<li ng-repeat="item in controller.fileUploader.queue">
<div class="file-upload-item" drag-container drag-data="controller.fileUploader.queue[$index]">
<div class="file-upload-icon">
<img src="/Icons/FileExtension/{{item.file.name | fileExtension}}" alt="Icon" class="file-upload-icon" />
</div>
<div class="file-upload-filename">
<a href="" title="{{ item.file.name }}">
{{ item.file.name | limitTo : -28 }}
</a>
</div>
<!-- Here is where I want the main view's template repeated and data-bound -->
<ng-transclude class="file-upload-meta-data"/>
<div class="progress">
<div class="progress-bar" role="progressbar" ng-style="{ 'width': item.progress + '%' }"></div>
</div>
</div>
</li>
</ul>
Sadly, this is not how ng-transclude transclusion works. What you are trying to do is create a container directive that makes use of its children as a "template" of what to stamp out inside your own directive.
The content that is transcluded is by definition bound to the scope of the place where the directive is instantiated; not to the scope of the directive's template.
So actually you don't really need to use transclusion here as what you really trying to do is simply inject the inner HTML into your own template. You can do this in the compile function like this:
app.directive('test', function(){
return {
restrict: 'E',
compile: function compile(tElement, tAttrs, tTransclude) {
// Extract the children from this instance of the directive
var children = tElement.children();
// Wrap the chidren in our template
var template = angular.element('<div ng-repeat="item in collection"></div>');
template.append(children);
// Append this new template to our compile element
tElement.html('');
tElement.append(template);
return {
pre: function preLink(scope, iElement, iAttrs, crtl, transclude) {
},
post: function postLink(scope, iElement, iAttrs, controller) {
}
};
}
};
});
-- Angular Issue #7874: ng-repeat problem with transclude
The solution was to use an ng-include inside the directive.
First I added an additional attribute on my directive in which the developer can specify the ID of the template, like so:
<file-uploader file-uploader-metadata-template-id="someID" class="col-md-10 file-uploader" file-uploader-upload-url="/x/upload">
Then inside the directive I just needed to use ng-include
<div class="file-upload-meta-data" ng-include="controller.fileUploaderMetadataTemplateId"/>
I can then use the directive like so
<file-uploader class="col-md-10 file-uploader" file-uploader-upload-url="/x/upload" file-uploader-metadata-template-id="file-meta-template"/>
<script type="text/ng-template" id="file-meta-template">
<dl>
<dt>Application</dt>
<dd>{{ item.meta.applicationName }} </dd>
<dt>Reference</dt>
<dd>{{ item.meta.referenceName }} </dd>
<dt>Folder</dt>
<dd>{{ item.meta.targetFolderName }} </dd>
</dl>
</script>
Note that I am binding to a controller, with controllerAs set to "controller" - here is the typescript for those interested....
module MyApp.Directives.FileUploaderDirective {
export class FileUploaderDirectiveController {
public uploadUrl: string;
public metaTemplateId: string;
}
class FileUploaderDirective implements angular.IDirective {
public restrict: string = "E";
public templateUrl: string = "/app/Directives/FileUploaderDirective/FileUploaderDirective.html";
public scope: any = {
uploadUrl: "#fileUploaderUploadUrl",
metaTemplateId : "#fileUploaderMetadataTemplateId"
};
public controller: string = "MyApp.Directives.FileUploaderDirective.FileUploaderDirectiveController";
public controllerAs: string = "controller";
public bindToController: boolean = true;
public transclude: boolean = true;
public replace: boolean = true;
}
export function register(app: angular.IModule) {
app.controller("MyApp.Directives.FileUploaderDirective.FileUploaderDirectiveController", FileUploaderDirectiveController);
app.directive("fileUploader", () => new FileUploaderDirective());
}
}
I am using Bootstrap UI's accordion directive. This uses transclusion under the hood. I have some logic that needs repeated, so I am trying to create a directive that wraps the accordian, which also uses transclusion.
<div>
<div accordion>
<div accordion-group is-open="isOpen">
<div accordion-heading>
<span class="glyphicon" ng-class="{'glyphicon-minus-sign': isOpen, 'glyphicon-plus-sign': !isOpen}"></span>
<strong>{{headerTitle}}</strong>
</div>
<div ng-transclude></div>
</div>
</div>
</div>
Here is the JavaScript:
application.directive('collapsePanel', ['baseUrl', function (baseUrl) {
return {
restrict: 'A',
templateUrl: baseUrl + 'content/templates/collapse_panel.html',
replace: true,
transclude: true,
scope: {
headerTitle: '#'
},
controller: ['$scope', function ($scope) {
$scope.isOpen = false;
}]
};
}]);
It should be as simple to use as:
<div collapse-panel header-title="Title">
{{scopeVariable}}
</div>
Assuming scopeVariable is in my controller, I would think its value would appear. From what I can tell, the scope belongs to the collapse-panel rather than the outer scope. It is almost like having nested transclusion directives is causing my problem.
Is there a trick to nesting transclusions like this?
I am trying to ng-repeat a custom directive, which has an attribute that should change over the iteration.
This is my html:
<div ng-controller="WalletsController as controller">
<bitcoin-address ng-repeat="bitcoin_label in controller.getWallets()" bitcoin-label="bitcoin_label"></bitcoin-address>
</div>
This is my controller:
(function() {
var app = angular.module('wallets', [ ]);
app.controller(
"WalletsController",
function($scope, $http) {
this.wallets = [];
var controller = this;
this.getWallets = function() {
return controller.wallets;
};
$http.get("wallet_addresses").success(
function(data) {
for (var i = 0; i < data.length; i++) {
var curWallet = data[i];
$scope[curWallet.label] = {
label: curWallet.label,
address: curWallet.address,
balance: curWallet.balance
};
controller.wallets.push(curWallet.label);
}
}
);
});
app.directive(
'bitcoinAddress',
function() {
return {
restrict: 'E',
templateUrl: '../../resources/html/bitcoin-address.html',
scope: {
bitcoinLabel: '=',
}
};
}
);
})();
And this is my template:
<div class="col-md-8 dashboardAddressCell dropdown-toggle" data-toggle="dropdown">{{bitcoinLabel.address}}</div>
What happens is that the template can not resolve the bitcoinLabel variable. I have tried specifying a constant value and the template works. My conclusion is that I am not correctly specifying the bitcoin_label attribute in the html section.I have also tried using {{bitcoin_address}}, but angularjs complains about that.
I have also tried with the following html code:
<div ng-controller="WalletsController as controller">
<!-- <tr><th>Indirizzo</th><th>Saldo</th><th></th>-->
<div ng-repeat="bitcoin_label in controller.getWallets()">
{{bitcoin_label}}
<bitcoin-address bitcoin-label="bitcoin_label"></bitcoin-address>
</div>
<bitcoin-address bitcoin-label="ciccio"></bitcoin-address>
</div>
It does not work either, but at least it shows the {{bitcoin_label}} value.
The problem seems pretty simple. Instead of
controller.wallets.push(curWallet.label);
you should push corresponding $scope[curWallet.label] object:
controller.wallets.push($scope[curWallet.label]);
Because curWallet.label is just a string so in the first case wallets ends up as array of stings. However you need wallets to be an array of objects, each having address, label, balance properties.
You have some problems with your logic. You're putting wallet labels into .wallets, then iterating over the labels, and then in your bitcoinAddress template you're trying to read .address property of the label string (not from the object where you saved it). Why not simplify the whole thing to this script:
.controller("WalletsController", function($scope, $http) {
$scope.wallets = [];
$http.get("wallet_addresses").success(function(data) {
$scope.wallets = data.slice();
});
})
.directive('bitcoinAddress', function() {
return {
restrict: 'E',
templateUrl: '...',
scope: {
wallet: '=',
}
};
})
this directive template:
<div class="..." ...>{{wallet.address}}</div>
and this body template:
<div ng-controller="WalletsController as controller">
<bitcoin-address ng-repeat="wallet in wallets" wallet="wallet"></bitcoin-address>
</div>
Both bitcoinAddress and ng-repeat directives creating scopes on the same element could cause some conflict (isolate scope in the bitcoinAddress case).
Try adjusting your html structure slightly:
<div ng-controller="WalletsController as controller">
<div ng-repeat="bitcoin_label in controller.getWallets()">
<bitcoin-address bitcoin-label="bitcoin_label"></bitcoin-address>
</div>
</div>
Why not use $scope.wallets instead of this.wallets? Also in your getWallets function. After that try
<div ng-controller="WalletsController">
<div ng-repeat="bitcoin_label in wallets">
<bitcoin-address bitcoin-label="bitcoin_label"></bitcoin-address>
</div>
</div>
But if your wallets is an array of non-object like array of string or integer, use
<div ng-controller="WalletsController">
<div ng-repeat="bitcoin_label in wallets track by $index">
<bitcoin-address bitcoin-label="wallets[$index]"></bitcoin-address>
</div>
</div>
I think it should be easy to use the well known angular attributes on a directive out of the box.
For example if the name of my directive is myDirective I would like to use it this way:
<div ng-controller="myController">
<my-directive ng-click="doSomething()"><my-directive>
</div>
instead of needing to define a custom click attribute (onClick) as in the example below
<div ng-controller="myController">
<my-directive on-click="doSomething()"><my-directive>
</div>
It seems that ng-click can work, but then you need to specify ng-controller on the directive tag too which I don't want. I want to define the controller on a surrounding div
Is it possible to use ng-click on a directive together with a controller defined on a parent html element?
Here is updated code. Maybe is this what you were looking for.
Html:
<div data-ng-app="myApp">
<div data-ng-controller="MyController">
<my-directive data-ng-click="myFirstFunction('Hallo')"></my-directive>
<my-directive data-ng-click="mySecondFunction('Hi')"></my-directive>
</div>
</div>
Angular:
var app = angular.module('myApp', []);
app.directive('myDirective', function(){
return {
restrict: 'EA',
replace: true,
scope: {
eventHandler: '&ngClick'
},
template: '<div id="holder"><button data-ng-click="eventHandler()">Call own function</button></div>'
};
});
app.controller('MyController', ['$scope', function($scope) {
$scope.myFirstFunction = function(msg) {
alert(msg + '!!! first function call!');
};
$scope.mySecondFunction = function(msg) {
alert(msg + '!!! second function call!');
};
}]);
Edit
Check solution that I made in jsFiddler is that what you were looking for?
http://jsfiddle.net/migontech/3QRDt/1/