how to detect location change in an angular directive? - angularjs

I have an angular directive detectFocus as :
app.directive("detectFocus", function ($focusTest, $location, $rootScope) {
return {
restrict: "A",
scope: {
onFocus: '&onFocus',
onBlur: '&onBlur',
},
link: function (scope, elem) {
$rootScope.$on('$locationChangeSuccess', function (event, newUrl, oldUrl) {
return;
});
elem.on("focus", function () { console.log("focus");
scope.onFocus();
$focusTest.setFocusOnBlur(true);
});
elem.on("blur", function () { console.log("blur");
scope.onBlur();
if($focusTest.getFocusOnBlur())
elem[0].focus();
});
}
}
});
this directive check two event focus and blur, so is there any way to check location change from this directive.

Try binding a listener on Angulars built in $locationChangeSuccess event. This event is fired every time your app has finished changing a location.
Your link function could look somehow like this.
link: function($rootScope) {
$rootScope.$on('$locationChangeSuccess', function (event, newUrl, oldUrl) {
console.log('Changed from ', oldUrl, ' to ', newUrl);
});
}

Try adding a watch on $location.path()
myModule.directive('highlighttab', ['$location', function(location) {
return {
restrict: 'C',
link: function($scope, $element, $attrs) {
var elementPath = $attrs.href.substring(1);
$scope.$location = location;
$scope.$watch('$location.path()', function(locationPath) {
(elementPath === locationPath) ? $element.addClass("current") : $element.removeClass("current");
});
}
};
}]);
adding watch variable is not much a recomented process since it will increase the load of the application. Instead you can use the $locationChangeSuccess event in angular js.
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.0/angular-route.min.js"></script>
<script>
var app = angular.module('routeApp', ['ngRoute']);
app.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/addStudent', {
template: '<div>Add Student</div>',
controller: 'addStudentController'
})
.when('/viewStudent', {
template: '<div>View Student</div>',
controller: 'viewStudentController'
})
.otherwise({
redirectTo: '/'
});
}]);
app.directive('activeLink', ['$location', function (location) {
return {
restrict: 'A',
link: function (scope, element, attrs, controller) {
var clazz = attrs.activeLink;
var path = attrs.href;
path = path.substring(1); //hack because path does bot return including hashbang
scope.location = location;
scope.$on('$locationChangeSuccess', function () {
console.log('$locationChangeSuccess changed!', new Date());
});
}
};
}]);
app.controller('addStudentController', function ($scope) {
$scope.message = "This is message from add student controller";
});
app.controller('viewStudentController', function ($scope) {
$scope.message = "This is message from view student controller";
});
app.controller('pageController', function ($scope, $location) {
$scope.GoTo = function (URl) {
$location.path('/' + URl);
};
});
</script>
<body ng-app="routeApp" ng-controller="pageController">
<h2>Sample Application</h2>
Add Student
View Student
home
<div ng-view></div>
</body>
</html>

inject $rootScope and use it like this :
$rootScope.$on('$stateChangeStart', function(evt, to, params) {
//do your stuff here
}
Furthermore if you use elem.on('focus/blur') you must call scope.$apply() in the even callback if you want angular to detect the changes.

Related

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>

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 * /
});
}
}
})

What will be the AngularJS equivalent of jQuery's .bind() / .trigger()

