service only works after `$rootScope.$appy()` applied - angularjs

I am loading the template from angular-service but that's not updating the template unless i use the $rootScope.$appy(). but my question is, doing this way this the correct approach to update the templates?
here is my code :
var app = angular.module('plunker', []);
app.service('modalService', function( $rootScope ) {
this.hide = function () {
this.show = false;
}
this.showIt = function () {
this.show = true;
}
this.setCategory = function ( category ) {
return this.showPath = category+'.html'
}
this.showCategory = function (category) {
this.setCategory( category )
$rootScope.$apply(); //is this correct?
}
})
app.controller('header', function($scope) {
$scope.view = "home view";
});
app.controller('home', function($scope, modalService) {
$scope.name = 'World';
$scope.service = modalService;
});
//header directive
app.directive('headerDir', function( modalService) {
return {
restrict : "E",
replace:true,
templateUrl:'header.html',
scope:{},
link : function (scope, element, attrs) {
element.on('click', '.edit', function () {
modalService.showIt();
modalService.showCategory('edit');
});
element.on('click', '.service', function () {
modalService.showIt();
modalService.showCategory('service');
})
}
}
});
app.directive('popUpDir', function () {
return {
replace:true,
restrict:"E",
templateUrl : "popup.html"
}
})
Any one please advice me if i am wrong here? or can any one show me the correct way to do this?
click on links on top to get appropriate template to load. and click on the background screen to close.
Live Demo

If you don't use Angular's error handling, and you know your changes shouldn't propagate to any other scopes (root, controllers or directives), and you need to optimize for performance, you could call $digest on specifically your controller's $scope. This way the dirty-checking doesn't propagate. Otherwise, if you don't want errors to be caught by Angular, but need the dirty-checking to propagate to other controllers/directives/rootScope, you can, instead of wrapping with $apply, just calling $rootScope.$apply() after you made your changes.
Refer this link also Angular - Websocket and $rootScope.apply()

Use ng-click for handling the click events.
Template:
<div ng-repeat="item in items">
<div ng-click="showEdit(item)">Edit</div>
<div ng-click="delete(item)">Edit</div>
</div>
Controller:
....
$scope.showEdit = function(item){
....
}
$scope.delete = function(item){
....
}
If you use jquery or any other external library and modify the $scope, angular has no way of knowing if something has changed. Instead if you use ng-click, you let angular track/detect change after you ng-click handler completes.
Also it is the angular way of doing it. Use jquery only if there is no other way to save the world.

Related

Update href in AngularJS before navigating to the URL

In an AngularJS application I have the following code:
<a target="_blank" ng-href="{{someProperty.href}}" ng-click="someMethod($event)">Hello!</a>
Now, someMethod() and someProperty belong to the same service.
Initially, someProperty.href has a default value.
What I need to do is that when the user clicks on the link, some calculation is performed and someProperty.href gets a new value. This new value need to be reflected in the ng-href and the user should be redirected to that new href.
tried reconstructing it and it seems to work, clicking on the link opens a new tab with the new url.
https://plnkr.co/edit/gy4eIKn02uF0S8dLGNx2?p=preview
<a target="_blank" ng-href="{{someService.someProperty.href}}" ng-click="someService.someMethod()">
Hello!
<br/>
{{someService.someProperty.href}}
</a>
You can do it as like the below code
;(function(angular) {
angular.module('myApp.directives')
.directive('myExample', myExample);
myExample.$inject = ['$timeout'];
function myExample($timeout) {
return {
restrict: 'A',
scope: {
myExample: '&',
ngHref: '='
},
link: function(scope, element, attrs) {
element.on('click', function(event) {
event.preventDefault();
$timeout(function() {
scope.myExample();
scope.$apply();
var target = attrs.target || '_blank';
var url = scope.ngHref;
angular.element('')[0].click();
});
});
}
};
}
})(angular);
In Controller
;(function(angular) {
'use strict';
angular.module('myApp.controllers').controller('HomeController', HomeController);
HomeController.$inject = ['$scope'];
function HomeController($scope) {
$scope.url = 'http://yahoo.com';
$scope.someFunction = function() {
$scope.url = 'http://google.com';
};
}
})(angular);
In HTML You can use like
<div ng-controller="HomeController">
<a ng-href="url" my-example="someFunction()" target="_blank">Click me to redirect</a>
</div>
Here instead of ng-click I have used custom directive which simulates the ng-click but not as exactly as ng-click
If the parent scope function is async you change your directive and someFunction in controller as like below
#Directive
;(function(angular) {
angular.module('myApp.directives')
.directive('myExample', myExample);
myExample.$inject = ['$timeout'];
function myExample($timeout) {
return {
restrict: 'A',
scope: {
myExample: '&',
ngHref: '='
},
link: function(scope, element, attrs) {
element.on('click', function(event) {
event.preventDefault();
scope.myExample().then(function() {
$timeout(function() {
scope.$apply();
var target = attrs.target || '_blank';
var url = scope.ngHref;
angular.element('')[0].click();
});
});
});
}
};
}
})(angular);
#Controller
;(function(angular) {
'use strict';
angular.module('myApp.controllers').controller('HomeController', HomeController);
HomeController.$inject = ['$scope', '$q'];
function HomeController($scope, $q) {
$scope.url = 'http://yahoo.com';
$scope.someFunction = function() {
var deferred = $q.defer();
$scope.url = 'http://google.com';
deferred.resolve('');
return deferred.promise;
};
}
})(angular);
Here I just simulated the async, it may be your http call too
make sure the value in href tag is updated after you click on it. Try debugging the ng-click function. According to the official documentation:
Using AngularJS markup like {{hash}} in an href attribute will make the link go to the wrong URL if the user clicks it before AngularJS has a chance to replace the {{hash}} markup with its value. Until AngularJS replaces the markup the link will be broken and will most likely return a 404 error. The ngHref directive solves this problem.
In your case, i think the old link is not getting updated with the new values of the model. Hence, redirecting to old link.
Try calling a function on ui-sref or ng-href which will return the state name that you want to redirect. Something like this
html:
<a ui-href="{{GetUpdatedHref()}}" >Hello!</a>
controller.js
$scope.GetUpdatedHref = function(){
//perform your http call while it's being processed show a loader then finally return the desired state (page location)
return "main.home"
}
If this doesn't work for you use ng-click instead of ui-sref and then $state.go("main.home") inside function.
Hope this may resolve your problem.

