How can I access isolated scope's property in directive tag?
Simplified example:
angular.module('app', [])
.controller('myController', function() {
var result_el = document.getElementById("result");
this.log = function(text) {
var p = document.createElement("p");
p.innerHTML = text;
result_el.appendChild(p);
}
})
.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
'click_fn': '&myClick'
},
template: '<span ng-click="click_fn()">Click me!</span>',
link: function(scope, element) {
scope.my_prop = 'text property';
}
}
});
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script>
<div ng-app="app" ng-controller="myController as mCtrl">
<my-directive my-click="mCtrl.log(my_prop)"></my-directive>
</div>
<div id="result"></div>
In this example I need to get my_prop property from directive's scope. Is it possible to do this somehow?
The directive definition object for isolate scope (DDO)should be as below
scope: {
click_fn: '&myClick' // click_fn should not be string
},
In directive template , need to pass parameter in object literal (aliasing)as below
Directive template
template: '<span ng-click="click_fn({my_prop:my_prop})">Click me!</span>'
Plunker
Related
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
}
})
}
}
});
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
I have a directive and a controller according to this (it's from the Angular JS Directives PacktPub book, mostly).
angular.module('myApp',[])
.directive('myIsolatedScopedDirective', function(){
return {
scope : {
'title' : '#msdTitle'
},
link: function ($scope, $element, $attrs) {
$scope.setDirectiveTitle = function (title) {
$scope.title = title;
};
}
};
})
.controller('MyCtrl', function($scope){
$scope.title = "Hello World";
$scope.setAppTitle = function(title){
$scope.title = title;
};
});
<div ng-controller="MyCtrl">
<h2>{{title}}</h2>
<button ng-click="setAppTitle('App 2.0')">Upgrade me!</button>
<div my-isolated-scoped-directive msd-title="I'm a directive within the app {{title}}">
<h4>{{title}}</h4>
<button ng-click="setDirectiveTitle('bob')">Bob it!</button>
</div>
</div>
The problem is the following:
Why the <h4>{{title}}</h4> evaluate to "Hello World" and why not "I'm a directive within the app Hello World"?
Can anybody explain this please?
Thank you.
Plunker
The reason is, you have to enter the template inside directive's template property to make it the isolated one. Right now, the directive creates an isolated scope, but it doesn't use it anywhere, because the content inside your directive tag is already evaluated in the parent scope (of MyCtrl) when the directive's link function is triggered
This is probably what to want to do
http://plnkr.co/edit/jmWrNpLFttDPhSooPF0M?p=preview
directive
.directive('myIsolatedScopedDirective', function(){
return {
scope : {
'title' : '#msdTitle'
},
replace: true,
template: '<div><h4>{{title}}</h4>' +
'<button ng-click="setDirectiveTitle(\'bob\')">Bob it!</button>',
link: function ($scope, $element, $attrs) {
$scope.setDirectiveTitle = function (title) {
$scope.title = title;
};
}
};
markup
<div my-isolated-scoped-directive msd-title="I'm a directive within the app {{title}}"></div>
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
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/