I have this trigger / bind code:
service:
$('body').trigger('ready');
directive:
$('body').bind("ready", function(){
alert("Ready was triggered");
});
Can I do the exact same thing only using angular? If yes, how?
You need to use events in AngularJS check the below sample example I hope it will be of help to you, please check this article for more information on $emit, $on and $broadcast event system in AngularJS
angular
.module('demo', [])
.controller('DefaultController', DefaultController)
.factory('helloService', helloService)
.directive('hello', hello);
function DefaultController() {
var vm = this;
}
helloService.$inject = ['$rootScope'];
function helloService($rootScope) {
var service = {
sendHello: sendHello
};
return service;
function sendHello() {
$rootScope.$broadcast('helloEvent', 'Hello, World!');
}
}
hello.$inject = ['$rootScope', 'helloService'];
function hello($rootScope, helloService) {
var directive = {
restrict: 'E',
scope: {
message: '='
},
link: linkFunc
}
return directive;
function linkFunc(scope, element, attrs, ngModelCtrl) {
scope.$on('helloEvent', function (event, data) {
element.text(data);
});
sendHello();
function sendHello() {
helloService.sendHello();
}
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="demo">
<div ng-controller="DefaultController as ctrl">
<hello></hello>
</div>
</div>

Using AngularJS service to update scope in other controller from other module

I am a newbie for AngularJS so maybe I am looking at this the wrong way. If so, please point me in the right direction.
Basically I want to update some DOM elements that reside in another controller in another module.
I am trying to send data through a service but it seems that it is not updated on the destination scope.
var mainModule = angular.module('main', []);
var appModule = angular.module('app', ['main']);
appModule.controller("appCtrl", function ($scope, $routeParams, mainService) {
$scope.mainService = mainService;
var initialize = function () {
$scope.mainService.currentID = $routeParams.productId;
}
initialize();
});
mainModule.factory('mainService', function () {
var mainService = { currentID: 0 };
return mainService
});
mainModule.controller('mainCtrl', ['$scope', 'mainService', function ($scope, mainService) {
$scope.mainService = mainService;
$scope.function1Url = "function1/" + $scope.mainService.currentID;
$scope.function2Url = "function2/" + $scope.mainService.currentID;
//CurrentID is always 0!!
}]);
I expect that when calling the initialize() function in the appCtrl, it will see the currentID param in the service which is also used by the mainCtrl.
For updating controller using service, I strongly recommend you to use $rootScope.$broadcast and $rootScope.$on. Here is an example of how you can do it, and link to a blog:
$rootScope.$broadcast('myCustomEvent', {
someProp: 'Sending you an Object!' // send whatever you want
});
// listen for the event in the relevant $scope
$rootScope.$on('myCustomEvent', function (event, data) {
console.log(data); // 'Data to send'
});
http://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
Here is your working solution:
var mainModule = angular.module('main', []);
var productModule = angular.module('product', ['main']);
productModule.service('mainService', ['$rootScope', '$timeout', function ($rootScope, $timeout) {
this.method1 = function () {
alert('broadcast');
$rootScope.$broadcast('myCustomEvent', {
newValue: 'This is an updated value!'
});
}
}]);
productModule.controller('mainCtrl', ['$scope', '$rootScope', function ($scope, $rootScope){
$scope.myValue = 'Initial value';
$rootScope.$on('myCustomEvent', function (event, data) {
$scope.myValue = data.newValue;
alert('received broadcast');
});
}]);
productModule.controller("productCtrl", function ($scope, mainService) {
$scope.mainService = mainService;
$scope.clickMe = 'Click to send broadcast';
$scope.callService = function () {
$scope.clickMe = 'Broadcast send!';
$scope.mainService.method1();
}
});
And HTML:
<body ng-app='main'>
<div ng-controller="mainCtrl"><b>My value:</b>{{myValue}}</div>
<div id="product" ng-controller="productCtrl">
<button ng-click="callService()">{{clickMe}}</button>
</div>
<script type="text/javascript">angular.bootstrap(document.getElementById("product"), ['product']);</script>
</body>
You have a couple different methods of doing this.
I agree with uksz, you should use broadcast/emit to let other scopes know of the change, let them handle as needed.
Broadcast goes to all child scopes of the element
$scope.$broadcast("Message Name", "payload, this can be an object");
Emit goes to all parent scopes of this element
$scope.$emit("message name", "payload, this can be an object");
Other option is you can also require the other controller
appModule.directive('myPane', function() {
return {
require: '^myTabs',
scope: {},
link: function(scope, element, attrs, tabsCtrl) {
tabsCtrl.addPane(scope);
}
};
});
Lastly you can include a function on the scope so you can let the parent scope know what's going on
appModule.directive('myPane', function() {
return {
scope: {
doSomething: '&something'
},
link: function(scope, element, attrs) {
$scope.doSomething(test);
}
};
});

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