angular.js - passing an object from directive to the view controller

*Please note: there is a Plunker link:
https://plnkr.co/edit/PAINmQUHSjgPTkXoYAxf?p=preview
At first I wanted to pass an object as parameter on directive click event,
(it was too complex for me), so i decide to simplify it by sending the event and the object separately.
In my program the object is always undefined in the view-controller and the view itself in oppose to the Plunker example.
In the Plunker example it's undefined on the controller only on the first passing event (the second directive click event works fine).
I don't know why I get 2 different results in the simple Plunker simulation and my massive code, I hope both cases are 2 different results of the same logic issue.
A solution with passing an object as parameter from directive by event function will be welcome as well.
HTML
<pick-er get-obj-d="getObj()" obj-d="obj"></pick-er>
View-Controller
function mainController($scope)
{
$scope.test = "work";
$scope.getObj = function(){
$scope.test = $scope.obj;
}
}
Directive:
function PickerDirective()
{
return {
restrict: 'E',
scope: // isolated scope
{
obj : '=objD',
getObj: '&getObjD'
},
controller: DirectiveController,
template:`<div ng-repeat="item in many">
<button ng-click="sendObj()">
Click on me to send Object {{item.num}}
</button>
</div>`
};
function DirectiveController($scope, $element)
{
$scope.many =[{"num":1,}];
$scope.sendObj = function() {
$scope.obj = {"a":1,"b":2, "c":3};
$scope.getObj();
}
}
}
I your case, will be more simple to use events, take a look at this Plunker:
https://plnkr.co/edit/bFYDfhTqaUo8xhzSz0qH?p=preview
Main controller
function mainController($scope)
{
console.log("mainCTRL ran")
$scope.test = "work";
$scope.$on('newObj', function (event, obj) {
$scope.obj = obj;
$scope.test = obj;
});
}
Directive controller
function DirectiveController($scope, $element)
{
$scope.many =[{"num":1,}]
$scope.sendObj = function() {
$scope.$emit('newObj', {"a":1,"b":2, "c":3} )
}
}
return {
restrict: 'E',
controller: DirectiveController,
template:'<div ng-repeat="item in many"><button ng-click="sendObj()">Click on me to send Object {{item.num}}</button></div>'
}

How to broadcast events from directive to other controllers in AngularJS

