How to update the scope value without using $watch - javascript

I have using custom directive
<users stats="stats"></users>
When we change the scope object from main controller, i have updating directive scope value also
app.directive('users', function(){
var directive = {};
directive.restrict = "E";
directive.templateUrl = "templates/partials/users_stats.html";
directive.scope = {stats:'='};
directive.controller = "userStatsCtrl"
return directive;
});
So, Inside the directive controller, i am doing some func. like
app.controller("userStatsCtrl", function($scope){
$scope.$watch('stats', function(newValue) {
if (angular.isDefined(newValue)) {
.....
}
});
})
So here, i am using $watch to listen the scope, If it is update, I will do the some perform.
my question is, I dont want to use watch to listen the scope, If scope gets updated, i need to do some perform.
So how to update scope value without using scope.$watch

When ever "scope.stats" value get changed in your main controller, you can broadcast an event and receive the same event in your directive and do operation what ever you want.
Example code for broadcast an event:
$scope.$broadcast('yourEventName');
Receive an event in directive:
$scope.$on('yourEventName', function(){
});

how about trying:
<users stats="stats" ng-change="onUserStatChange($event)"></users>
or
<users stats="stats" ng-blur="onUserStatChange($event)"></users>
and then in controller:
$scope.onUserStatChange = function(event) {
//event.whatever
}

Related

Angular Parent Scope Variable not changing

