Infinite scrolling issue - angularjs

I found example of infinite scrolling and tried to perform it the same, but after fixing all mistakes I've found it still doesn't work.
Here's the directive:
module.exports = /*#ngInject*/
function scrollDirective($rootScope) {
return {
link: function (scope, elm, attr) {
var raw = elm[0];
elm.bind('expressly', function() {
if (raw.scrollTop + raw.offsetHeight >= raw.scrollHeight) {
scope.$apply(attr.scrollDirective);
}
});
}
};
};
Then how it goes on in controller (I have JSON-data in promise)
module.exports = /*#ngInject*/
function cardController($scope, dataFactory) {
var promise = dataFactory.get();
promise.then(function(data) {
$scope.data = data;
$scope.items = [];
//$scope.items.push($scope.data[0]);
var counter = 0;
$scope.loadMore = function() {
for (var i = counter; i < counter+9; i++) {
$scope.items.push($scope.data[i]);
}
counter += 9;
};
$scope.loadMore();
}, function(reason) {
console.log('Failed: ' + reason);
});
};
And finally html:
<div class="mdl-grid" scroll-directive="loadMore()">
<div class="mdl-cell mdl-cell--4-col mdl-cell--4-col-phone" ng-repeat="item in items">
<md-card>
<img ng-src="{{ item.url }}" class="md-card-image">
<md-card-content>
<md-icon ><i class="material-icons md-36 md-album">photo_album</i></md-icon>
<h3 class="md-title">{{ item.title }}</h3>
</md-card-content>
</md-card>
</div>
</div>

Actually it turned up pretty simple. In directive instead of elm I used $document and pass parameter i to the function. After exploring the guts of i, I found all parameters I needed through i.target.activeElement. Here is the solution:
function scrollDirective($rootScope,$document) {
return {
link: function (scope, elm, attr) {
var raw = elm[0];
$document.bind('scroll', function (i) {
if (i.target.activeElement.scrollTop+i.target.activeElement.offsetHeight +5>= i.target.activeElement.scrollHeight){
scope.$apply(attr.scrollDirective);
}
});
}
};
};

Related

AngularJs app not show uib tooltip

