Calling controller method from directive in template - angularjs

I have a directive that is sitting in a template that is then included on a page.
If I place my directive directly onto my page, then on a button click I can call a method within my controller.
However, when I place the directive within a template, and then the template on the page, I can no longer call a method in my controller from the directive.
I've tried a number of things with the posted code below my latest attempt. However, this code produces the error
asking for new/isolated scope on:
So HTML first;
This is on my HTML page.
<session-list trackid='san'></session-list>
This is the template HTML;
<div class="container col-sm-12 col-xs-12">
<div>Session list template for {{trackid}}</div>
<session-calendar callback-fn="ctrlFn()"></session-calendar>
</div>
My primary controller looks like this with the "eventClick" method I want to call.
angular.module('GAP.viewsessions', ['ngRoute'])
.controller('viewsessionsCtrl', ['$scope', function($scope){
$scope.eventClick = function(eventData){
console.log(eventData);
}
}]);
Then the "SessionList" directive;
angular.module("GAP.sessionList", [])
.directive("sessionList", function(){
return {
restrict: 'E',
link: function(scope, element, attributes){
},
scope: {
trackid: '#'
},
templateUrl: '/templates/sessionlist.html', // or use a path to a html file like 'path_to/template.html'
replace: true,
};
})
The other directive is a FullCalendar and in the click event of the event I have this;
eventClick: function(calEvent, jsEvent, view) {
scope.someCtrlFn();
if (scope.eventClick){
scope.eventClick(calEvent.data);
}
},
And If I include this;
scope: { someCtrlFn: '&callbackFn' },
I get the previously quoted error. If I leave it out, then the page renders but the "eventClick" method is never run in my controller.

One possible workaround is use an angular event
Inject $rootScope in directive then something like:
$rootScope.$broadcast('cal-event-clicked', eventData)
In controller
$scope.$on('cal-event-clicked', function(evt, data){
$scope.eventClick(data)
})

Related

Not able to access angular directive's isolated scope

