Angular directive does not call parent scope function - angularjs

I am trying to call a parent function from a directive. But my functions are not being called.
Here is the code for your reference.
Controller
'use strict';
angular.module('myApp')
.controller('MyCtrl', function($scope) {
$scope.iconSelected = function() {
console.log('iconSelected');
var icon = angular.element('#icon').prop('files');
if (!icon) {
return;
}
icon = icon[0];
var _URL = window.URL || window.webkitURL;
$scope.utility.icon = _URL.createObjectURL(icon);
}
$scope.sourceSelected = function() {
console.log('sourceSelected');
var source = angular.element('#source');
console.log(source.prop('files'));
};
});
Directive
'use strict';
angular.module('myApp')
.directive('uploadButton', function() {
return {
templateUrl: 'app/directives/upload-button/upload-button.html',
restrict: 'E',
transclude: true,
scope: {
onSelect: '&'
},
link: function(scope, element, attrs) {
scope.name = attrs.name;
scope.id = attrs.id || attrs.name;
scope.label = attrs.label || attrs.name;
scope.accept = attrs.accept;
scope.showDialog = function() {
element.find('#' + scope.id).trigger('click');
};
element.find('input').change(function() {
scope.$apply(attrs.onSelect);
});
}
};
});
Directive Template
<md-input-container class="upload-button">
<md-button class="md-raised" ng-click="showDialog()">
<span ng-transclude></span>
</md-button>
<input type="file" name="{{name}}" id="{{id}}" aria-label="{{label}}" accept="{{accept}}">
</md-input-container>
Directive Usage
<upload-button name="icon" on-select="iconSelected()" accept=".svg">Choose an icon</upload-button>
<upload-button class="source-btn" name="source" on-select="sourceSelected()" accept=".zip">Select source code</upload-button>

Inside your directive code you are calling onSelect using attrs.onSelect change it to scope.onSelect. attrs.onSelect will just give you the string value iconSelected(). You need the function reference which will be available in the isolated scope which is created by the directive.
element.find('input').change(function() {
scope.$apply(scope.onSelect);
});

Related

Angular 1.x - retrieve directive value from controller and assign it back to parent scope

