angularjs binding in scope not in ui - angularjs

i am using jquery fancytree plug in in my angularjs
and i am trying to use the 'activate' event to call a function inside the $scope.
if i breakpoint the code - it looks good , but its not showing in the html.
here is my code:
app.controller('ctrl', ['$scope', 'treeSvc', function ($scope, treeSvc) {
$scope.selectedNode = null;
$scope.SetSelectedNode = function(newNode){
$scope.selectedNode = newNode;
//a break point here shows that $scope.selectedNode is really changing but its not showing in the html. calling this function outside the fancy tree - works.
}
treeSvc.get()
.then(function (response) {
$("#tree").fancytree({
source: response.data,
activate: function(event, data) {
$scope.SetSelectedNode(data.node);
}
});
},
function () {
alert('error getting tree data');
});
}]);
Html
{{selectedNode | json}}
any ideas why?

As your event is fired "outside" of angular, you need to refresh the scope manually :
$("#tree").fancytree({
source: response.data,
activate: function(event, data) {
$scope.SetSelectedNode(data.node);
$scope.$apply();
}
});
See here for more infos : https://docs.angularjs.org/guide/scope

Related

Auto trigger function throuigh service in angularjs

hi all i am using angulrajs passing one value from one controller to another controller using service it's work fine but my need is when service value change in controller 2 i get the service value in one scope when scope value change i need trigger the function it's called refresh function when service value change and that i need to call the refresh function here my fiddle
https://jsfiddle.net/ctawL4t3/10/
You can just $watch your value.storeObject. Though it's not best of the practices, but it suits this kind of feature.
$scope.$watch('value.storedObject', function(newVal) {
if(newVal !== '') {
refresh()
}
})
working fiddle (open console to see refresh function logging)
You can try to use angular default $emit, $broadcast, or try to do 2 simple functions in own service
angular.module('app').factory('StoreService', function() {
var listeners = {};
var emit = function(name, val) {
if(listeners[name]) {
listeners[name](val)
}
}
var on = function(name, callback) {
listeners[name] = callback;
}
return {
emit: emit,
on: on,
storedObject: ''
};
});
JSFiddle example
JSFiddle example $watch
JSFiddle example ng-change is better because, you can use easily debounce
you can use broadcast function for that
Please check this SO link to find the related answer
How to call a function from another controller in angularjs?
app.controller('One', ['$scope', '$rootScope'
function($scope) {
$rootScope.$on("CallParentMethod", function(){
$scope.parentmethod();
});
$scope.parentmethod = function() {
// task
}
}
]);
app.controller('two', ['$scope', '$rootScope'
function($scope) {
$scope.childmethod = function() {
$rootScope.$emit("CallParentMethod", {});
}
}
]);

View is not refreshed after $scope is updated in Firebase promise

I am using Ionic Framework and Firebase is my BaaS.
Controller:
.controller('ProfileCtrl', function($scope, AuthService, DatabaseService) {
console.info('** ProfileCtrl **');
var user = firebase.auth().currentUser;
$scope.public = {};
DatabaseService.getUserPublicInfo(user.uid)
.then(function(infoSnap) {
infoSnap.forEach(function(item) {
$scope.public[item.key] = item.val();
});
});
})
Service:
.service('DatabaseService', function($q) {
this.getUserPublicInfo = function(uid) {
return firebase.database().ref('/users/'+uid+'/public/').once('value');
}
}
In my HTML view I have the following:
<div><h3>{{public.firstname}} {{public.lastname}}</h3></div>
No error and when debugging, $scope.public.firstname as the correct value in it but nothing is displayed.
I have a button in my HTML view ; when I click on it, it changes page but just before page switches, I see the firstname appearing. When I go back to my view, the firstname is well displayed.
I tried to wrap getUserPublicInfo in $scope.$apply() in my controller but I get the "$digest already in progress" error...
Please, help, it's driving me crazy !
Thanks in advance
To resove, "$digest already in progress" error... put $scope .$appy inside timeout service.
DatabaseService.getUserPublicInfo(user.uid)
.then(function(infoSnap) {
infoSnap.forEach(function(item) {
$scope.public[item.key] = item.val();
$timeout(function(){
$scope.$apply()
},1)
});
});
Edit 1: Try this to avoid using $scope.$apply(). I haven't tested it. But it should work.
DatabaseService.getUserPublicInfo(user.uid)
.then(function(infoSnap) {
$scope.updatePublic(infoSnap)
});
});
$scope.updatePublic = function (infoSnap) {
infoSnap.forEach(function(item) {
$scope.public[item.key] = item.val();
})
}
I used $q to create a promise. By doing the initial Firebase promise is resolved within the Angular scope:
this.getUserPublicInfo = function(uid) {
console.info('getUserPublicInfo - get user public information for uid: '+uid);
var deferred = $q.defer();
firebase.database().ref('/users/'+uid+'/public/').once('value')
.then(function(snap) {
deferred.resolve(snap);
})
.catch(function(error) {
deferred.reject(error);
});
return deferred.promise;
}

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>