First Directive:
app.directive("myDirectiveOne", function($rootScope){
return {
templateUrl : "/custom-one-html.html",
restrict: "AE",
replace:true,
scope: {
somedata: "=",
flags: "=",
functionone: "&"
}
,controller: function($rootScope,$scope, $element) {
$scope.firstFunction = function(){
console.log("First function is getting called")
}
$scope.$on('firstBroadcast',function(event, data){
$rootScope.$broadcast('secondBroadcast', data)
});
}
Second Directive:
app.directive("myDirectiveTwo", function($rootScope){
return {
templateUrl : "/custom-two-html.html",
restrict: "AE",
replace:true,
scope: {
data: "=",
functiontwo: "&"
}
,controller: function($rootScope,$scope, $element) {
$scope.secondFunction = function(){
console.log("Second function is getting called")
$rootScope.$broadcast('firstBroadcast', {})
}
$scope.$on('secondBroadcast',function(event, data){
$scope.callSomeFunctionWithData(data);
});
$scope.secondFunction();
$scope.editFunction = function(x){
console.log("This is the edit function", x);
}
Parent Controller:
$scope.parentFuntion = function(){
console.log("No trouble in calling this function")
}
So, the problem is when I try calling a function from a myDirectiveTwo html template, the controller which is active is the parent controller and not the isolated one.
May be it has something to do with the broadcasts I am using?
Html Code:
<div ng-repeat="x in data">
<h5>{{x.name}}</h5>
<button ng-click="editFunction(x)">Edit</button>
</div>
The strange thing is I get data values and ng-repeat works fine on load. But, when I click on the button it doesnt do anything. And if I add the same function in the parent controller, it gets called.. :(
How do I make the isolated scope controller active again..?
The problem is that ng-repeat creates a child scope, therefore the editFunction ends up being on the parent scope.
From docs
... Each template instance gets its own scope, where the given loop variable is set to the current collection item ...
Docs here
You can verify that this is the issue by getting your button element's scope and checking the $parent, as such angular.element(document.getElementsByTagName("button")).scope()
Although considered code smell, you can append $parent to your function call in order to access it, though keep in mind this now places a dependency on your HTML structure.
<button ng-click="$parent.editFunction(x)">Edit</button>
The issue was that I was using a deprecated method replace:true. This caused the unexpected scenarios.. As #Protozoid suggested, I looked at his link and found the issue.. To quote from the official documentation:
When the replace template has a directive at the root node that uses transclude: element, e.g. ngIf or ngRepeat, the DOM structure or scope inheritance can be incorrect. See the following issues: Incorrect scope on replaced element: #9837 Different DOM between template and templateUrl: #10612
I removed replace:true and its fine now :)
This is the link:
Here

Change templateURL of directive dynamically after $http.get()

I'm working on 'skeleton' loading the UI in different components. I have a directive that I'm loading a template initially (this template has low opacity and looks like a mock table). Once I get the data I need in an http.get() then I want to change the templateUrl of the directive. Below is what I've tried so far.
function registers($log, $state, $templateCache, currentCountData, storeFactory) {
var directive = {
restrict: 'A',
scope: true,
templateUrl: '/app/shared/tableMock/tableMock.html',
link: link
};
return directive;
function link(scope, element, attrs) {
storeFactory.getRegisters().then(function (data) {
scope.registers = data.registers;
$templateCache.put('/app/dashboard/registers/registers.html');
});
}
}
I'm not sure I'm on the right track. I can step through and see the storeFactory return the correct data from my factory. How can I then change the templateUrl of the directive?
For cases like this I usually do something like this in my directive template:
<div ng-switch="viewState">
<div ng-switch-when="gotRegisters">
Content for when you get the registers
</div>
<div ng-switch-default>
For when you don't have the registers
</div>
</div>
This way you could just change a variable to show your content ie scope.viewState = 'gotRegisters'; instead of waiting for it to download after you already downloaded your registers.
With a help from this question I was able to come up with this
function link(scope, element, attrs) {
storeFactory.getRegisters().then(function (data) {
scope.registers = data.registers;
$http.get('/app/dashboard/registers/registers.html', { cache: $templateCache }).success(function (tplContent) {
element.replaceWith($compile(tplContent)(scope));
});
});
}
tplContent correlates to the response of the $http.get(). It's the html in the registers.html file. element represents the directive itself.

Why does my nested directive disconnect from its ngModel as soon as $setViewValue() is called?

Plunker here.
I have a directive ("child") nested inside another directive ("parent"). It requires ngModel, and ngModelCtrl.$modelValue is shown and kept up-to-date just fine in its template. That is, until I call ngModelCtrl.$setViewValue().
So here is the HTML initialising the directives:
<div parent>
<div child ng-model="content">Some</div>
</div>
And here are the directives:
angular.module('form-example2', [])
.controller('MainCtrl', function($scope){
$scope.content = 'Hi';
})
.directive('parent', function() {
return {
transclude: true,
template: '<div ng-transclude></div>',
controller: function(){
},
scope: {}
};
})
.directive('child', function() {
return {
require: ['ngModel', '^parent'],
transclude: true,
template: '<div>Model: {{model.$modelValue}} (<a style="text-decoration: underline; cursor: pointer;" ng-click="alter()">Alter</a>)<br />Contents: <div style="background: grey" ng-transclude></div></div>',
scope: {},
link: function(scope, elm, attrs, ctrl) {
var ngModelCtrl = ctrl[0];
var parentCtrl = ctrl[1];
scope.model = ngModelCtrl;
// view -> model
scope.alter = function(){
ngModelCtrl.$setViewValue('Hi2');
}
// model -> view
// load init value from DOM
}
};
});
When the model (i.e. content) changes, this change can be seen inside the child directive. When you click the "Alter" link (which triggers a call of $setViewValue()), the model's value should become "Hi2". This is correctly displayed inside the child directive, but not in the model outside the directive. Furthermore, when I now update the model outside the directive, it is no longer updated inside the directive.
How come?
The directives ended up being just fine; the only problem was that the passed model should be an object property. Hence, the directives work if the following modifications are made to the calling code (Plunker):
In the controller, instead of $scope.content = 'Hi';:
$scope.content = {
value: 'Hi'
};
In the template, replace all references to content with content.value:
<input ng-model="content.value" type="text" />
<div parent>
<div child ng-model="content.value">Some</div>
</div>
<pre>model = {{content.value}}</pre>
The reason this works, roughly, is that when Angular passes the reference to the model to the transcluded scope of the parent directive (i.e. the one the child is in), this is only a reference when it refers to an object property - otherwise it is a copy, which Angular cannot watch for changes.
#Josep's answer helped greatly so, even though it did not provide the actual solution, if you're reading this and it's useful, give it a vote :)

Directive does not have access to controller function (AngularJS)

When I declare a directive to use that accesses a controller function, it cannot find the controller.
Here is my directive declared in app.js:
app.directive("delete", function() {
return {
restrict: 'A',
link: function(scope, elem, attr, ctrl) {
elem.bind('click', function(e) {
alertCtrl.alert();
});
}
}
});
Here is my controller:
app.controller('AlertController', function() {
this.alert = function() {
alert('Ahah!');
}
});
And here is my HTML:
<div ng-controller="AlertController as alertCtrl">
<div ng-repeat="i in [1,2,3]">
<img src='image.png' delete />
</div>
</div>
If I click on the image, I get no alert and the console says that alertCtrl is not defined. How come alertCtrl is not defined when you click on the image with the delete directive on it?
If I change the controller to have $scope.alert = function()... it works fine. But I do not want this.
Also, is this the proper way to handle such a situation? If not, what is the best practice?
You need to call, it on the scope. scope has the property alertCtrl which is your controller instance. Your controller alias (alertCtrl) is available when you bind it on the html since scope is implicit there. but when you do it in the javascript i.e in the directive, or another controller you would need to get the controller instance (defined as alias) from the scope as a property.
scope.alertCtrl.alert();
Plnkr

Passing controller scope from directive to partial

I am new to AngularJs. I have a route configured to a controller and a template. In the template I am calling a custom directive. The custom directive loads a partial file in which I need to fetch the scope which is set by the controller. How can I pass the scope from the directive to the partial so that the partial file can consume the scope data.
Kindly let me know how to get this implemented in AngularJs
Code snippet inside the link function of the directive:
myApp.directive('basicSummary', function() {
return {
restrict: 'E',
replace:'true',
templateUrl: "partials/basicSummary.html",
link: function(scope, elem, attrs) {
console.log(scope.testURL);
}
}
});
Output on the console is : undefined
Update: I found the root cause of why the variable was not getting initialized. The issue, is that the variable is being fetched by making an ajax call in the controller and by the time the ajax call is completed and the variable is put inside the scope inside the controller, the partial file is already loaded and hence I am getting the value of the variable inside the directive as undefined.
How can I make sure that only on success of the http call, I load the partial and the directive?
Adding the jsfiddle link: http://jsfiddle.net/prashdeep/VKkGz/
You could add a new variable to your scope in the definition of your directive to create a two-way binding, that you could safely watch for changes (for use in Javascript once the variable has been populated via ajax), and in your template use ng-show to show/hide based on whether or not the variable is defined. See this JSFiddle for an example of how that would work: http://jsfiddle.net/HB7LU/3588/
Default Template
<div ng-controller="MyCtrl">
<my-test my-test-url="myAjaxProperty"></my-test>
</div>
App Definition
var myApp = angular.module('myApp',[]);
myApp.directive('myTest', function(){
return {
restrict: 'E',
repalce: 'true',
template: '<div ng-show="myTestUrl">{{myTestUrl}}</div>',
scope: { myTestUrl: '=' },
link: function(scope, elem, attrs){
scope.$watch('myTestUrl', function(newVal, oldVal){
if(newVal){
console.log("Watched value is defined as: "+scope.myTestUrl);
}
})
}
}
});
function MyCtrl($scope, $timeout) {
$timeout(function(){
$scope.myAjaxProperty = "My Test Url";
console.log("Ajax returned");
}, 3000, true)
console.log("Default Controller Initialized");
}
as long as you don't isolate your scope with,
scope: {}
in your directive, your scope has access to its parent controller's scope directly.

Resources