I'm unable to get a variable value from directive to use that back in a controller. I do not have to bind the value to a view. All I need is to set 'cleanInputValue' from directive to $scope.keywords in Controller.
Here is my directive and controller -
Html with md-autocomplete for keywords field - search box.
<form id="searchbox" ng-controller="navsearchController as sc" title="Search this site" layout="row">
<md-autocomplete
md-no-cache="true"
md-selected-item="sc.currentKeyword"
md-search-text="keywords"
md-items="item in querySearch(keywords)"
md-item-text="item.display"
md-min-length="3"
md-input-name="search"
md-input-id="search-nav"
md-clear-button="false"
placeholder="Search"
md-dropdown-position="bottom"
flex>
<md-item-template>
<span md-highlight-text="keywords" md-highlight-flags="gi">{{item.display}}</span>
</md-item-template>
</md-autocomplete>
<div class="search-button" flex="none">
<button type="submit" ng-click="sc.search()" title="Search" tabindex="0">GO</button>
</div>
</form>
Directive:
.directive('test', function () {
return {
require: 'ngModel',
restrict: 'A',
scope: {
text: '#text'
},
link:function(scope, element, attrs, modelCtrl){
modelCtrl.$parsers.push(function(inputValue) {
if (inputValue === undefined){
return '';
}
var cleanInputValue = inputValue.replace(/[^\w\s\-\"]/gi, '');
if (cleanInputValue != inputValue) {
modelCtrl.$setViewValue(cleanInputValue);
modelCtrl.$render();
}
return cleanInputValue;
});
//console.log(scope.text);
}
};
})
Controller:
.controller('navsearchController', function($timeout, $element, $compile, $scope, $rootScope, $http, $location, DataService, $routeParams, $filter, $route){
var _this = this;
$timeout(function () {
var myAutoCompleteInput =
angular.element($element[0].querySelector('#search-nav'));
myAutoCompleteInput.attr("test", "test");
//myAutoCompleteInput.attr("text", "blah");
console.log($scope.keywords);
$compile(myAutoCompleteInput)($scope);
});
_this.search = function(){
xyzStorage.set('currentKeyword', $scope.keywords);
$scope.keywords = $filter('removeSpecialChars')($scope.keywords);
$location.path('/xyz/search/' + $scope.keywords);
$location.url($location.path());
$location.search({
range: xyzStorage.get('itemPerPage'),
})
$route.reload();
};
})
What you really want to do is bind the value from your controller to your directive. Don't think of it as "returning" a value from your directive.
Let's take a look.
.directive('test', function () {
return {
require: 'ngModel',
restrict: 'A',
scope: {
text: '#text',
cleanInputValue: '=testTextClean' // Adding a new TWO WAY binding here!
},
link:function(scope, element, attrs, modelCtrl){
modelCtrl.$parsers.push(function(inputValue) {
if (inputValue === undefined){
return; // exit the function and don't assign, ok
}
// Now we use scope
scope.cleanInputValue = inputValue.replace(/[^\w\s\-\"]/gi, '');
if (scope.cleanInputValue != inputValue) {
modelCtrl.$setViewValue(scope.cleanInputValue);
modelCtrl.$render();
}
// no longer need a return
});
}
};
})
In your controller you are accessing the input element within the md-autocomplete component
.controller('navsearchController', function($timeout, $element, $compile, $scope, $rootScope, $http, $location, DataService, $routeParams, $filter, $route){
var _this = this;
$timeout(function () {
var myAutoCompleteInput =
angular.element($element[0].querySelector('#search-nav'));
myAutoCompleteInput.attr("test", "test");
myAutoCompleteInput.attr("test-text-clean", "sc.keywords");
console.log($scope.keywords);
$compile(myAutoCompleteInput)($scope);
});
_this.search = function(){
xyzStorage.set('currentKeyword', $scope.keywords);
$scope.keywords = $filter('removeSpecialChars')($scope.keywords);
$location.path('/xyz/search/' + $scope.keywords);
$location.url($location.path());
$location.search({
range: xyzStorage.get('itemPerPage'),
})
$route.reload();
};
})
Now in your controller the value in $scope.keywords will always have the updated value set from your directive.

How to expose directive methods using a service

How to expose directive methods without using $broadcast or '=' between modules?
Using $broadcast (events) if there are multiple directives all will be notified. It cannot return value too.
Exposing directive's function by html attribute I think it is not that best that Angular has to offer.
Angular Bootstrap UI do it using services (I guess): It have a service named "$uibModal".
You can call a function "$uibModal.open()" of Modal Directive by injecting $uibModal service.
Is that the right way?
An example of a directive that registers its API with a service:
app.service("apiService", function() {
var apiHash = {};
this.addApi = function (name,api) {
apiHash[name] = api;
};
this.removeApi = function (name) {
delete apiHash[name];
};
this.getApi = function (name) {
return apiHash[name];
};
});
app.directive("myDirective", function (apiService) {
return {
restrict: 'E',
scope: {},
template: `<h1>{{title}}</h1>`,
link: postLink
};
function postLink(scope, elem, attrs)
var name = attrs.name || 'myDirective';
var api = {};
api.setTitle = function(value) {
scope.title = value;
};
apiService.addApi(name, api);
scope.$on("$destroy", function() {
apiService.removeApi(name);
});
}
});
Elsewhere in the app, the title of the directive can be set with:
apiService.getApi('myDirective').setTitle("New Title");
Notice that the directive registers the api with a name determined by the name attribute of the directive. To avoid memory leaks, it unregisters itself when the scope is destroyed.
Update
How could I use it from a controller?
app.controller('home', function($scope,apiService) {
$scope.title = "New Title";
$scope.setTitle = function() {
apiService.getApi('mainTitle').setTitle($scope.title);
};
})
<body ng-controller="home">
<my-directive name="mainTitle"></my-directive>
<p>
<input ng-model="title" />
<button ng-click="setTitle()">Set Title
</button>
</p>
</body>
The DEMO
angular.module('myApp', [])
.service("apiService", function() {
var apiHash = {};
this.addApi = function(name, api) {
apiHash[name] = api;
};
this.getApi = function(name) {
return apiHash[name];
};
})
.directive("myDirective", function(apiService) {
return {
restrict: 'E',
scope: {},
template: `<h1>{{title}}</h1>`,
link: postLink
};
function postLink(scope, elem, attrs) {
var name = attrs.name || 'myDirective';
var api = {};
api.setTitle = function(value) {
scope.title = value;
};
apiService.addApi(name, api);
scope.$on("$destroy", function() {
apiService.addApi(name, null);
});
}
})
.controller('home', function($scope,apiService) {
$scope.title = "New Title";
$scope.setTitle = function() {
apiService.getApi('mainTitle').setTitle($scope.title);
};
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="myApp" ng-controller="home">
<my-directive name="mainTitle"></my-directive>
<p>
<input ng-model="title" />
<button ng-click="setTitle()">Set Title
</button>
</p>
</body>
.factory('myService', [function() {
return {
charCount: function(inputString) {
return inputString.length;
}
}
}])
this service exposes function charCount();
in your directive you have to inject it like this
.directive('testDirective', ['myService', function(myService) {
return {
restrict: 'A',
replace: true,
template: "<div>'{{myTestString}}' has length {{strLen}}</div>",
link: function($scope, el, attrs) {
$scope.myTestString = 'string of length 19';
$scope.strLen = myService.charCount( $scope.myTestString );
}
}
}])
and, of course call it
$scope.strLen = myService.charCount( $scope.myTestString );
<html>
<style>
#out {
width:96%;
height:25%;
padding:10px;
border:3px dashed blue;
font-family: monospace;
font-size: 15px;
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<script>
var APP = angular.module('MYAPP', []);
APP.controller('main', ['$scope', '$element', '$compile', 'myService', function($scope, $element, $compile, myService) {
$scope.test = 'my Test Controller';
$scope.directiveTest = "directive test";
var testSvc = myService.charCount($scope.test);
$scope.showTestDir = true;
}])
.directive('testDirective', ['myService', function(myService) {
return {
restrict: 'A',
replace: true,
template: "<div>'{{myTestString}}' has length {{strLen}}</div>",
link: function($scope, el, attrs) {
$scope.myTestString = 'string of length 19';
$scope.strLen = myService.charCount( $scope.myTestString );
}
}
}])
.factory('myService', [function() {
return {
charCount: function(inputString) {
return inputString.length;
}
}
}])
.filter('toUpper', function() {
return function(input) {
return input.toUpperCase();
}
})
.filter('toLower', function() {
return function(input) {
return input.toLowerCase();
}
})
;
</script>
<body ng-app="MYAPP">
<div id="out" ng-controller="main">
{{test}} - not filtered
<br/>
{{test|toUpper}} - filtered toUpper
<br/>
{{test|toLower}} - filtered toLower
<br/>
<br/>
<div test-directive ng-if="showTestDir"></div>
</div>
</body>
</html>

Send data from controller to directive

I want to send that to my directive but I want that data to stay updated if the data in the controller changes.
// Controller
angular
.module('app')
.controller('IndexController', IndexController)
IndexController.$inject = [];
function IndexController() {
var vm = this;
vm.name = 'John';
newName = function() {
vm.name = 'Brian';
}
newName();
}
// Directive
angular
.module('app')
.directive('userName', userName);
userName.$inject = ['$document'];
function userName($document) {
var directive = {
restrict: 'EA',
template: '<div id="user"></div>',
replace: true,
scope: {
name: '='
},
link: function(scope, elem, attrs) {
console.log(scope.data);
}
}
return directive;
}
this is how I use the directive. the problem is that it always returns the first name and not the new name after the change in the controller.
<div ng-controller="indexController">
<user-name name="indexController.name">
</div>
thank you.
Try this, you just have to inject $scope into your Indexcontroller
angular
.module('app', [])
.controller('IndexController', function($scope) {
var vm = this;
vm.name = 'John';
vm.newName = function() {
vm.name = 'Brian';
console.log(vm.name);
}
//vm.newName();
})
.directive('userName', ['$document', function() {
var directive = {
restrict: 'E',
template: '<div id="user"></div>',
replace: true,
scope: {
name: '='
},
link: function(scope, elem, attrs) {
console.log(scope.name);
}
}
return directive;
}])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="IndexController as vm">
<user-name name="vm.name"></user-name>
<button ng-click="vm.newName()">Click</button>
</div>
Without using as in controller, you cannot use controller.prop inside the scope.
Inside the controlleryou need to call the method using its $scope or this.
Check the below code.
angular
.module('app', [])
.controller('IndexController', function($scope) {
$scope.name = 'John';
$scope.newName = function() {
$scope.name = 'Brian';
}
$scope.newName();
})
.directive('userName', ['$document', function() {
var directive = {
restrict: 'E',
template: '<div id="user"></div>',
replace: true,
scope: {
name: '='
},
link: function(scope, elem, attrs) {
console.log(scope.name);
}
}
return directive;
}])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="IndexController">
<user-name name="name"></user-name>
</div>

controller Syntax with transclude

I'm trying to customize directuve 'angular-popover'.
<a angular-popover
direction="bottom"
template-url="assets/app/common/templates/popovers/page-choose.html"
class="content_paginator_trigger openPaginator inline-block pull-left">
<span class="content_paginator_trigger_text popoverTriger">
Page {{$ctrl.data.current_page}} of {{$ctrl.data.last_page || 1}}
</span>
</a>
That directive use inherit scope.
scope: true
$ctrl outside, inside and in the 'template-url' is the same.
If I change it on isolate scope then I'm loosing $ctrl context in template. Template is adding via ng-transclude attribute
scope: {
onOpen: "&onOpen"
}
How can I pass some function from $ctrl to 'angularPopover' directive?
You can create a service that gets called by both the controller and the directive.
(function (ng) {
'use strict';
ng.module('ServiceDemo')
.service('DemoService', DemoService)
.controller('DemoController', DemoController)
.directive('DemoDirective', DemoDirective);
function DemoService () {
var self = this;
self.onOpen = onOpen;
function onOpen () {
console.log('onOpen called!');
}
}
function DemoController (DemoService) {
var demoControllerVM = this;
demoControllerVM.onOpen = DemoService.onOpen;
}
function DemoDirective (DemoService) {
return {
restrict: 'A',
scope: {},
templateUrl: 'path/to/directive/template.html',
controllerAs: 'demoDirectiveVM',
bindToController: true,
controller: function ($scope, $element, $attrs) {
var demoDirectiveVM = this;
demoDirectiveVM.onOpen = DemoService.onOpen;
}
};
}
})(angular);

AngularJS - set a model defined in a directives template

I have a directive defined like so:
angular.module('directives.myInput', [])
.directive('myInput', function($parse, $http, $sce){
return {
restrict: 'E',
template: '<input type="text" ng-model="searchStr" />',
controller: function($scope){
$scope.keyPressed = function(event){
$scope.showDropdown = true;
.
.
.
}
}
};
});
And then I have a button in html and directive above declared like so:
<div ng-controller="IndexCtrl">
<button ng-click="startNewLog()">Start</button>
<div ng-controller="ItemNewCtrl">
<myInput />
</div>
</div>
I want to change/initialize ng-model="searchStr" model on a button ng-click. How can I do that?
Thanks guys,
Jani
If I understand you right, first of all you need call child controller with $broadcast. Since we don't use isolate scope, we just call directive method from child controller:
[Short answer]
No isolate scope example
Demo 1 Fiddle
For isolate scope, I would map value to directive that listens on value change automatically:
Isolate scope example
Demo 2 Fiddle
[Full answer]
No isolate scope example
HTML
<div ng-controller = "IndexCtrl">
<button ng-click="startNewLog()">Start</button>
<div ng-controller="ItemNewCtrl">
<my-input></my-input>
</div>
</div>
JS
var app = angular.module('myModule', []);
app.controller('IndexCtrl', function ($scope) {
$scope.startNewLog = function(){
$scope.$broadcast('someEvent');
};
});
app.controller('ItemNewCtrl', function ($scope) {
$scope.$on('someEvent', function() {
$scope.callDirective();
});
});
app.$inject = ['$scope'];
app.directive('myInput', function(){
return {
restrict: 'E',
template: '<input type="text" ng-model="searchStr" />',
controller: function($scope){
$scope.searchStr;
$scope.keyPressed = function(event){
$scope.showDropdown = true;
}
},
link: function(scope, elm, attrs) {
scope.callDirective = function() {
scope.searchStr = 'callDirective';
};
}
};
});
Isolate scope example
HTML
<div ng-controller = "IndexCtrl">
<button ng-click="startNewLog()">Start</button>
<div ng-controller="ItemNewCtrl">
<my-input my-model='contInput'></my-input>
</div>
</div>
JS
var app = angular.module('myModule', []);
app.controller('IndexCtrl', function ($scope) {
$scope.startNewLog = function(){
$scope.$broadcast('someEvent');
};
});
app.controller('ItemNewCtrl', function ($scope) {
$scope.contInput = '';
$scope.count = 0;
$scope.$on('someEvent', function() {
$scope.contInput = 'hey mate';
});
});
app.$inject = ['$scope'];
app.directive('myInput', function(){
return {
restrict: 'E',
scope:{searchStr: '=myModel'},
template: '<input type="text" ng-model="searchStr" />',
controller: function($scope){
$scope.searchStr;
$scope.keyPressed = function(event){
$scope.showDropdown = true;
}
}
};
});

Resources