AngularJS Calling the controller method from directive - javascript

Prompt as from a directive to cause a method of the controller.
Directive
app.directive('scroll', function($location){
return{
restrict: 'A',
link: function(scope, element, attrs){
element.on('scroll', function(){
let fh = $('#ngview').height();
let nh = Math.round($(element).height() + $(element).scrollTop());
if(fh == nh){
//Here we do what we need
}
})
}
}
});
HTML markup
<div class="col-md-12 middle-body" scroll>
<div ng-show="showUserModal" ng-include="'partial/loginModal.html'"></div>
<div class="user-loader" ng-show="loading">
<div class="spinner"></div>
</div>
<div ng-view id="ngview">
</div>
</div>
app is the main application module
var app = angular.module('app',
[
'ngRoute',
'lastUpdateModule',
'selectedByGenreModule',
'currentFilmModule',
'httpFactory',
'userModule',
'accountModule'
]);
The controller from which you want to call the method is described in a separate file
and connects via require
const SelectedByGenreModule = require('../controllers/selectedByGenre.controller.js')
and passed as a dependency to the main module
So it is from this controller that I need to call the method in the directive.
Tell me how to do it correctly. I left through $rootScope but it did not work out
As far as I know, the directive has the same scope as the controller in which it is located. That is, the directive is in the controller which is the parent for the controller from which you need to call the method.