AngularJs event to call after content is loaded

I have a function which I want to call after page content is loaded. I read about $viewContentLoaded and it doesn't work for me. I am looking for something like
document.addEventListener('DOMContentLoaded', function () {
//Content goes here
}, false);
Above call doesn't work for me in AngularJs controller.
According to documentation of $viewContentLoaded, it supposed to work
Emitted every time the ngView content is reloaded.
$viewContentLoaded event is emitted that means to receive this event you need a parent controller like
<div ng-controller="MainCtrl">
<div ng-view></div>
</div>
From MainCtrl you can listen the event
$scope.$on('$viewContentLoaded', function(){
//Here your view content is fully loaded !!
});
Check the Demo
Angular < 1.6.X
angular.element(document).ready(function () {
console.log('page loading completed');
});
Angular >= 1.6.X
angular.element(function () {
console.log('page loading completed');
});
fixed - 2015.06.09
Use a directive and the angular element ready method like so:
js
.directive( 'elemReady', function( $parse ) {
return {
restrict: 'A',
link: function( $scope, elem, attrs ) {
elem.ready(function(){
$scope.$apply(function(){
var func = $parse(attrs.elemReady);
func($scope);
})
})
}
}
})
html
<div elem-ready="someMethod()"></div>
or for those using controller-as syntax...
<div elem-ready="vm.someMethod()"></div>
The benefit of this is that you can be as broad or granular w/ your UI as you like and you are removing DOM logic from your controllers. I would argue this is the recommended Angular way.
You may need to prioritize this directive in case you have other directives operating on the same node.
You can directly call it by adding {{YourFunction()}} after HTML element.
Here is a Plunker Link.
I had to implement this logic while handling with google charts. what i did was that at the end of my html inside controller definition i added.
<body>
-- some html here --
--and at the end or where ever you want --
<div ng-init="FunCall()"></div>
</body>
and in that function simply call your logic.
$scope.FunCall = function () {
alert("Called");
}
var myM = angular.module('data-module');
myM.directive('myDirect',['$document', function( $document ){
function link( scope , element , attrs ){
element.ready( function(){
} );
scope.$on( '$viewContentLoaded' , function(){
console.log(" ===> Called on View Load ") ;
} );
}
return {
link: link
};
}] );
Above method worked for me
you can call javascript version of onload event in angular js. this ng-load event can be applied to any dom element like div, span, body, iframe, img etc. following is the link to add ng-load in your existing project.
download ng-load for angular js
Following is example for iframe, once it is loaded testCallbackFunction will be called in controller
EXAMPLE
JS
// include the `ngLoad` module
var app = angular.module('myApp', ['ngLoad']);
app.controller('myCtrl', function($scope) {
$scope.testCallbackFunction = function() {
//TODO : Things to do once Element is loaded
};
});
HTML
<div ng-app='myApp' ng-controller='myCtrl'>
<iframe src="test.html" ng-load callback="testCallbackFunction()">
</div>
If you're getting a $digest already in progress error, this might help:
return {
restrict: 'A',
link: function( $scope, elem, attrs ) {
elem.ready(function(){
if(!$scope.$$phase) {
$scope.$apply(function(){
var func = $parse(attrs.elemReady);
func($scope);
})
}
else {
var func = $parse(attrs.elemReady);
func($scope);
}
})
}
}
I was using {{myFunction()}} in the template but then found another way here using $timeout inside the controller. Thought I'd share it, works great for me.
angular.module('myApp').controller('myCtrl', ['$timeout',
function($timeout) {
var self = this;
self.controllerFunction = function () { alert('controller function');}
$timeout(function () {
var vanillaFunction = function () { alert('vanilla function'); }();
self.controllerFunction();
});
}]);
Running after the page load should partially be satisfied by setting an event listener to the window load event
window.addEventListener("load",function()...)
Inside the module.run(function()...) of angular you will have all access to the module structure and dependencies.
You can broadcast and emit events for communications bridges.
For example:
module set onload event and build logic
module broadcast event to controllers when logic required it
controllers will listen and execute their own logic based on module onload processes.
If you want certain element to completely loaded, Use ng-init on that element .
e.g. <div class="modal fade" id="modalFacultyInfo" role="dialog" ng-init="initModalFacultyInfo()"> ..</div>
the initModalFacultyInfo() function should exist in the controller.
I found that if you have nested views - $viewContentLoaded gets triggered for every of the nested views. I've created this workaround to find the final $viewContentLoaded. Seems to work alright for setting $window.prerenderReady as required by Prerender (goes into .run() in the main app.js):
// Trigger $window.prerenderReady once page is stable
// Note that since we have nested views - $viewContentLoaded is fired multiple
// times and we need to go around this problem
var viewContentLoads = 0;
var checkReady = function(previousContentLoads) {
var currentContentLoads = Number(viewContentLoads) + 0; // Create a local copy of the number of loads
if (previousContentLoads === currentContentLoads) { // Check if we are in a steady state
$window.prerenderReady = true; // Raise the flag saying we are ready
} else {
if ($window.prerenderReady || currentContentLoads > 20) return; // Runaway check
$timeout(function() {checkReady(currentContentLoads);}, 100); // Wait 100ms and recheck
}
};
$rootScope.$on('$stateChangeSuccess', function() {
checkReady(-1); // Changed the state - ready to listen for end of render
});
$rootScope.$on('$viewContentLoaded', function() {
viewContentLoads ++;
});
var myTestApp = angular.module("myTestApp", []);
myTestApp.controller("myTestController", function($scope, $window) {
$window.onload = function() {
alert("is called on page load.");
};
});
The solution that work for me is the following
app.directive('onFinishRender', ['$timeout', '$parse', function ($timeout, $parse) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function () {
scope.$emit('ngRepeatFinished');
if (!!attr.onFinishRender) {
$parse(attr.onFinishRender)(scope);
}
});
}
if (!!attr.onStartRender) {
if (scope.$first === true) {
$timeout(function () {
scope.$emit('ngRepeatStarted');
if (!!attr.onStartRender) {
$parse(attr.onStartRender)(scope);
}
});
}
}
}
}
}]);
Controller code is the following
$scope.crearTooltip = function () {
$('[data-toggle="popover"]').popover();
}
Html code is the following
<tr ng-repeat="item in $data" on-finish-render="crearTooltip()">
I use setInterval to wait for the content loaded. I hope this can help you to solve that problem.
var $audio = $('#audio');
var src = $audio.attr('src');
var a;
a = window.setInterval(function(){
src = $audio.attr('src');
if(src != undefined){
window.clearInterval(a);
$('audio').mediaelementplayer({
audioWidth: '100%'
});
}
}, 0);

