adding removing an ng-class on a ng-click function - angularjs

I have created a an ng-clickfunction which is doing the basics i need, add an active state class click here. I seem to be have an issue, removing the active class when i want to click out of the active.
<div ng-click="showTooltip(activeTooltip)" ng-class="{ active: activeTooltip == active }">Tooltip!</div>
<script>
var app = angular.module('myApp', []);
app.controller('IndexController', function($scope) {
$scope.activeTooltip = 'Adam';
$scope.active = '';
$scope.showTooltip = function(active) {
$scope.active = active;
};
});
Does anyone know the correct way of doing this?

The way you have implemented it (which doesn't make much sense btw), you need to register a listener for the click event on the window object (and because we are in Angular we should use the $window service).
Every time the window object receives the click event it should reset $scope.active to ''.
Furthermore, when calling $scope.showTooltip(), we need to stop further propagation of the click event, so it doesn't reach the window object if it is captured by the <div> first.
Your controller should be changed like this:
app.controller('IndexController', function($scope, $window) {
$scope.activeTooltip = 'Adam';
$scope.active = '';
$scope.showTooltip = function (active, evt) {
evt.stopPropagation(); // Don't let it reach $window
$scope.active = active;
};
$window.addEventListener('click', function (evt) {
$scope.$apply($scope.showTooltip.bind($scope, '', evt));
});
});
See, also, this short demo.
It turned out I didn't get the actual requiements, which is that the tooltip class should be toggled (added/removed) each time the user clicks on the div. So, there is no need for an event-listener on the window and the code should be modified like this:
app.controller('IndexController', function($scope, $window) {
$scope.activeTooltip = 'Adam';
$scope.active = '';
$scope.showTooltip = function (active) {
$scope.active = ($scope.active === active) ? '' : active;
};
});
<div ng-class="{active:activeTooltip===active}"
ng-click="showTooltip(activeTooltip)">
Tooltip!
</div>
See, also, this other short demo.

Related

Update $scope variable that is used in ng-repeat, from other controller

I have a controller HomeworkPageController where I get all the topics from MongoDB using method getAllMainTopics from TopicService. $scope.topics is then used to show all topics. I have a button that open a modal where a new topic is add in MongoDB. The modal is using another controller AddTopicController. How can I update $scope.topics from HomeworkPageController in AddTopicController ? I want to do this because after I close the modal, the list of all topics should be refreshed, it must contain the topic that has been added. I tried to use HomeworkPageController in AddTopicController and then call the method getAllMainTopics but the $scope.topics from html is not updated. Thanks.
Here is HomeworkPageController:
app.controller('HomeworkPageController', ['$scope','TopicService',
function ($scope, TopicService) {
$scope.topics = [];
$scope.getAllMainTopics = function () {
TopicService.getAllMainTopics('homework')
.success(function(data) {
$scope.topics = data;
}
$scope.addTopic = function () {
ModalService.openModal({
template: "templates/addTopic.html",
controller: 'AddTopicController'
});
}
]);
Here is AddTopicController:
app.controller('AddTopicController', ['$scope','$controller', '$timeout','TopicService', '$modalInstance',
function ($scope, $controller, $timeout,TopicService, $modalInstance) {
var homeworkPageController = $scope.$new();
$controller('HomeworkPageController',{$scope : homeworkPageController });
$scope.save = function() {
TopicService.saveTopic(data)
.success(function(result){
homeworkPageController.getAllMainTopics();
$modalInstance.close();
})
}
}]);
Here is the view where I use $scope.topics:
<div class="homework-content-topic-list" ng-repeat="topic in topics">
<label> {{ topic.subject }} </label>
</div
You should probably keep your list of topics in a service and then inject that service into both controllers. This way you would be able to access and update the topics in both of your controllers. It could look something like
app.controller('HomeworkPageController', ['$scope','TopicService',
function ($scope, TopicService) {
$scope.topics = TopicService.topics;
// Do stuff here
]);
Then you just need to modify your TopicService to have it's methods work on the stored object.
you can solve this by two methods
1)look at the example given in ui-bootstrap's website. They have given an example that will suit your requirement - plunker. There are three items in the modal - item1, item2, item3. If you select one of those items and click 'ok', the selected item is sent to the main controller through "resolve" attribute in the $scope.open function.
2)You can write a custom service that acts as a bridge to the two controllers and you can write getter and setter methods in the service.
angular.module('app').service('popupPageService', function() {
var topics;
var setDetails = function(param) {
topics = param;
};
var getDetails = function() {
return topics;
};
return {
setDetails: setDetails,
getDetails: getDetails,
};
});
call the setDetails function in the AddTopicController and once when you come out of the modal, update your $scope.topics in HomeworkPageController by pushing the new value added (getDetails)

Binding variable in Service with variable in Controller

I try to do some databindings between a controller and a service.
I just want my variable userIsConnected, which is into my controller, has to change as soon as the function getUserIsConnected returns an other value.
Maybe I have to add an attribute userIsConnected into my service?
Here the example :
app.service('UserService', function($http){
var user = null;
this.getUser = function (){
return user;
};
this.setUser = function (user){
user = user;
};
this.getUserIsConnected = function (){
if(user)
return true;
else
return false;
};
});
app.controller("NavBarCtrl", function($scope, $http, $location, $rootScope, UserService) {
$scope.signInIsDisabled = true;
$scope.userIsConnected = UserService.getUserIsConnected();
});
Thanks for your help
The problem with this:
$scope.userIsConnected = UserService.getUserIsConnected();
...is that $scope.userIsConnected becomes whatever getUserIsConnected() outputs in the moment the Controller is initialized. If you want it to continue updating, you should try something like this instead:
$scope.userIsConnected = UserService.getUserIsConnected;
And then in your view, you would use {{userIsConnected()}}.
HOWEVER, from my experience, this sort of watcher is very expensive on rendering. Because when the view is rendered, it will actually run userIsConnected() 10 times before finally giving-up (because in AngularJS, the stack overflow limit is capped at 10).
I used that method in my applications when I first started using AngularJS. But now, I have a directive called "myDynamicMenu", and it detects when a route event has fired using $routeChangeSuccess (ie. when user changes page from login to dashboard, or from dashboard to logout to home).
At this event, my menu directive will check the user authentication state (logged-in or logged-out), and it will then display the proper menu (ie. the user menu [dashboard, my settings, logout], or the guest menu [login, register]). This method is much faster at rendering.
Edit: Example directive:
app.directive('myDynamicMenu', function ($rootScope, UserService) {
return {
restrict: 'E',
templateUrl: 'my-dynamic-menu.html',
link: function (scope) {
$rootScope.$on('$routeChangeSuccess', updateMenuWhenRouteChanges);
////////////////////////////////////////////////////////////
function updateMenuWhenRouteChanges () {
scope.isLoggedIn = UserService.getUserIsConnected() ? true : false;
}
}
};
});
Your template would be something like:
<div id="guest-menu" ng-show="!isLoggedIn">
I am a logged-out guest!
</div>
<div id="member-menu" ng-show="isLoggedIn">
I am a logged-in member!
</div>
And drop the menu into your layout using <my-dynamic-menu></my-dynamic-menu>.

Angularjs - how to watch for changes in DOM and apply a new style to it?

I have a very simple angular app that pushes data in without refreshing the page using setInterval. Now, how can I listen or watch for new data/changes, so that if the new value/data differ from the previous one a new css style will be applied to that particular new value (for example it will change the font color to red).
My code is below:
view:
<h1>{{title}}</h1>
<ul>
<li ng-repeat="friend in friends"><strong>Name: </strong>{{friend.name}} : {{friend.username}}</li>
</ul>
data:
angular
.module ('myApp')
.factory ('Friends', ['$http', function ($http) {
return {
get: function () {
return $http.get ('users.json').then (function (response) {
return response.data;
});
}
};
}]);
Controller:
angular
.module ('myApp')
.controller ('summaryCtrl', ['$scope', 'Friends', function ($scope, Friends) {
$scope.title = "Friends";
$scope.loadData = function () {
Friends.get ().then (function (data) {
$scope.friends = data;
});
};
//initial load
$scope.loadData();
var timer = setInterval(function(){
$scope.loadData();
},5000);
}]);
many thanks
Use $interval instead of setInterval, since it triggers a digest loop it will update your data automatically
angular
.module ('myApp')
.controller ('summaryCtrl', ['$scope', 'Friends', '$interval' function ($scope, Friends, $interval) {
$scope.title = "Friends";
$scope.loadData = function () {
Friends.get ().then (function (data) {
$scope.friends = data;
});
};
//initial load
$scope.loadData();
var timer = $interval(function(){
$scope.loadData();
},5000);
}]);
My recommendation would be to manually compare each friend item and assign a changeFlag whenever the data has changed.
To start, keep a reference to the old data and whenever new data comes in, compare the two, like this:
var oldData = undefined; // Somewhere in initialization.
...
Friends.get().then(function (response) {
var newData = response;
if (oldData && JSON.stringify(oldData) != JSON.stringify(newData))
{
$scope.friends = newData;
$scope.$apply(); // Force the entire page to be redrawn. You can do style bindings to change a style.
}
oldData = response;
}
This will get you half-way to your goal. You will only be refreshing the page whenever something has changed, but there is no indication as to which friend has changed. I imagine this is what you are attempting to accomplish. You want to highlight those friends that have changed.
To do this we could simply create a comparison function that applies a flag to each object that has changed. However, this code assumes that some property on each friend remains fixed. This is normally why an id property is given to each item in a database. I'm going to assume you have an id property for each friend that never changes regardless if their name, age, email, etc. does.
var changeFlagFriendsObjects = function(oldData, newData) {
var idToOldDataMap = {};
oldData.forEach(function (friend) {
idToOldDataMap[friend.id] = friend;
});
newData.forEach(function (friend) {
var oldFriendData = idToOldDataMap[friend.id];
friend.changeFlag = JSON.stringify(oldFriendData) != JSON.stringify(friend);
});
};
// You would call changeFlagFriendsObjects in the other example above. I'm sure this would be easy to figure out how to place.
Regarding binding styles in the HTML to properties, see here.
An example would be like the following:
<!-- Apply the 'highlight' style when changeFlag is true -->
<li ng-repeat="friend in friends" ng-style="highlight={changeFlag: true}"><strong>Name: </strong>{{friend.name}} : {{friend.username}}</li>

Unsure why changes to a factory property are not being pushed to the page

This is a simplistic example of a problem I am having. I am clearly missing something in my understanding of Angular.
A Plunker is here: http://plnkr.co/edit/VLqA22dDTgk5PyPlCOGH?p=preview
And a copy-paste of the pertinent bits below:
<div ng-controller="myController">
<div>message: <label ng-model="message"></label></div>
<div></div><button ng-click="start()">Get Message</button></div>
</div>
var app = angular.module("app", []);
app.service('GetMessage', function() {
var message;
var start = function () {
this.message = 'Hello World';
};
return {
message: this.message,
start: start
}
});
app.controller('myController', function ($scope, GetMessage) {
$scope.message = GetMessage.message;
$scope.start = function () {
GetMessage.start();
console.warn('started..');
};
});
I would expect that the label directive would be 2-way bound to the factory's message property, so that when the start() function is called and the message is updated, that the page would be too.
To update the label in this way, do I need to broadcast an event to $rootScope, listen for it in the controller, and then update the label? It seems a very manual way of doing it.. surely there is a better way.
Thank you.
You are currently using a primitive data type, which means the following line will copy the value into a new variable:
$scope.message = GetMessage.message;
Updating one will not affect the other.
An easy solution is to use an object instead:
var message = { value: '' };
var start = function() {
message.value = 'Hello World';
};
And:
$scope.message = GetMessage.message;
Now the reference to the object would be copied into a new variable instead and both would refer to the same object.
Another issue is that you are using ngModel on a label to display the value, which will not work. ngModel is normally used on input, select and textarea elements.
You can instead use ng-bind:
<label ng-bind="message.value"></label>
Or the less verbose shortcut:
<label>{{message.value}}</label>
Demo: http://plnkr.co/edit/rUgN94DAIOfQGI8tr9kl?p=preview
If you prefer to keep using primtive values you need to handle it another way. For example by using events like you mentioned. Another solution is to register a watcher to watch for changes and update the scope variable:
app.controller('myController', function($scope, GetMessage) {
var watchExpression = function() {
return GetMessage.message;
};
var listener = function(newValue, oldValue) {
if (newValue === oldValue) return;
$scope.message = newValue;
}
$scope.$watch(watchExpression, listener);
$scope.start = function() {
GetMessage.start();
console.warn('started..');
};
});
Demo: http://plnkr.co/edit/klSl1DCUlQI3Z5ih16sq?p=preview

AngularJS access scope from outside js function

I'm trying to see if there's a simple way to access the internal scope of a controller through an external javascript function (completely irrelevant to the target controller)
I've seen on a couple of other questions here that
angular.element("#scope").scope();
would retrieve the scope from a DOM element, but my attempts are currently yielding no proper results.
Here's the jsfiddle: http://jsfiddle.net/sXkjc/5/
I'm currently going through a transition from plain JS to Angular. The main reason I'm trying to achieve this is to keep my original library code intact as much as possible; saving the need for me to add each function to the controller.
Any ideas on how I could go about achieving this? Comments on the above fiddle are also welcome.
You need to use $scope.$apply() if you want to make any changes to a scope value from outside the control of angularjs like a jquery/javascript event handler.
function change() {
alert("a");
var scope = angular.element($("#outer")).scope();
scope.$apply(function(){
scope.msg = 'Superhero';
})
}
Demo: Fiddle
It's been a while since I posted this question, but considering the views this still seems to get, here's another solution I've come upon during these last few months:
$scope.safeApply = function( fn ) {
var phase = this.$root.$$phase;
if(phase == '$apply' || phase == '$digest') {
if(fn) {
fn();
}
} else {
this.$apply(fn);
}
};
The above code basically creates a function called safeApply that calles the $apply function (as stated in Arun's answer) if and only Angular currently isn't going through the $digest stage. On the other hand, if Angular is currently digesting things, it will just execute the function as it is, since that will be enough to signal to Angular to make the changes.
Numerous errors occur when trying to use the $apply function while AngularJs is currently in its $digest stage. The safeApply code above is a safe wrapper to prevent such errors.
(note: I personally like to chuck in safeApply as a function of $rootScope for convenience purposes)
Example:
function change() {
alert("a");
var scope = angular.element($("#outer")).scope();
scope.safeApply(function(){
scope.msg = 'Superhero';
})
}
Demo: http://jsfiddle.net/sXkjc/227/
Another way to do that is:
var extScope;
var app = angular.module('myApp', []);
app.controller('myController',function($scope, $http){
extScope = $scope;
})
//below you do what you want to do with $scope as extScope
extScope.$apply(function(){
extScope.test = 'Hello world';
})
we can call it after loaded
http://jsfiddle.net/gentletech/s3qtv/3/
<div id="wrap" ng-controller="Ctrl">
{{message}}<br>
{{info}}
</div>
<a onClick="hi()">click me </a>
function Ctrl($scope) {
$scope.message = "hi robi";
$scope.updateMessage = function(_s){
$scope.message = _s;
};
}
function hi(){
var scope = angular.element(document.getElementById("wrap")).scope();
scope.$apply(function() {
scope.info = "nami";
scope.updateMessage("i am new fans like nami");
});
}
It's been a long time since I asked this question, but here's an answer that doesn't require jquery:
function change() {
var scope = angular.element(document.querySelector('#outside')).scope();
scope.$apply(function(){
scope.msg = 'Superhero';
})
}
Here's a reusable solution: http://jsfiddle.net/flobar/r28b0gmq/
function accessScope(node, func) {
var scope = angular.element(document.querySelector(node)).scope();
scope.$apply(func);
}
window.onload = function () {
accessScope('#outer', function (scope) {
// change any property inside the scope
scope.name = 'John';
scope.sname = 'Doe';
scope.msg = 'Superhero';
});
};
You can also try:
function change() {
var scope = angular.element( document.getElementById('outer') ).scope();
scope.$apply(function(){
scope.msg = 'Superhero';
})
}
The accepted answer is great. I wanted to look at what happens to the Angular scope in the context of ng-repeat. The thing is, Angular will create a sub-scope for each repeated item. When calling into a method defined on the original $scope, that retains its original value (due to javascript closure). However, the this refers the calling scope/object. This works out well, so long as you're clear on when $scope and this are the same and when they are different. hth
Here is a fiddle that illustrates the difference: https://jsfiddle.net/creitzel/oxsxjcyc/
I'm newbie, so sorry if is a bad practice. Based on the chosen answer, I did this function:
function x_apply(selector, variable, value) {
var scope = angular.element( $(selector) ).scope();
scope.$apply(function(){
scope[variable] = value;
});
}
I'm using it this way:
x_apply('#fileuploader', 'thereisfiles', true);
By the way, sorry for my english
<input type="text" class="form-control timepicker2" ng-model='programRow.StationAuxiliaryTime.ST88' />
accessing scope value
assume that programRow.StationAuxiliaryTime is an array of object
$('.timepicker2').on('click', function ()
{
var currentElement = $(this);
var scopeValues = angular.element(currentElement).scope();
var model = currentElement.attr('ng-model');
var stationNumber = model.split('.')[2];
var val = '';
if (model.indexOf("StationWaterTime") > 0) {
val = scopeValues.programRow.StationWaterTime[stationNumber];
}
else {
val = scopeValues.programRow.StationAuxiliaryTime[stationNumber];
}
currentElement.timepicker('setTime', val);
});
We need to use Angular Js built in function $apply to acsess scope variables or functions outside the controller function.
This can be done in two ways :
|*| Method 1 : Using Id :
<div id="nameNgsDivUid" ng-app="">
<a onclick="actNgsFnc()"> Activate Angular Scope</a><br><br>
{{ nameNgsVar }}
</div>
<script type="text/javascript">
var nameNgsDivVar = document.getElementById('nameNgsDivUid')
function actNgsFnc()
{
var scopeNgsVar = angular.element(nameNgsDivVar).scope();
scopeNgsVar.$apply(function()
{
scopeNgsVar.nameNgsVar = "Tst Txt";
})
}
</script>
|*| Method 2 : Using init of ng-controller :
<div ng-app="nameNgsApp" ng-controller="nameNgsCtl">
<a onclick="actNgsFnc()"> Activate Angular Scope</a><br><br>
{{ nameNgsVar }}
</div>
<script type="text/javascript">
var scopeNgsVar;
var nameNgsAppVar=angular.module("nameNgsApp",[])
nameNgsAppVar.controller("nameNgsCtl",function($scope)
{
scopeNgsVar=$scope;
})
function actNgsFnc()
{
scopeNgsVar.$apply(function()
{
scopeNgsVar.nameNgsVar = "Tst Txt";
})
}
</script>
This is how I did for my CRUDManager class initialized in Angular controller, which later passed over to jQuery button-click event defined outside the controller:
In Angular Controller:
// Note that I can even pass over the $scope to my CRUDManager's constructor.
var crudManager = new CRUDManager($scope, contextData, opMode);
crudManager.initialize()
.then(() => {
crudManager.dataBind();
$scope.crudManager = crudManager;
$scope.$apply();
})
.catch(error => {
alert(error);
});
In jQuery Save button click event outside the controller:
$(document).on("click", "#ElementWithNgControllerDefined #btnSave", function () {
var ngScope = angular.element($("#ElementWithNgControllerDefined")).scope();
var crudManager = ngScope.crudManager;
crudManager.saveData()
.then(finalData => {
alert("Successfully saved!");
})
.catch(error => {
alert("Failed to save.");
});
});
This is particularly important and useful when your jQuery events need to be placed OUTSIDE OF CONTROLLER in order to prevent it from firing twice.

Resources