It sounds like you want your directive to trigger an action defined by your controller. I'd recommend passing the function to the directive via the scope property. See the example below.
var app = angular.module('ExampleApp', []);
app.directive('scroll', function($location) {
return {
restrict: 'A',
scope: {
scroll: '='
},
link: function(scope, element, attrs) {
element.on('scroll', function() {
let fh = $('#ngview').height();
let nh = Math.round($(element).height() + $(element).scrollTop());
if (fh == nh) {
scope.scroll();
}
})
}
}
});
app.controller('ExampleCtrl', function($scope) {
$scope.onScroll = function() {
console.log('Scrolled!')
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div ng-app="ExampleApp" ng-controller="ExampleCtrl">
<div class="col-md-12 middle-body" scroll="onScroll">
<div ng-show="showUserModal" ng-include="'partial/loginModal.html'"></div>
<div class="user-loader" ng-show="loading">
<div class="spinner"></div>
</div>
<div ng-view id="ngview">
</div>
</div>
</div>

You can require parent controllers using the require property when defining the directive, the ^^ tells angular to look up the DOM for a parent, otherwise it will only look on the local element.
app.directive('scroll', function($location){
return{
restrict: 'A',
require: '^^selectedByGenreCtrl', // Use the correct controller name here
link: function(scope, element, attrs, selectedByGenreCtrl){
element.on('scroll', function(){
let fh = $('#ngview').height();
let nh = Math.round($(element).height() + $(element).scrollTop());
if(fh == nh){
//Here we do what we need
}
})
}
}
});

Related

AngularJS: ng-change called before an select element is changed

As far as I can tell, ng-change is called before ng-model is actually changed in a select element. Here's some code to reproduce the issue:
angular.module('demo', [])
.controller('DemoController', function($scope) {
'use strict';
$scope.value = 'a';
$scope.displayValue = $scope.value;
$scope.onValueChange = function() {
$scope.displayValue = $scope.value;
};
})
.directive("selector", [
function() {
return {
controller: function() {
"use strict";
this.availableValues = ['a', 'b', 'c'];
},
controllerAs: 'ctrl',
scope: {
ngModel: '=',
ngChange: '='
},
template: '<select ng-model="ngModel" ng-change="ngChange()" ng-options="v for v in ctrl.availableValues"> </select>'
};
}
]);
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<div ng-app="demo" ng-controller="DemoController">
<div selector ng-model="value" ng-change="onValueChange">
</div>
<div>
<span>Value when ng-change called:</span>
<span>{{ displayValue }}</span>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
<script src="demo.js"></script>
</body>
</html>
If you run this, you should change the combobox 2 times (e.g. 'b' (must be different than the default), then 'c').
The first time, nothing happens (but the value displayed in the text should have changed to match the selection).
The second time, the value should change to the previous selection (but should have been set to the current selection).
This sounds really similar to a couple previous posts: AngularJS scope updated after ng-change and AngularJS - Why is ng-change called before the model is updated?. Unfortunately, I can't reproduce the first issue, and the second was solved with a different scope binding, which I was already using.
Am I missing something? Is there a workaround?
It's good idea not to use the same model value in the directive. Create another innerModel which should be used inside directive and update the "parent" model when needed with provided NgModelController.
With this solution, you don't hack the ng-change behavior - just use what Angular already provides.
angular.module('demo', [])
.controller('DemoController', function($scope) {
$scope.value = 'a';
$scope.displayValue = $scope.value;
$scope.onValueChange = function() {
$scope.displayValue = $scope.value;
};
})
.directive("selector", [
function() {
return {
controller: function() {
this.availableValues = ['a', 'b', 'c'];
},
require: 'ngModel',
controllerAs: 'ctrl',
scope: {
'ngModel': '='
},
template: '<select ng-model="innerModel" ng-change="updateInnerModel()" ng-options="v for v in ctrl.availableValues"> </select>',
link: function(scope, elem, attrs, ngModelController) {
scope.innerModel = scope.ngModel;
scope.updateInnerModel = function() {
ngModelController.$setViewValue(scope.innerModel);
};
}
};
}
]);
<div ng-app="demo" ng-controller="DemoController">
<div selector ng-model="value" ng-change="onValueChange()">
</div>
<div>
<span>Value when ng-change called:</span>
<span>{{ displayValue }}</span>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
Inspired by this answer.
It's a digest issue.. Just wrap the onValueChange operations with $timeout:
$scope.onValueChange = function() {
$timeout(function(){
$scope.displayValue = $scope.value;
});
};
Don't forget to inject $timeout in your controller.
... or you can check this link on how to implement ng-change for a custom directive

angular-ui bootstrap $modal service using directive instead

The examples I see of using angular-ui/bootstrap's $modal always look something like this:
$modal.open({
templateUrl: 'modaltemplate.html',
controller: function($scope) {
...
}
});
What if I want to use a directive, instead? Like this:
$modal.open({
template: '<my-modal-directive></my-modal-directive>'
// no "controller" property; use directive's controller
});
The markup for my-modal-directive renders fine, and I've moved the controller property into the my-modal-directive definition object, but now getting this error from the my-modal-directive:
Error: [$injector:unpr] Unknown provider: $modalInstanceProvider <- $modalInstance
Can anyone point me to an example where $modal uses a directive where that directive defines the controller?
For example, this works, where I've replaced the templateUrl with a directive:
http://plnkr.co/edit/YrGaF83GH6bzZPRR55GK?p=preview
But when I move the controller from $modal.open() into the directive, that's when the error happens:
http://plnkr.co/edit/aLBT239EpL004DRh4jll?p=preview
The problem is that $modalInstance can only be injected in the controller that you provide to $modal.open.
Check out the sources here:
$modal.open = function (modalOptions) {
...
var modalInstance = {
...
};
...
if (modalOptions.controller) {
...
ctrlLocals.$modalInstance = modalInstance;
...
ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
...
}
...
}
In essence when you try to add $modalInstance as a dependency to your controller AngularJS looks for a registered global provider named $modalInstanceProvider. Now the trouble is, if you understood the code above, that $modalInstance is not a globally registered provider. It only "exists" as a dependency for the controller you pass to $modal.open.
If you read the rest of the code you'll notice that $modal.open returns modalInstance, maybe you can use that.
Something like this:
function SomeController($modal) {
$scope.modal = {
instance: null
};
$scope.modal.instance = $modal.open({
template: '<my-modal-directive modal="modal"></my-modal-directive>',
scope: $scope
});
}
function MyModalDirective() {
scope: {
modal: '='
},
link: function($scope) {
// here you can access $scope.modal.instance
}
}
The issue you have is that you are trying to inject values which are not available for injection. Only values registered with the injector can be injected.
The logic of you code is also flawed, you are creating the modal in your main controller but trying to close it in the directive. Ideally, the modal should be triggered by the directive (via it's link function), and then you can ok/cancel it from there.
See my http://plnkr.co/edit/3p1rXAymd7BilyklgxKy?p=preview for one possible approach, I have kept the code that closes and cancels the modal in the main controller.
angular.module('ui.bootstrap.demo', ['ui.bootstrap']);
angular.module('ui.bootstrap.demo').directive('myModal', function() {
return {
restrict: 'E',
templateUrl: 'myModalContent.html',
controller: function ($scope) {
$scope.selected = {
item: $scope.items[0]
};
}
};
});
angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($scope, $modal, $log) {
$scope.items = ['item1', 'item2', 'item3'];
$scope.open = function (size) {
var modalInstance;
var modalScope = $scope.$new();
modalScope.ok = function () {
modalInstance.close(modalScope.selected);
};
modalScope.cancel = function () {
modalInstance.dismiss('cancel');
};
modalInstance = $modal.open({
template: '<my-modal></my-modal>',
size: size,
scope: modalScope
}
);
modalInstance.result.then(function (selectedItem) {
$scope.selected = selectedItem;
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
};
});
I create a directive to create modals easily. A modal content is based on a template view.
angular.module('your_app').directive('modalViewUrl', function ($modal) {
return {
restrict: 'A', // A: attribute
scope: { // isolate scope
'modalViewUrl': '#', // modal view url to render the modal content
'modalController': '#' // modal view controller (optional)
},
link: function($scope, element, attrs){
element.bind('click', function(){
var template =
'<div class="modal-body">' +
'<button ng-click="$close()" type="button" class="close" aria-label="Close">' +
'<span aria-hidden="true">×</span>' +
'</button>' +
'<div ng-include="\'' + $scope.modalViewUrl + '\'"></div>' +
'</div>';
// see modal reference from ui bootstrap at <http://angular-ui.github.io>
var modalInstance = $modal.open({
animation: true,
template: template,
controller: $scope.modalController,
});
});
}
};
});
Example how to use it:
index.html
<a modal-view-url="hello.html" modal-controller="HelloCtrl" href="#">
Click here to open the modal
</a>
hello.html
<h1> Hello World {{name}} </h1>
HelloCtrl.js
angular.module('yourApp').controller('HelloCtrl',
function ($scope, $modalInstance) {
// $modalInstance: same from from ui bootstrap
$scope.name = "Xico";
});
A modal view can have its own controller. Example:
hello.html (modified)
<h1 ng-controller="Hello2Ctrl"> {{msg}} {{name}} </h1>
Hello2Ctrl.js
angular.module('yourApp').controller('Hello2Ctrl',
function ($scope) {
$scope.msg = "Hello Worldsszz";
$scope.name = "Zefa";
});
Observe that the modal output will be "Hello Worldsszz Xico", because the modal controller (HelloCtrl) will be rendered after view controller (Hello2).
Reference
It's even more late reply, but someone may find it useful.
I have enhanced Fernando Felix answer and made my own quite flexible directive which communicates with the controller, which I think might be solution for this question.
Directive
var modalUrl = function ($modal) {
return {
restrict: 'A', // A: attribute
scope: { // isolate scope
'modalUrl': '#', // modal view url to render the modal content
'modalController': '#', // modal view controller (optional)
'value': "="
},
link: function(scope, element, attrs){
console.log('modalUrl link');
var modalInstance;
var template = [
'<div class="modal-body">',
'<button ng-click="$close()" type="button" class="close" aria-label="Close">',
'<span aria-hidden="true">×</span>',
'</button>',
'<div ng-include="\'' + scope.modalUrl + '\'"></div>',
'</div>'
].join('');
element.bind('click', function(){
// see modal reference from ui bootstrap at <http://angular-ui.github.io>
modalInstance = $modal.open({
size: attrs.size,
animation: true,
template: template,
resolve: {
params: function () {
console.log('value passed to modal:');
console.log(scope.value);
return scope.value;
}
},
controller: scope.modalController
});
modalInstance.result.then(
function (returnValue) {
// alert('value: '+returnValue);
console.log('modal returnValue:');
console.log(returnValue);
scope.value = returnValue;
}, function () {
console.log('Modal dismissed at: ' + new Date());
}
);
});
}
};
}
modalUrl.$inject = ['$modal'];
angular.module('app').directive('modalUrl', modalUrl);
Controller
var HelloCtrl = function ($scope, $modalInstance, modalVal) {
// $modalInstance: same from from ui bootstrap
console.log('Hello init!');
// modalVal is the init modal value passed via directive
console.log(modalVal);
// your code
$scope.name = modalVal;
$scope.ok = function() {
$modalInstance.close(this.name); // returnValue
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
}
HelloCtrl.$inject = ['$scope', '$modalInstance','params'];
angular.module('app').controller('HelloCtrl',HelloCtrl);
inline template
<script type="text/ng-template" id="hello.html">
<div class="modal-header">
<h3 class="modal-title">I'm a modal!</h3>
</div>
<div class="modal-body">
<input type="text" ng-model="name" />
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="ok()">OK</button>
<button class="btn" ng-click="cancel()">Cancel</button>
</div>
</script>
It's one controller and template per popup type, then you can call it multiple times with:
<a modal-url="hello.html" modal-controller="HelloCtrl" value="yourVal" ng-init="yourVal='test'" href="#">Click here to open the modal</a>
You can initialize value with whatever - ie. object, array etc.
or external template
Pretty much the same, just url changes and template file is used for template.
<a modal-url="/modal/test1.html" modal-controller="HelloCtrl" value="yourVal" ng-init="yourVal='test'" href="#">Click here to open the modal</a>
test1.html
<div class="modal-header">
<h3 class="modal-title">I'm a modal!</h3>
</div>
<div class="modal-body">
<input type="text" ng-model="name" />
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="ok()">OK</button>
<button class="btn" ng-click="cancel()">Cancel</button>
</div>
Modal size etc.
Just add parameter size="sm|lg" for the modal link/button ie.
Click here to open the modal
For standard size skip the parameter.
You may enhance it yourself using link function attrs.
I'm kanda late replay put simplest way is to use
$scope.$parent.$close(result);
$scope.$parent.$dismiss(reason);
This works form your directive controller.

Angular directive to add content rather than overwrite

I am trying to write a directive that will show a "loading" message over a div while the data is fetched from the server.
Thus far I've managed to get this:
.directive('dataLoadingPanel', function () {
return {
templateUrl: '/Utilities/loadingPanelBox.html',
scope: {
panelData: '=',
loadingMessage: "#"
}
};
})
loadingPanelBox.html has this:
<div class="modal-dialog" style="background-color: white;width:300px;height:46px;padding-top:16px;top:30px;padding-left:40px;border-radius: 4px;" ng-hide="panelData">
<img src="/images/BlueSpinner.gif" style="margin-top:-2px" /> {{loadingMessage}}
</div>
This actually does most of what I want, the panel is shown until the data is returned at which point it disappears.
Unfortunately it also overwrites the contents of the div it's placed on, so in this instance:
<div data-loading-panel panel-data="myData" loading-message="Loading Data">Hello There</div>
the Hello There is never seen. This seems to be a function of my using a template.
Is there a way of stopping this overwriting happening or maybe some way of adding the content other than with a template.
I use this:
BlockUI
This is a module which is very easy to use. There is a nice tutorial on the page that fills all of your needs.
Thanks to #Amiros I've made it work slightly differently.
Here's the directive and controller code:
.controller('dataLoadingPanelController', [
'$scope', '$timeout', function($scope, $timeout) {
$timeout(function() {
$scope.setBoxSize();
});
} ])
.directive('dataLoadingPanel', function () {
return {
restrict: 'EA',
scope: {
panelData: '=',
loadingMessage: "#"
},
templateUrl: '/Content/Utilities/loadingPanelBox.html',
link: function (scope, element, attr) {
var elMessage = element.find('.loading-message-area');
var elBox = element.find('.loading-dialog');
scope.setBoxSize = function() {
var messageSize = parseInt(elMessage.css('width').replace(/px$/, ""));
var parentSize = parseInt(element.parent().css('width').replace(/px$/, ""))
var newBoxSize = messageSize + 70;
elBox.css('width', newBoxSize + 'px');
var newBoxPosition = (parentSize / 2) - (newBoxSize / 2);
elBox.css('margin-left', newBoxPosition + 'px');
};
},
controller: 'dataLoadingPanelController'
};
})
The html file is:
<div style="position:absolute;color:black;font-weight:normal;">
<div class="modal-dialog loading-dialog" style="border:1px solid #1f4e6c;background-color: white;height:46px;padding-top:14px;top:30px;padding-left:20px;border-radius: 4px;" ng-hide="panelData">
<img src="/images/BlueSpinner.gif" style="margin-top:-2px" /> <span class="loading-message-area">{{loadingMessage}}</span>
</div>
</div>
which is pretty simple and means that the usage is as follows:
<div class="panel-body">
<data-loading-panel panel-data="myData" loading-message="Loading Data"></data-loading-panel>
{{myData}}
</div>

Call parent controllers' function from directive in angular

jsfiddle
I have a ng-click within a directive named ball. I am trying to call MainCtrl's function test() and alert the value of ng-repeat's alignment of ball.
Why cant i recognize the MainCtrl's test function?
var $scope;
var app = angular.module('miniapp', []);
app.controller('MainCtrl', function($scope) {
$scope.project = {"name":"sup"};
$scope.test = function(value) {
alert(value);
}
$scope.test2 = function(value) {
alert('yo'+value);
}
}).directive('ball', function () {
return {
restrict:'E',
scope: {
'test': '&test'
},
template: '<div class="alignment-box" ng-repeat="alignment in [0,1,2,3,4]" ng-click="test(alignment)" val="{{alignment}}">{{alignment}}</div>'
};
});
html
<div ng-app="miniapp">
<div ng-controller="MainCtrl">
{{project}}
<ball></ball>
</div>
</div>
You need to pass the test() method from the controller into the directive...
<div ng-app="miniapp">
<div ng-controller="MainCtrl">
{{project}}
<ball test="test"></ball>
</div>
</div>
Change & to = in directive:
scope: {
'test': '=test'
}
Fiddle: http://jsfiddle.net/89AYX/49/
You just need to set the controller in your directive as:
controller: 'MainCtrl'
so the code for your directive should look like:
return {
restrict:'E',
scope: {
'test': '&test'
},
template: '<div class="alignment-box" ng-repeat="alignment in [0,1,2,3,4]" ng-click="test(alignment)" val="{{alignment}}">{{alignment}}</div>',
controller: 'MainCtrl'
};
the one way is to not isolate a directive scope...just remove the scope object from directive.
Another way is to implement an angular service and put a common method there, inject this service wherever you need it and in the directive call function that will be insight isolated scope and there call a function from directive

AngularJS Odd Directive Scope Behavior

After describing my setup, my questions are below in bold.
index.html
<div ng-controller="MyCtrl">
<user-picker placeholder="Type a name..."></user-picker>
</div>
Setup:
var app = angular.module('app', ['app.directives', 'app.controllers']);
var directives = angular.module('app.directives', []);
var ctrls = angular.module('app.controllers', []);
Controller:
ctrls.controller('MyCtrl', function($scope) {
$scope.foo = 'this is a foo';
});
Directive:
directives.directive('userPicker', function() {
return {
restrict: 'E',
replace: true,
scope: {
placeholder: '#'
},
templateUrl: 'file.html',
link: function postLink($scope, ele, attrs) {
console.log($scope);
console.log('[ng] Scope is: ');
console.log($scope.placeholder);
console.log($scope.$parent.foo);
}
});
file.html (the directive):
<span>
<input placeholder="{{placeholder}}" type="text">
</span>
So what I want to end up with, is generally working:
<span placeholder="Type a name...">
<input placeholder="Type a name..." type="text">
</span>
The placeholder attribute is correctly resolved.
Is this the right way to accomplish this? Note that the attribute ends up in two places.
Why this odd behavior:
Secondly, I am baffled by the results of console.log($scope). The console output reveals the accurately set placeholder attribtue on the $scope object. However, even still, the very next console.log($scope.placeholder) statement returns "undefined". How is this possible, when the console output clearly shows the attribute is set?
My goals are:
Move or copy the placeholder attribute from the parent down to the child <input> tag.
Have access to the template scope from within linking function.
Reference the parent MyCtrl controller that this directive sits within.
I was almost there, until I ran into the odd behavior noted above. Any thoughts are appreciated.
Instead of attempting to read this off the scope would reading the attrs work?
Some HTML
<script type="text/ng-template" id="file.html">
<span>
<input placeholder="{{placeholder}}" type="text"/>
</span>
</script>
<body ng-app="app">
<div ng-controller="MyCtrl">
<user-picker placeholder="Type a name..."></user-picker>
</div>
</body>
Some JS
var app = angular.module('app', ['app.directives', 'app.controllers']);
var directives = angular.module('app.directives', []);
var ctrls = angular.module('app.controllers', []);
ctrls.controller('MyCtrl', function ($scope) {
$scope.foo = 'this is a foo';
});
directives.directive('userPicker', function () {
return {
restrict: 'E',
replace: true,
scope: {
placeholder: '#'
},
templateUrl: 'file.html',
link: function postLink($scope, ele, attrs) {
console.log($scope);
console.log('[ng] Scope is: ');
console.log(attrs["placeholder"]);
console.log($scope.$parent.foo);
}
}
});
A Fiddle
http://jsfiddle.net/Rfks8/

Categories

Resources