Angularjs: scope.data is undefined inside directive

First off, i found the api address from this topic:
Laravel 4 and Angular JS and Twitter Bootstrap 3 Pagination
Now i am working about this, my little script is so:
var app = angular.module('kategori', [
'ngResource',
'apiBaseRoute'
]);
app.factory('Data', ['$resource', 'apiBaseRoute', function($resource, config){
return $resource('http://develop.alexei.me/careers/careers.php?callback=JSON_CALLBACK&page=:page', {
page: 1
}, {
'get': {
method: 'JSONP'
}
});
}]);
app.controller('KategoriListCtrl', function($scope, Data){
$scope.init = function() {
Data.get({}, function(response){
$scope.kategoriList = response.careers;
},function(error){
console.log("HATA VAR" + error);
});
};
});
app.directive('paginate', function(){
return{
scope:{ allData: '=paginate2' },
link: function(scope){
console.log(scope);
}
}
});
And this is the html side :
<div class="col-md-6 col-md-offset-3" ng-controller="KategoriListCtrl" ng-init="init()">
{{kategoriList}}
<div paginate paginate2="kategoriList"></div>
</div>
as you see, console.log(scope) inside directive is shows a lot of things in console, especially i see allData there with lots data, but if i change it to
console.log(scope.allData)
it prints undefined..
i don't understand why. how can i solve this? thanks.
By the time JS reaches your console.log the allData property is undefined (since kategoriList is undefined). kategoriList (and thus allData) is created (and populated with lots of data) asynchronously at a later time.
So, why do you see the data when logging the scope object instead ?
At the time the object is logged it has no property allData (and no data).
But by the time you go over to the console and expand the node and look for the allData property, the property has been added and populated by your AJAX call (using $resource).
It is not clear what you want to do with allData.
If you want to use it in e.g. ng-repeat you don't have to worry: You can use it normally (as if it were defined) and Angular will automatically "pick it up" as soon as it arrives and do stuff.
Yet, if you want (for your own mysterious reasons) to get informed when it is ready, your can use $watch:
scope.$watch('allData', function(newValue) {
if (newValue !== undefined) {
console.log(scope.allData);
}
});
See, also, this short demo.

Resources