Accessing directive scope from a separate controller - javascript

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

Related

Is there a way to reload an angular.js directive outside of the ui-view?

I'm working on an app that has a component outside the ui-view (navigation bar). This component includes a directive that should add a listener to some async event that happens after $location changes. I have the same directive in numerous places within the ui-view. The problem is that while the directives within the ui-view are re-rendered with every $location change, the directive within the navigation bar doesn't re-render and the listener isn't assigned.
Any idea how to force directive re-rendering?
Index.html:
<body ng-app="myapp">
<nav class="cf" ng-include="'app/components/registration/nav.html'"></nav>
<div ui-view autoscroll="true"></div>
</body>
nav.html:
<div ng-controller="RegistrationController">
<my-directive></my-directive>
</div>
directive.js:
function myDirective(WSService, ShoutingRoomService, VIDABOO_CONSTANTS, $rootScope, $scope) {
return {
scope: {},
replace: true, // Replace with the template below
bindToController: false,
link: function(scope, element, attrs) {
$rootScope.$on('SomeAsyncEvent', function(event, data) {
WSService.doWhenSomeAsyncEventHappens(function () {
changeSomethingVisibleInHTMLTemplate();
}) ;
});
},
template: '<div>ChangeThisWhenAsyncEventHappens</div>'
};
};
Have you looked into using $compile to force Angular to relink $scope and your template together?
Quick summary of $compile:
The compilation is a process of walking the DOM tree and matching DOM
elements to directives.
That would be my suggestion. Try adding something like this to your code:
$compile(angular.element('div.someElement').contents())($scope);

AngularJS Directive Isolated scope inside nested ng-transclude

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 === '') {
...
}

Ng-repeat into a directive with a string & ng-repeat property

I'm trying to dynamically build an encoded query in an ng-repeat that will then be passed into a typeahead directive.
I know I can pass in the entire ng-repeat item similar to this jsfiddle:
http://jsfiddle.net/yaroslavya/8YEkh/
And I know you can easily pass a string into a directive attribute.
Is there a way to do both?
Basically in the fiddle above i'd want to do something like:
<div ng-repeat="image in images">
<typeahead-directive image='"encoded_query=" + image' ></div>
</div>
I tried doing this and it works in the console, but the ng-repeat property isn't actually stored inside the directive:
<div ng-repeat="image in images">
<typeahead-directive image='encoded_query={{image}}' ></div>
</div>
any suggestions?
Is there a way to do both?
In a your directive you can get the string value of the your image attribute with $attrs.
You can use $eval to get the object from the your image attribute.
Your HTML
<div ng-repeat="image in images">
<div myimg image='image' ></div>
</div>
The directive
myModule.directive('myimg', function(){
return{
restrict: 'A',
scope: false,
template: '<p>{{image.title}}</p><img src="{{image.url}}" />',
controller: ['$scope','$attrs', function($scope, $attrs) {
console.log($attrs.image);
console.log($scope.$eval($attrs.image));
}]
/*link:function(scope, elem, attr){
console.log(attr.image);
console.log(scope.$eval(attr.image));
}*/
};
});
Controller local attributes are documented in AngularJS $compile API Reference -- controllers
The $eval function is documented in AngularJS scope API Reference
Also notice that I set scope: false because your directive doesn't really need a scope.

In angular js, How do I pass data from a parent controller to a child controller?

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.

In Angular JS - how to use ng-include to make my code generic and reusable?

Using the AngularJS,
I want to use ng-include to make my code generic.
So here is my problem:
I want to have my 'ang-attribute.html' file used in the ng-include to be as generic as possible, and for that I want to get the attribute name outside of the html.
With something like this:
<div data-field-name="display_name">
<div ng-include="'ang-attribute.html'"></div>
</div>
In my html file I would like to use the data-field-name, and then use it again with a different value.
I tried to get it via DOM... and couldn't find a way to do that.
So in my ng-include html file I have the following line:
<div ng-controller="attributeCtrl">
<div class="my-profile-constant-text">{{displayAttribute}}:</div>
....
</div>
Maybe there is a way to pass a constructor to the controller and then I could pass my data-field-name into it?
Or any other solution?
Thanks!
I believe you want to be looking at directives, this has an attributes object easily accessible after the dom has rendered to re-use.
Html
<my-directive field-name="display_name"></my-directive>
Directive
angular
.module("myApp", [])
.directive("myDirective", function() {
return {
restrict: "E",
template: "<div>{{exposeAttribute}}</div>",
//templateUrl: "mytemplate.html",
link: function(scope, element, attr) {
scope.exposeAttribute = attr.fieldName;
}
};
});
http://jsfiddle.net/mhCaD/

Categories