I have a parent controller where I set instantiate an object called links. I assign a property with a value that I want to change within another function. However when I set the variable in the instagramModel the links.imagesa doesn't get updated.
I print the value out in the console and the parentscope doesn't get updated. I have thought I followed the rules of prototypical inheritance.
Why is $scope.links.imagesa not updating?
.controller('HomeCtrl', function HomeController($scope, titleService, config, $sails, $timeout, $upload, leafletData, $modal, $log) {
$scope.links = {};
$scope.links.imagesa = "This should change";
$scope.instagramModal = function (size) {
var modalInstance = $modal.open({
templateUrl: 'instagramModal.html',
controller: 'InstagramModalInstanceCtrl',
size: size,
resolve: {
items: function () {
return $sails.get("/instagram/self").success(function (response) {
return response.data;
}).error(function (response) {
console.log('error');
});
}
}
});
modalInstance.result.then(function (selectedItem) {
$scope.links.imagesa = "wept";
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
};
$scope.ask = function () {
console.log($scope.links.imagesa);
};
});
If you want the parent's scope to get updated, then you must use $scope.$parent.links.imagesa since the changes made in child scope are not reflected in the parent scope directly.
I had the HomeCtrl instantiated in the UI Router and also on the template page using ng-controller. This messed up the scope.
Angular UI's modals use $rootScope by default. See documentation at "http://angular-ui.github.io/bootstrap/#/modal"
You can pass a scope parameter with a custom scope when you open the modal – e.g. scope: $scope if you want to pass the parent scope. The modal controller will create a sub-scope from that scope, so you will only be able to use it for your initial values.
Hence, if you want to update any value, keep the object/data in rootScope.

Angular: $rootScope variable vs event

I'm dealing with an app that manages users login. Like in many apps, i want to change the header when the user logs in.
I've a main file (index.html) which uses ng-include to include the header.html
I found two solutions (i'm new to angular, so both may be wrong):
1) use a $rootScope.broadcast()
So when the user logs in I broadcast (the auth.js, it's inside a factory) a message that is intercepted by the controller in the header.
the auth.js
$rootScope.$broadcast('logged',user);
the controller.js
$scope.$on('logged', function(evnt, message){
$scope.user = message;
});
the header.html
<div class="header" ng-controller="GcUserCtrl as gcUserCtrl">
...
<li><a ng-show="user" href="#">User: {{user.name}}</a></li>
2) set a $rootScope variable
As far as I understood $rootScope is the root of all the scope (the naming is quite smart) and all the $scope have access to it.
the auth.js
$rootScope.user=user;
the heaeder.html (no controller is needed here)
<div class="header">
...
<li><a ng-show="user" href="#">User: {{user.name}}</a></li>
Now, what's the correct way to handle it?
the first seems a bit more expensive since the broadcast may have to do many checks.
the second .. well, I'm not a fan of global variables..
EDIT
3) use service
after the comment of alex I add this options, even if I'm not able to make it working. (here the plunkr)
it does not work without events
index.html
...
<ng-include src="'header.html'"></ng-include>
...
header.html
as for the number 1)
controller.js
.controller('GcUserCtrl', ['$scope','my.auth','$log', function ($scope, auth, $log) {
$scope.user = auth.currentUser();
}]);
my.auth.js
.factory('my.auth', ['$rootScope', '$log', function ($rootScope, $log, localStorageService) {
var currentUser = undefined;
return {
login: function (user) {
currentUser = user;
...
},
...
currentUser: function () {
return currentUser;
}
};
}]);
The problem here is that the controller is called only the first time and nothing happens after the login.
As I stated earlier you will want to use a Service which will store the user's information. Attach user information to this service where ever you are authenticating the user. If you have questions about the best way to authenticate that would be a seperate question but you may want to look into using a Login Factory that does the actual authentication (and any authorization). You can then inject the login Service into that factory. I have created a Plunker here as a reference.
var app = angular.module('myApp', []);
app.service('SessionService', function () {
this.attachUser = function(userId, fName){
this.userId = userId;
this.fName = fName;
}
});
app.controller('MainCtrl', function($scope, SessionService){
// You will want to invoke attachUser some other way (perhaps on authentication), this is for test purposes only
SessionService.attachUser(1234, 'John');
$scope.userName = SessionService.fName;
});
The code above is an example of your Service. This will act as a Session handler and store important information about the user. The controller MainCtrl can then invoke properties in the SessionService using dependency injection. The part I mentioned at the beginning of this post, SessionService.attachUser(userId, fName) would most likely live in a login factory.
The reason this is the best choice is because it decouples your application. It puts the session (which is really what you are storing in global variables) in a place that is designated to store that data. It makes it maintainable. You do not need to find every occurrence of $rootScope, for instance.
EDIT:
New plunker uses rootScope broadcast/on to capture changes
Events are the preferred way to communicate that action needs to be taken by something else. That an action occurred that something else might be interested in action against. It also reduces scope pollution as you mentioned.
The comment about using a service in this case is only partially accurate. All of the login logic could, and should, be put into a single service specific to logging and logging out. That service would then broadcast the event when a login occurs.
module.service('LoginHelper', function($rootScope) {
this.loginUser = function(username, password) {
// on success
$rootScope.broadcast('loggedIn', logginUserData)
}
this.logout = function() {
// on success
$rootScope.broadcast('loggedOut')
}
})
The logged in data should be stored and accessible by the service.
Alternatively, $emit could be used on $rootScope. You would then only be able to watch for the 'loggedIn' event on the $rootScope by there would be marginally less overhead.
Avoid watches
An event would be the appropriate way to go for this kind of requirement, like how alex has pointed out. A plunk demonstrating an example: http://plnkr.co/edit/v6OXjOXZzF9McMvtn6hG?p=preview
But for this particular scenario, I don't think the "angular way" is the "way". Given the nature of how $broadcast and/or $emit works (i.e. the default way events work in angular) I would avoid them...(Read the docs to understand why). In short, these mechanisms are meant to trigger listeners (attached to some scope) up/down the scope heirarchy. You don't really need all that. (Ref code for $emit)
I'd normally rely on other event propagation mechanisms (considering this pattern of requirement).
app.controller('MainCtrl', function($scope, SessionService, $document){
// You will want to invoke attachUser some other way (perhaps on authentication), this is for test purposes only
$scope.isLoggedIn = false;
$document.bind('$loggedin', function(){
$scope.isLoggedIn = true;
$scope.user = SessionService.fName;
});
$scope.logout = function() {
SessionService.attachUser(null, null);
$scope.isLoggedIn = false;
};
});
app.controller('LoginCtrl', function($scope, SessionService, $document){
$scope.doLogin = function() {
console.log('doLogin');
SessionService.attachUser(1234, $scope.username);
var doc = $document[0];
var evt = new Event('$loggedin');
doc.dispatchEvent(evt);
};
});
Plunk
Of course, when you are done with that view, always cleanup. Handle the $destroy event on that controller's scope and unbind the event handler...
$scope.$on('$destroy', function() {
$document.unbind('$loggedin');
};
Refer MDN for more on how to trigger events using DOM.
Update: [24 Sep]
Here is a small directive setup which demonstrates the point:
app.directive('ngNest', function($parse, $compile, $timeout){
var end = false;
var level = 0;
var fnPostLink = function(scope, element, attrs) {
//console.log('start:', ++fnPostLink.count);
var lvl = attrs.level;
if(!lvl) {
throw 'Level not specified';
}
var create = document.createElement.bind(document);
var level = parseInt(lvl);
var count = 0;
var div = create('div');
div.setAttribute('ng-controller', 'DummyCtrl');
var cls = function() {
return 'margin ' + (count % 2 ? 'even' : 'odd');
//return 'margin even';
};
div.setAttribute('class', cls());
var node = div;
while(count++ < level - 1) {
var child = create('div');
child.setAttribute('ng-controller', 'DummyCtrl');
child.setAttribute('class', cls());
node.appendChild(child);
node = child;
}
node.setAttribute('ng-controller', 'FinalCtrl');
node.innerHTML = 'foo';
var $new = $compile(div)(scope);
var el = element;
el.append($new);
};
fnPostLink.count = 0;
var fnPreLink = function(scope, element, attrs) {
//console.log('prelink');
};
var api = {
link: {
post: fnPostLink,
pre: fnPreLink
},
template: '<div></div>',
scope: {},
restrict: 'E',
replace: true
};
return api;
});
It simply nests divs attaching a controllers to it. I am attaching these two controllers:
app.controller('DummyCtrl', function($scope){
});
app.controller('FinalCtrl', function($scope, $document){
$scope.$on('$myEvt', function(){
console.log('$myEvt', $scope.$id, new Date().getTime());
});
$document.bind('$myEvt', function(){
console.log('$myEvt', $scope.$id, new Date().getTime());
});
});
FinalCtrl is added to the tail; DummyCtrl is added to the rest.
In the html template I do something like:
<ng-nest level="10"></ng-nest>
There is also in the html file a nested markup which is manually put there...
Entire code may be found here: https://gist.github.com/deostroll/a9a2de04d3913f021f13
Here are the results I've obtained running from my browser:
Live reload enabled.
$broadcast 1443074421928
$myEvt 14 1443074421929
$myEvt 19 1443074421930
DOM 1443074426405
$myEvt 14 1443074426405
$myEvt 19 1443074426405
You can notice the difference in the ticks when I've done $broadcast. I have done a $broadcast on $rootScope; hence angular walks down the scope tree depth-first and triggers those listeners attached the respective scopes, and, in that order...The stuff in $emit & $broadcast source code also validates this fact.

Trigger a function in a child directive from it's parent [angularJS]

So I totally do this in reverse all the time when using the directive property require: '^ParentCtrl' inside the child directive. Using require to then call the parent function; however, I need to do this in reverse.
Question:
How do I trigger FROM a parent directive the execution of a function IN a child directive.
Note:
1. Child Directive has no function is inside a link:
2. essentially I want a reverse require.
Parent Directive:
'use strict';
angular.module('carouselApp')
.directive('waCarousel', function() {
return {
templateUrl: 'views/carousel/wa.carousel.html',
controller: function($scope) {
var self = this;
// this function is being called based on how many pages there are
self.carouselElLoaded = function(result) {
var count = 1;
Carousel.params.pageRenderedLength += count;
//when all the pages are loaded
if (Carousel.params.pageRenderedLength === Carousel.params.pageLength) {
Carousel.params.carouselReady = true;
// !!!!!!!! Trigger will go here!!!!!!!!!//
ChildCtrl.drawHotspots(); // (**for placement only**)
} else {
Carousel.params.carouselReady = false;
}
};
}
}
})
Child Directive:
'use strict';
angular.module('carouselApp')
.directive('waHotspots', function() {
return {
require: '^waCarousel',
link: function (scope, element, attrs, ctrl) {
//call this directive based on how
scope.drawHotspots = function () {...};
}
})
This is possible by having the parent controller talk to the child controller through a well defined API, that you create. The idea is that you want to maintain loose coupling between the parent and the child directive by having each respective controller knowing as little about each other as possible, but still have enough knowledge to get the job done.
To achieve this, require the parent directive from the child directive, and let the child directive register itself with parent's controller:
Child directive:
require: '^parentDirective',
controller: function(){
this.someFunc = function() {...}
},
link: function(scope,element,attr, parentCtrl){
parentCtrl.register(element);
}
Then in your parent directive, implement the register function, and get the child's controller, and call the child's function when needed:
Parent directive:
controller: function(){
var childCtrl = undefined;
this.register = function (element) {
childCtrl = element.controller();
}
this.callChildFunc = function (){
childCtrl.someFunc();
}
},
link: function (scope,element){
var ctrl = element.controller();
ctrl.callChildFunc();
}
You could always trigger it via a $watch. Just pass in the parent scope value that you want to watch and change it's value.
Parent:
$scope.drawHotspots = false;
Template:
waHotspots the-trigger="drawHotspots"....
Child Directive:
localTrigger: '#' // Receive the value to watch
scope.$watch('localTrigger',function() {
// call drawHotspots if value is set to true
});
Its on old topic but I came here today so might others ...
I think the best approche is to use a Service
angular.module('App').service('SomeService', [SomeService]);
Then inject the service into both the parent and child ...
controller : ['$rootScope', '$scope','SomeService', SomeDirectiveController],
Use the service to talk to each other ...
In their controllers SomeService.setParent(this) and SomeService.setChild(this)
Service would have a field to hold the references :
this.parentCtrl = null;
this.childCtrl = null;//or [] in-case you have multiple childs!
Somewhere in the parent : SomeService.childCtrl.someFunctionInChild()
Or if you want a restricted access , in service make the fields private :
var parentCtrl = null;
var childCtrl = null;//or [] in-case you have multiple childs of the same type!
this.callUserFunc = function(param){childCtrl.someFunctionInChild(param)};
And Somewhere in the parent : SomeService.callUserFunc(myparam)

How to set a variable in different controller in AngularJS?

I'd like to do simple notifications in angular. Here is the code I've written.
http://pastebin.com/zYZtntu8
The question is:
Why if I add a new alert in hasAlerts() method it works, but if I add a new alert in NoteController it doesn't. I've tried something with $scope.$watch but it also doesn't work or I've done something wrong.
How can I do that?
Check out this plnkr I made a while back
http://plnkr.co/edit/ABQsAxz1bNi34ehmPRsF?p=preview
I show a couple of ways controllers can use data from services, in particular the first two show how to do it without a watch which is generally a more efficient way to go:
// Code goes here
angular.module("myApp", []).service("MyService", function($q) {
var serviceDef = {};
//It's important that you use an object or an array here a string or other
//primitive type can't be updated with angular.copy and changes to those
//primitives can't be watched.
serviceDef.someServiceData = {
label: 'aValue'
};
serviceDef.doSomething = function() {
var deferred = $q.defer();
angular.copy({
label: 'an updated value'
}, serviceDef.someServiceData);
deferred.resolve(serviceDef.someServiceData);
return deferred.promise;
}
return serviceDef;
}).controller("MyCtrl", function($scope, MyService) {
//Using a data object from the service that has it's properties updated async
$scope.sharedData = MyService.someServiceData;
}).controller("MyCtrl2", function($scope, MyService) {
//Same as above just has a function to modify the value as well
$scope.sharedData = MyService.someServiceData;
$scope.updateValue = function() {
MyService.doSomething();
}
}).controller("MyCtrl3", function($scope, MyService) {
//Shows using a watch to see if the service data has changed during a digest
//if so updates the local scope
$scope.$watch(function(){ return MyService.someServiceData }, function(newVal){
$scope.sharedData = newVal;
})
$scope.updateValue = function() {
MyService.doSomething();
}
}).controller("MyCtrl4", function($scope, MyService) {
//This option relies on the promise returned from the service to update the local
//scope, also since the properties of the object are being updated not the object
//itself this still stays "in sync" with the other controllers and service since
//really they are all referring to the same object.
MyService.doSomething().then(function(newVal) {
$scope.sharedData = newVal;
});
});
The notable thing here I guess is that I use angular.copy to re-use the same object that's created in the service instead of assigning a new object or array to that property. Since it's the same object if you reference that object from your controllers and use it in any data-binding situation (watches or {{}} interpolation in the view) will see the changes to the object.

Angular JS: How do I set a property on directive local scope that i can use in the template?

I want to access a variable in this case map in the directive without having to predefine it like setting it as an attr of the directrive map="someValue". And also i dont want to use scope.$apply because i actually only want the variable in the isolated scope of the directive. Is this even possible ?
What is the best practice here? Basically my directive needs to do both. Access the parent scope and have its own scope which with i can build the template with.
Thank you everybody.
Here my Js code:
.directive('myFilter', function() {
return {
restrict: 'E',
scope: {
source: '=source',
key: '=key',
},
link: function(scope, element, attrs) {
scope.$on('dataLoaded', function(e) {
scope.map = {};
angular.forEach(scope.source, function(paramObj) {
if (!scope.map[paramObj[scope.key]]) {
var newEntry = {
value: paramObj[scope.key],
isChecked: false
}
scope.map[paramObj[scope.key]] = newEntry;
}
});
});
}
}
});
and my html:
<my-filter source="metaPara.filteredParameters" key="'value'">
<table class="table table-borered">
<tr data-ng-repeat="item in map">
<td>{{item.value}}</td>
</tr>
</table>
</my-filter>
You might want to refer to the Angular documentation for directives, again.
If you want an isolate-scope (a scope which has no access to ancestors), then use
scope : { /* ... */ }
otherwise, if you want a unique scope, which does have access to ancestors, use
scope : true
Then, you can put your HTML-modifying or event-listening (that doesn't rely on ng-click or something else Angular already covers) in
link : function (scope, el, attrs, controller) { }
...and you can put all of your regular implementation inside of
controller : ["$scope", function ($scope) {
var myController = this;
myController.property = "12";
}],
controllerAs : "myController"
So that in your template you can say:
<span>{{ myController.property }}</span>
You can also use a pre-registered controller, which you call by name:
controller : "mySimpleController",
controllerAs : "myController"
Also, rather than using $scope.$apply, I'd recommend using $timeout (has to be injected).
The difference is that $scope.$apply will only work at certain points -- if you're already inside of a digest cycle, it will throw an error, and not update anything.
$timeout( ) sets the updates to happen during the next update-cycle.
Ideally, you should know whether or not you need an $apply or not, and be able to guarantee that you're only using it in one spot, per update/digest, but $timeout will save you from those points where you aren't necessarily sure.

Categories

Resources