I am trying to implement in angular js that when in li ellipsis come display tooltip for that my code is below
Dependency Injection:
angular.module('spt', ['ui.router', 'ngStorage', 'ngAnimate', 'ui.bootstrap', 'ui.slimscroll', 'angular-google-analytics', 'jmdobry.angular-cache',
'stpa.morris', 'angularReverseGeocode', 'chart.js', 'ui.calendar', 'ui.date',
'me-lazyload', 'angularUtils.directives.dirPagination', 'angular-loading-bar', 'base64',
'nemLogging', 'ui-leaflet', 'angular-google-adsense', 'dropstore-ng', 'ngVideo', 'angular-google-adsense', 'cgBusy', 'duScroll', 'angularGrid', 'infinite-scroll'
]);
In HTML:
<p class="contactEmail">
<ul style="max-width: 200px;">
<li uib-tooltip="{{email}}" tooltip-enable="flag" show-tooltip-on-text-overflow="flag" style="overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;">
<i class="ion-email"></i>
Email : {{email}}
</li>
</ul>
</p>
JS:
angular.module('spt').directive('showTooltipOnTextOverflow',
function ($timeout) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var el = element[0];
scope.$watch(function(){
return el.scrollWidth;
}, function() {
var el = element[0];
if (el.offsetWidth < el.scrollWidth) {
//console.log('ellipsis is active for element', element);
attrs.tooltipEnable = "true";
} else {
//console.log('ellipsis is NOT active for element', element);
}
});
}
};
});
Controller:
function ContactController(
$scope,
$rootScope,
$modal,
$log,
$sce,
ContactService,
Utility,
SettingsService,
Session,
APPLICATION,
RESPONSE,
CONSTANTS) {
$log.debug('in ContactController');
//function Declaration
$scope.closeModelInstance = closeModelInstance;
$scope.showDropdown = showDropdown;
$scope.selectItem = selectItem;
$scope.showBlock = false;
$scope.showBlockMessage = false;
$scope.blockMessage = '';
$scope.syncContacts = syncContacts;
$scope.flag = true;
$scope.email = "sdajkdsjsadklsdajkasldjsdakljsadklsdadsa#adsjsdsadkjadsjk.it";
if (Utility.redirectToDashboard(Session.getValue(APPLICATION.currentDeviceId)) === true) {
return true;
}
$rootScope.isChildSelected1 = Session.getValue('isChild');
var params = {};
params.id = Session.getValue(APPLICATION.currentDeviceId);
$log.debug('contacts');
$scope.items = {};
$scope.blockType = {
message: false,
Contacts: false
};
You can't use a static attribute to do that!
Use a variable from your scope e.g.
$scope.myVar = false;
HTML
<p class="contactEmail">
<ul>
<li uib-tooltip="{{item.email}}"
tooltip-enable="myVar"
show-tooltip-on-text-overflow="myVar">
<i class="ion-email"></i> Email : {{item.email}}
</li>
</ul>
</p>
JS
angular.module('spt').directive('showTooltipOnTextOverflow', function () {
return {
restrict: 'A',
scope: {
showTooltipOnTextOverflow: "="
},
link: function(scope, element, attrs) {
var el = element[0];
scope.$watch(function(){
return el.scrollWidth;
}, function() {
var el = element[0];
if (el.offsetWidth < el.scrollWidth) {
scope.showTooltipOnTextOverflow = true;
}
else {
scope.showTooltipOnTextOverflow = false;
}
});
}
};
});
Working fiddle ==> http://plnkr.co/edit/EGHPncgOVvubU9iBlJdx?p=preview

AngularJS :wait in 'link' of directive is not working

In the below source, the watch method in the link part of a custom directive is not working. I use 'link' within the directive because I have to update the DOM structure.
How can I get the watch in the link{} of the directive working EACH time the button is pushed?
EDIT: I found the wrong code. See below 'ERROR' and 'CORRECT' code.
The HTML above this script is (click on a button to increment a variable):
<div ng-controller="AppController as vmx">
<button ng-click="vmx.incrementFoo()">Increment Foo</button>:
{{ vmx.fooCount }}.
<div foo-count-updated></div>
</div>
Angular code:
angular.module( "myapp", [])
.controller( "AppController", myAppController)
.directive('showAlsoInCustomDirective', showAlsoInCustomDirective);
// *** CONTROLLER
function myAppController( $scope ) {
var vm = this;
vm.fooCount = 0;
vm.copiedFooCount = 0;
// ERROR code:
// **vm.getFooCount** = function() {
// return vm.fooCount;
// }
// CORRECT code:
getFooCount = function() {
return vm.fooCount;
}
vm.getFooCount = getFooCount;
vm.incrementFoo = incrementFoo;
function incrementFoo() {
++vm.fooCount;
}
}
// *** DIRECTIVE
.directive('fooCountUpdated', fooCountUpdater);
function fooCountUpdater() {
var indirectivecounter = 0;
getFooCountInDirective = function() {
return getFooCount();
}
var watcherFn = function (watchScope) {
return getFooCountInDirective();
}
return {
link: function (scope, element, attrs) {
scope.$watch(watcherFn, function (newValue, oldValue) {
element.html( "Got the change: " + newValue);
})
}};
}
The complete source is put in this file:
https://plnkr.co/edit/J6nfLQ3dmLW0gDNXV0J5?p=preview
As indicated above, the solution was simple. It is also in the plunker file.
HTML:
<div ng-controller="AppController as vmx">
<button ng-click="vmx.incrementFoo()">Increment Foo</button>:
{{ vmx.fooCount }}.
<div foo-count-updated></div>
</div>
Angular code:
angular.module( "myapp", [])
.controller( "AppController", function( $scope ) {
var vm = this;
vm.fooCount = 0;
getFooCount = function() {
return vm.fooCount;
}
vm.getFooCount = getFooCount;
vm.incrementFoo = incrementFoo;
function incrementFoo() {
++vm.fooCount;
}
})
.directive('fooCountUpdated', fooCountUpdater);
function fooCountUpdater() {
var indirectivecounter = 0;
getFooCountInDirective = function() {
return getFooCount();
}
var watcherFn = function (watchScope) {
return getFooCountInDirective();
}
return {
link: function (scope, element, attrs) {
scope.$watch(watcherFn, function (newValue, oldValue) {
element.html( "Got the change: " + newValue);
})
}};
}

AngularJS - ng-if runs digest loop

I am facing problem with infinite loop on loading the view. The data is loaded from an API call using ngResource in the controller. The view seems to be reloaded multiple times before rendering the view correctly. I use ng directives in the template calling scope methods and this seems to get into loop causing the view to be re-rendered.
Here is my Controller
.controller('IndexCtrl', ['$scope', '$stateParams', 'ProfileInfo',
function($scope, $stateParams, ProfileInfo) {
$scope.navTitle = 'Profile Information';
$scope.data = {};
ProfileInfo.query({
id: $stateParams.id
}).$promise.then(function(Profile) {
if (Profile.status == 200) {
$scope.data.Profile = Profile.data[0];
}else{
console.log(Profile.status);
}
}, function(error) {
console.log(error);
});
$scope.showImageBlock = function(object, image) {
if (object.hasOwnProperty('type') && object.type == 'image') {
imageReference = object.value;
var imageUrl;
angular.forEach(image, function(value, key) {
if (value.id == imageReference) {
$scope.data.imageUrl = value.graphic.url;
return;
}
});
}
return object.hasOwnProperty('type') && object.type == 'image';
};
$scope.showText = function(object) {
console.log('text');
return object.hasOwnProperty('type') && object.type == 'text';
};
}
])
And Here is my template
<ion-view cache-view="false">
<ion-nav-title>
{{navTitle}}
</ion-nav-title>
<div class="bar bar-subheader bar-light">
<h2 class="title">{{navSubTitle}}</h2>
</div>
<ion-content has-header="true" padding="true" has-tabs="true" class="has-subheader">
<div ng-repeat="profileInfo in data.Profile">
<div class="list">
<img ng-if="showImageBlock(profileInfo,data.Profile.images)" ng-src="{{ data.imageUrl }}" class="image-list-thumb" />
<div ng-if="showText(profileInfo)">
<a class="item">
{{profileInfo.name}}
<span ng-if="profileInfo.description.length != 0"class="item-note">
{{profileInfo.description}}
</span>
</a>
</div>
</div>
</div>
</ion-content>
Here is the output of console window when tried log the number of times showText function is called.
The actual result from ngResource call has only 9 items in array but it loops more than 9 times and also multiple loops. This happens for a while and stops. Could anyone please point me in the right direction in fixing it.
Thank you
Finally I ended up creating a custom directive which does the function of ng-if without the watchers which triggers the digest loop. It's not a pretty solution but it seems to do the job as expected. I copied the code of ng-if and removed the $scope watcher. Here is the custom directive.
angular.module('custom.directives', [])
.directive('customIf', ['$animate',function($animate) {
return {
multiElement: true,
transclude: 'element',
priority: 600,
terminal: true,
restrict: 'A',
$$tlb: true,
link: function($scope, $element, $attr, ctrl, $transclude) {
var block, childScope, previousElements;
value = $scope.$eval($attr.customIf);
if (value) {
if (!childScope) {
$transclude(function(clone, newScope) {
childScope = newScope;
clone[clone.length++] = document.createComment(' end customIf: ' + $attr.customIf + ' ');
block = {
clone: clone
};
$animate.enter(clone, $element.parent(), $element);
});
}
}
else {
if (previousElements) {
previousElements.remove();
previousElements = null;
}
if (childScope) {
childScope.$destroy();
childScope = null;
}
if (block) {
previousElements = getBlockNodes(block.clone);
$animate.leave(previousElements).then(function() {
previousElements = null;
});
block = null;
}
}
}
};
}]);
This allows us to use the customIf as follows
<div custom-if="showText(profileInfo)">

Capturing AngularJS Carousel Slide Change Event [duplicate]

I'm using Angular-UI's carousel and I need to tell my google charts to redraw after they have slid into view. In spite of what I've read, I can't seem to hook into the event.
See my attempt:
http://plnkr.co/edit/Dt0wdzeimBcDlOONRiJJ?p=preview
HTML:
<carousel id="myC" interval="myInterval">
<slide ng-repeat="slide in slides" active="slide.active">
<img ng-src="{{slide.image}}" style="margin:auto;">
<div class="carousel-caption">
<h4>Slide {{$index}}</h4>
<p>{{slide.text}}</p>
</div>
</slide>
</carousel>
On document load:
$('#myC').live('slid.bs.carousel', function (event) { console.log("slid"); } );
It should work something like this:
http://jsfiddle.net/9fwuq/ - non-angular-ui carousel
Perhaps there is a more Angular way to hook into the fact that my chart has slid into view?
There are 3 ways I can think of and that depends of your requirement.
Please see http://plnkr.co/edit/FnI8ZX4UQYS9mDUlrf6o?p=preview for examples.
use $scope.$watch for an individual slide to check if it is become active.
$scope.$watch('slides[0].active', function (active) {
if (active) {
console.log('slide 0 is active');
}
});
use $scope.$watch with custom function to find an active slide.
$scope.$watch(function () {
for (var i = 0; i < slides.length; i++) {
if (slides[i].active) {
return slides[i];
}
}
}, function (currentSlide, previousSlide) {
if (currentSlide !== previousSlide) {
console.log('currentSlide:', currentSlide);
}
});
use a custom directive to intercept select() function of the carousel directive.
.directive('onCarouselChange', function ($parse) {
return {
require: 'carousel',
link: function (scope, element, attrs, carouselCtrl) {
var fn = $parse(attrs.onCarouselChange);
var origSelect = carouselCtrl.select;
carouselCtrl.select = function (nextSlide, direction) {
if (nextSlide !== this.currentSlide) {
fn(scope, {
nextSlide: nextSlide,
direction: direction,
});
}
return origSelect.apply(this, arguments);
};
}
};
});
and use it like this:
$scope.onSlideChanged = function (nextSlide, direction) {
console.log('onSlideChanged:', direction, nextSlide);
};
and in html template:
<carousel interval="myInterval" on-carousel-change="onSlideChanged(nextSlide, direction)">
...
Hope this help : )
AngularUI Bootstrap has changed naming conventions for controllers as thery have prefixed all of their controllers with prefix uib, so below is the updated solution of the original solution provided by runTarm:
Angular:
.directive('onCarouselChange', function($parse) {
return {
require: '^uibCarousel',
link: function(scope, element, attrs, carouselCtrl) {
var fn = $parse(attrs.onCarouselChange);
var origSelect = carouselCtrl.select;
carouselCtrl.select = function(nextSlide, direction, nextIndex) {
if (nextSlide !== this.currentSlide) {
fn(scope, {
nextSlide: nextSlide,
direction: direction,
nextIndex: this.indexOfSlide(nextSlide)
});
}
return origSelect.apply(this, arguments);
};
}
};
});
Angular with TypeScript:
module App.Directive {
export class CarouselChange implements ng.IDirective {
public require: string = '^uibCarousel';
constructor(private $parse: ng.IParseService) { }
public link: ng.IDirectiveLinkFn = (scope: ng.IScope, element: ng.IAugmentedJQuery, attributes: any, carouselCtrl: any) => {
var fn = this.$parse(attributes.carouselChange);
var origSelect = carouselCtrl.select;
carouselCtrl.select = function(nextSlide, direction) {
if (nextSlide !== this.currentSlide) {
fn(scope, {
nextSlide: nextSlide,
direction: direction
});
}
return origSelect.apply(this, arguments);
};
}
static Factory(): ng.IDirectiveFactory {
var directive: ng.IDirectiveFactory = ($parse: ng.IParseService) => new CarouselChange($parse);
directive['$inject'] = ["$parse"];
return directive;
}
}
}
Thanks,
Following the answer given by runTarm If you want to know the index of the next slide, you should add something like this:
.directive('onCarouselChange', function ($parse) {
return {
require: 'carousel',
link: function (scope, element, attrs, carouselCtrl) {
var fn = $parse(attrs.onCarouselChange);
var origSelect = carouselCtrl.select;
carouselCtrl.select = function (nextSlide, direction,nextIndex) {
if (nextSlide !== this.currentSlide) {
fn(scope, {
nextSlide: nextSlide,
direction: direction,
nextIndex:this.indexOfSlide(nextSlide)
});
}
return origSelect.apply(this, arguments);
};
}
};
})
Then, in the controller you just need to do this to catch the new index:
$scope.onSlideChanged = function (nextSlide, direction, nextIndex) {
console.log(nextIndex);
}
I managed to modify runTarm's answer so that it calls the callback once the slide has finished sliding into view (i.e. the sliding animation has finished). Here's my code:
.directive('onCarouselChange', function ($animate, $parse) {
return {
require: 'carousel',
link: function (scope, element, attrs, carouselCtrl) {
var fn = $parse(attrs.onCarouselChange);
var origSelect = carouselCtrl.select;
carouselCtrl.select = function (nextSlide, direction) {
if (nextSlide !== this.currentSlide) {
$animate.on('addClass', nextSlide.$element, function (elem, phase) {
if (phase === 'close') {
fn(scope, {
nextSlide: nextSlide,
direction: direction,
});
$animate.off('addClass', elem);
}
});
}
return origSelect.apply(this, arguments);
};
}
};
});
The secret lies in using $animate's event handler to call our function once the animation is finished.
here's an alternate method that uses controllers, somewhere between runTarm's #2 and #3.
original HTML + a new div:
<carousel id="myC" interval="myInterval">
<slide ng-repeat="slide in slides" active="slide.active">
<div ng-controller="SlideController"> <!-- NEW DIV -->
<img ng-src="{{slide.image}}" style="margin:auto;">
<div class="carousel-caption">
<h4>Slide {{$index}}</h4>
<p>{{slide.text}}</p>
</div>
</div>
</slide>
</carousel>
the custom controller:
.controller('SlideController',
function($log, $scope) {
var slide = $scope.slide;
$scope.$watch('slide.active', function(newValue) {
if (newValue) {
$log.info("ACTIVE", slide.id);
}
});
});
And if you just want to start playing a video when the slide comes into view, and pause when it leaves:
JS
{# Uses angular v1.3.20 & angular-ui-bootstrap v0.13.4 Carousel #}
{% addtoblock "js" %}<script type="text/javascript">
angular.module('videoplay', []).directive('videoAutoCtrl', function() {
return {
require: '^carousel',
link: function(scope, element, attrs) {
var video = element[0];
function setstate(visible) {
if(visible) {
video.play();
} else {
video.pause();
}
}
// Because $watch calls $parse on the 1st arg, the property doesn't need to exist on first load
scope.$parent.$watch('active', setstate);
}
};
});
</script>{% endaddtoblock %}
{% addtoblock "ng-requires" %}videoplay{% endaddtoblock %}
NOTE: Has additional bits for Django
HTML:
<carousel interval="15000">
<slide>
<video class="img-responsive-upscale" video-auto-ctrl loop preload="metadata">
<source src=
...

Accessing a service or controller in my link function - Angular.js

I have a directive, but I am having a problem access the controller and my service that is injected into it. Here is my directive:
angular.module('clinicalApp').directive('chatContainer', ['encounterService', function(encounterService) {
return {
scope: {
encounter: '=',
count: '='
},
templateUrl: 'views/chat.container.html',
controller: 'EncounterCtrl',
link: function(scope, elem, attrs, controller) {
scope.addMessage = function(message) {
//RIGHT HERE
scope.resetChat();
};
scope.resetChat = function() {
scope.chatText = '';
scope.updateCount(scope.chatText);
};
}
};
}]);
You can see that I am attaching a couple of functions to my scope inside the link function. Inside those methods, like addMessage, I don't have access to my controller or the service that is injected into the directive. How do I acceess the controller or service?
UPDATE
Here is the service:
angular.module('clinicalApp').factory('encounterService', function ($resource, $rootScope) {
var EncounterService = $resource('http://localhost:port/v2/encounters/:encounterId', {encounterId:'#id', port: ':8280'}, {
search: {
method: 'GET'
}
});
var newEncounters = [];
var filterTerms = {};
EncounterService.pushNewEncounter = function(encounter) {
newEncounters.push(encounter);
$rootScope.$broadcast('newEncountersUpdated');
};
EncounterService.getNewEncounters = function() {
return newEncounters;
}
EncounterService.clearNewEncounters = function() {
newEncounters = [];
}
EncounterService.setFilterTerms = function(filterTermsObj) {
filterTerms = filterTermsObj;
$rootScope.$broadcast('filterTermsUpdated');
EncounterService.getFilterTerms(); //filter terms coming in here, must redo the search with them
}
EncounterService.getFilterTerms = function() {
return filterTerms;
}
return EncounterService;
});
and the chat.container.html
<div class="span4 chat-container">
<h5 class="chat-header">
<span class="patient-name-container">{{encounter.patient.firstName }} {{encounter.patient.lastName}}</span>
</h5>
<div class="chat-body">
<div class="message-post-container">
<form accept-charset="UTF-8" action="#" method="POST">
<div class="text-area-container">
<textarea id="chatBox" ng-model="chatText" ng-keyup="updateCount(chatText)" class="chat-box" rows="2"></textarea>
</div>
<div class="counter-container pull-right">
<span class="muted" id="counter">{{count}}</span>
</div>
<div class="button-container btn-group btn-group-chat">
<input id="comment" class="btn btn-primary btn-small btn-comment disabled" value="Comment" ng-click="addMessage(chatText)"/>
</div>
</form>
<div messages-container messages="encounter.comments">
</div>
</div>
</div>
</div>
Here is Demo Plunker I played with.
I removed scope{....} from directive and added 2 values in controller and directive to see how they change regards to action.
JS
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
// listen on any change of chatText in directive
$scope.$watch(function () {return $scope.chatText;},
function (newValue, oldValue) {
if (newValue == oldValue) {return;}
$scope.chatTextFromController = newValue;
}, true);
});
app.directive('chatContainer', ['encounterService', function(encounterService) {
return {
templateUrl: 'chat.container.html',
link: function(scope, elem, attrs) {
scope.countStart = scope.count;
scope.updateCount = function(chatText) {
alert('updateCount');
scope.count = scope.countStart - chatText.length;
};
scope.addMessage = function(message) {
alert('addMessage');
encounterService.sayhello(message);
scope.resetChat();
};
scope.resetChat = function() {
alert('resetChat');
scope.chatText = 'someone reset me';
scope.name = "Hello " + scope.name;
scope.updateCount(scope.chatText);
};
}
};
}]);
app.service('encounterService', function() {
var EncounterService = {};
EncounterService.sayhello = function(message) {
alert("from Service " + message);
};
return EncounterService;
});
HTML
<body ng-controller="MainCtrl">
<div chat-container></div>
<pre>chatText from directive: {{chatText|json}}</pre>
<pre>chatText from controller: {{chatTextFromController|json}}</pre>
<pre>name: {{name|json}}</pre>
</body>

Resources