How to expose directive methods using a service - angularjs

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>

Related

How to bind multiple custom events in angularjs?

I need to bind custom events in angularjs(1.x) and I tried with the following code,
HTML
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link href="https://www.polymer-project.org/components/polymer/polymer.html" rel="import">
<link href="https://www.polymer-project.org/components/paper-button/paper-button.html" rel="import">
<div ng-app="demo-app">
<div ng-controller="DemoController">
<template bind-angular-scope is="auto-binding">
<paper-button raised on-tap="{{clickMe}}" on-mouseover="{{mouseOver}}">click me</paper-button>
</template>
<pre><code>{[{text}]}</code></pre>
</div>
</div>
Script
<script>
angular.module('demo-app', [])
.config(function ($interpolateProvider) {
$interpolateProvider.startSymbol('{[{').endSymbol('}]}');
})
.directive('bindAngularScope', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
for (k in scope) {
if (!element[0][k]) {
element[0][k] = scope[k];
}
}
}
}
})
.controller('DemoController', function ($scope) {
$scope.text = '';
$scope.clickMe = function () {
$scope.text += '\nyou clicked me!!';
$scope.$apply();
};
$scope.mouseOver = function () {
$scope.text += '\nyou hovered me!!';
$scope.$apply();
}
});
</script>
This is not working.Could you point out me the issue or Is there is any solution for binding custom events(multiple) ? Do we need to create a custom directive for each of them ?
Note:
The above code is referred from the following url,
How to bind custom events in AngularJS?
Thanks in advance!
angular.module('demo-app', [])
.config(function ($interpolateProvider) {
$interpolateProvider.startSymbol('{[{').endSymbol('}]}');
})
.directive('bindAngularScope', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
for (k in scope) {
if (!element[0][k]) {
element[0][k] = scope[k];
}
}
elem.bind('click', function() {
/* Place your click logic here * /
});
}
}
})

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>

Not able to access service in directive in angular

I am new in angular and stuck in a conceptual problem. I am not able to access "game" service in "helloWorld" directive.
expected = Name : WarCraft
Actual = Name :
Here is my js and html file :
JS code :
var app = angular.module("app",[]);
app.provider("game", function () {
var type;
return {
setType: function (value) {
type = value;
},
$get: function () {
return {
title: type + "Craft"
};
}
};
});
app.config(function (gameProvider) {
gameProvider.setType("War");
});
app.controller("AppCtrl", function ($scope,game) {
$scope.title = game.title;
});
app.directive('helloWorld', ["game",function (game) {
return {
template: 'Name : {{game.title}}'
};
}])
HTML :
<title>Services</title>
<script src="angular.min.js"></script>
<script src="my-file.js"></script>
</head>
<body ng-app="app">
<div ng-controller="AppCtrl">{{title}} </div>
<hello-world></hello-world>
</body>
something like this:
app.directive('helloWorld', ["game",function (game) {
return {
restrict: 'E',
scope: {},
link: function (scope, elem, attrs, ctrl) {
scope.title = game.title;
},
template: 'Name : {{title}}'
};
}])
Access your game service in controller, which will be available in your template. If you need it only in template, inject it only in controller... your link or other function need not know.
app.directive('helloWorld', function () {
return {
controller: function($scope, game) {
$scope.game = game;
},
template: 'Name : {{game.title}}'
};
}])
Here is solution on plunker, to play around.
Just put define variable which will be used by view and set to it needed value.
app.directive('helloWorld', ["game", function(game) {
return {
template: 'Name : {{game.title}}',
link: function(scope) {
scope.game = game;
}
};
}])

Angular directive with ng-template

Today I'm trying to develop a popover directive. I don't know why the ng-repeat inside the styles-select directive wich is insered in the popover after click doesn't work(<- Edited it works now)... And I want to get the value of "selectedStyles" in my controller "MyController" without passing it through the directive.
var app = angular.module('MyApp', []);
app.controller('MyController', ['$scope', function($scope) {
$scope.selectedStyles = [];
$scope.$watch('selectedStyles', function(newValue, oldValue) {
console.log(newValue);
});
}]);
app.directive('popover', ['$compile', '$templateCache', function($compile, $templateCache) {
return {
restrict: 'A',
scope: {
header: '#header',
template: '=template'
},
link: function(scope, element) {
element[0].onclick = function (event) {
var popover = document.createElement('div'),
header = document.createElement('h4'),
content = document.createElement('p');
header.textContent = scope.header;
content.innerHTML = $templateCache.get(scope.template);
popover.appendChild(header);
popover.appendChild(content);
document.body.appendChild($compile(popover)(scope)[0]);
scope.$apply();
}
}
};
}]);
app.directive('stylesSelect', ['$compile', '$filter', function($compile, $filter) {
return {
restrict: 'E',
scope: {
selectedStyles: '=selectedStyles'
},
template: '<div ng-repeat="s in styles"><label><input type="checkbox" ng-model="s.selected" ng-change="selectStyle()" /> {{s.label}}</label></div>',
link: function(scope, element) {
scope.styles = [
{label: 'Hipster', selected: false},
{label: 'Hip-Hop', selected: false},
{label: 'Punk', selected: false}
];
scope.selectStyle = function() {
scope.selectedStyles = $filter('filter')(scope.styles, {selected: true});
};
}
}
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.1/angular.min.js"></script>
<div ng-app="MyApp">
<div ng-controller="MyController">
{{test}}
<button popover template="'popoverContent.html'" header="Select your styles" type="button">Show Popover</button>
<script type="text/ng-template" id="popoverContent.html">
<styles-select selected-styles="selectedStyles"></styles-select>
</script>
</div>
</div>
It gonna make me crazy... Please Help lol
Thank you
Instead of changing values in different scopes, try to use a service with a promise. This way the popover service is more reusable in your application.
var app = angular.module('MyApp', []);
app.controller('MyController', ['$scope', 'popover',
function($scope, popover) {
$scope.selectedStyles = [];
$scope.showStylesSelect = function() {
popover.show({
templateUrl: 'popoverContent.html',
scope: {
header: 'Select your style',
styles: [{
label: 'Hipster',
selected: false
}, {
label: 'Hip-Hop',
selected: false
}, {
label: 'Punk',
selected: false
}]
}
}).then(function(result) {
$scope.selectedStyles = result.selectedStyles;
});
};
$scope.$watch('selectedStyles', function(newValue, oldValue) {
console.log(newValue);
});
}
]);
app.factory('popover', ['$rootScope', '$q', '$compile', '$templateCache',
function($rootScope, $q, $compile, $templateCache) {
function showPopover(options) {
var defer = $q.defer(),
scope = $rootScope.$new(),
popover = document.createElement('div'),
header = document.createElement('h4'),
content = document.createElement('p');
angular.extend(scope, options.scope || {});
scope.close = function() {
popover.parentNode.removeChild(popover);
defer.resolve(scope);
};
header.textContent = options.header || '';
content.innerHTML = $templateCache.get(options.templateUrl);
popover.appendChild(header);
popover.appendChild(content);
document.body.appendChild($compile(popover)(scope)[0]);
return defer.promise;
}
return {
show: showPopover
}
}
]);
app.directive('stylesSelect', ['$filter',
function($filter) {
return {
restrict: 'E',
scope: false,
template: '<div ng-repeat="s in styles"><label><input type="checkbox" ng-model="s.selected" ng-change="selectStyle()" /> {{s.label}}</label></div><button ng-click="close()">close</button>',
link: function(scope) {
scope.selectStyle = function() {
scope.selectedStyles = $filter('filter')(scope.styles, {
selected: true
});
};
}
}
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.1/angular.min.js"></script>
<div ng-app="MyApp" class="ng-scope">
<script type="text/ng-template" id="popoverContent.html">
<styles-select selected-styles="selectedStyles"></styles-select>
</script>
<div ng-controller="MyController" class="ng-scope ng-binding">
<button ng-click="showStylesSelect()">Show Popover</button>
</div>
</div>

angularJS how to $eval the function with parameter

I am trying to implement the directive, in the directive, I want to $eval the values which contains the function name and parameter value:
Html page:
<select mydirective="action('pValue')">
AngularJS directive code:
app.directive('mydirective', function ($timeout) {
return {
restrict: 'A',
link: function ($scope, element, attr) {
$timeout(function () {
$scope.$eval(attr.mydirective);
});
}
}
What I am expected is it will invoke the action function define in scope and pass the pValue as function parameter. How can I make it work please?
What you want happen automatically, the function will invoke with the value, this is the purpose of eval:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.action = function(val) {
alert(val);
}
});
app.directive('mydirective', function($timeout) {
return {
restrict: 'A',
link: function($scope, element, attr) {
$timeout(function() {
$scope.$eval(attr.mydirective);
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular.min.js"></script>
<div ng-app="plunker" ng-controller="MainCtrl">
<select mydirective="action('pValue')"></select>
</div>
For those whom looking for a way to pass $event info to custom directive method see example below:
TEMPLATE:
<div on-touch-end="onTouchEnd( 'some data' )">
TOUCH ME!
</div>
CONTROLLER:
$scope.onTouchEnd = function( data ) {
console.log("onTouchEnd event with data", data, event );
};
DIRECTIVE:
.directive('onTouchEnd', function() {
return {
restrict : 'A',
link : function( $scope, $element, $attr ) {
$element.on('touchend', function( event ) {
$scope.$apply(function() {
$scope.$eval( $attr.onTouchEnd );
});
});
}
}
})

Resources