I need somehow to emit event from part of the page (scrolling, clicking) that is served by one directive to other parts of the page, served by other controller so that it could be updated accordingly. Use case - for example Word document with annotations that are moving along with the page in the viewport.
SO in my design I have directive with link method in it and I need to broadcast events from it to other controllers in my app. What I have inside my link function:
element.bind('click', function (e) {
var eventObj = element.scrollTop();
scope.$broadcast('app.scrollOnDocument', eventObj);
});
This event cannot I cannot be see in other controllers directly - so code like this in other controller doesn't work:
$scope.$on('app.scrollOnDocument', function (e, params) {
console.log(e, params);
});
So what I have to do is to intercept those events in the same directive's controller and broadcast them to the higher scope like:
$scope.$on('app.scrollOnDocument', function(event, params){
//go further only if some_condition
if( some_condition ){
$rootScope.$broadcast('app.scrollOnDocumentOuter', params);
}
});
I am not sure this is the correct way of doing this. Maybe I am missing some directive property or setting to make it possible?
Non standard services can be passed to a directive like
.directive('notify', ['$rootScope', '$interval', function(rootScope, interval){
return {
restrict : 'E',
link : function(){
interval(function(){
rootScope.$broadcast('custom.event', new Date());
}, 1500);
}
};
}]);
The example below broadcasts an event every 1500ms.
If using the rootScope for communication cannot be avoided,you should always try unregistering the listener.
angular.module('app', [])
.controller('indexCtrl', ['$rootScope', '$scope',
function(rootScope, scope) {
scope.title = 'hello';
scope.captured = [];
var unregister = rootScope.$on('custom.event', function(evt, data) {
scope.captured.push(data);
});
scope.$on('$destroy', function() {
unregister();
});
}
])
.directive('notify', ['$rootScope', '$interval',
function(rootScope, interval) {
return {
restrict: 'E',
link: function() {
interval(function() {
rootScope.$broadcast('custom.event', new Date());
}, 1500);
}
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="indexCtrl">
<h1>{{title}}</h1>
<notify></notify>
<ul>
<li ng-repeat="event in captured">{{event|date:'medium'}}</li>
</ul>
</div>
For broadcasting in AngularJS, you always have to use $rootScope. You are listening always on $scope instead of $rootScope.

Extending a controller function within directive

I have a cancel function in my controller that I want to pass or bind to a directive. This function essentially clears the form. Like this:
app.controller('MyCtrl', ['$scope', function($scope){
var self = this;
self.cancel = function(){...
$scope.formName.$setPristine();
};
}]);
app.directive('customDirective', function() {
return {
restrict: 'E'
scope: {
cancel : '&onCancel'
},
templateUrl: 'form.html'
};
});
form.html
<div>
<form name="formName">
</form>
</div>
However, the $setPristine() don't work as the controller don't have access on the form DOM. Is it possible to extend the functionality of controller's cancel within the directive so that I will add $setPristine()?
Some suggested using jQuery to select the form DOM, (if it's the only way) how to do that exactly? Is there a more Angular way of doing this?
Since the <form> is inside the directive, the controller should have nothing to do with it. Knowing it would break encapsulation, i.e. leak implementation details from the directive to the controller.
A possible solution would be to pass an empty "holder" object to the directive and let the directive fill it with callback functions. I.e.:
app.controller('MyCtrl', ['$scope', function($scope) {
var self = this;
$scope.callbacks = {};
self.cancel = function() {
if( angular.isFunction($scope.callbacks.cancel) ) {
$scope.callbacks.cancel();
}
};
});
app.directive('customDirective', function() {
return {
restrict: 'E'
scope: {
callbacks: '='
},
templateUrl: 'form.html',
link: function(scope) {
scope.callbacks.cancel = function() {
scope.formName.$setPristine();
};
scope.$on('$destroy', function() {
delete scope.callbacks.cancel;
});
}
};
});
Use it as:
<custom-directive callbacks="callbacks"></custom-directive>
I'm not sure I am OK with this either though...

How can I change information in another view via an AngularJS Directive?

Okay, this is a convoluted question and I may be asking for a bad approach. If that's the case, please do let me know.
So I have a directive for a navigation bar. When someone clicks something, I managed to get the directive to add a class and therefore load the bar. Thanks to StackOverflow.
But now, I have a service that gets and sets values. When a value is changed in the service, I want to reflect that in a view. Is such a thing possible?
EDIT
For clarification, if I do use a $apply(function()...., how exactly do I do that? My view has something like. My view is not bound to any particular controller, or scope. Not sure if it should be. But here's a snippet of my view:
<p>
Are you sure you change that song,
<br />
{{ songs[0].title }}
</p>
Here's my directive:
angular.module('MyApp')
.directive('navbar', function () {
return {
restrict: 'A',
templateUrl : '/views/partials/nav.html',
controller: function ($scope, ModalService) {
$scope.ms = ModalService;
$scope.songs = {};
$scope.$watch('ms.songs', function(newVal, oldVal) {
if(newVal != null) {
$scope.$apply(function() {
$scope.songs = newVal;
});
}
});
},
Have you tried this?
angular.module('MyApp')
.directive('navbar', function () {
return {
restrict: 'A',
templateUrl : '/views/partials/nav.html',
controller: function ($scope, ModalService) {
$scope.songs = ModalService.songs;
}
});
I did run into a scenario recently where I had to setup a watch on a service property within a directive and the solution was to setup the watch within the link function similar to:
link: function(scope, element, attrs) {
// other link code
scope.$watch(function() { return svc.property; }, function(data) {
// do something here
});
// other link code
}

Resources