I have a mobile application. I use Angular and Ionic and the app's idea is to have feed with posts. When the user reach 70% ( for example) of the feed, I append new posts to the view. I have 5 posts from the beginning and append 5 posts each time. Even after the first 5 appended posts, the app stucks for half a second. If I am scrolling fast when I reach 70%, the scroll suddenly stops and the app stucks for 0.5 second, then I can scroll again.
This is how I am implementing the functionality:
<div>
<div ng-repeat="post in posts">
<div ng-include src="'js/post/post.html'"></div>
</div>
</div>
<ion-infinite-scroll immediate-check="false" on-infinite="appendPosts()" distance="30%"></ion-infinite-scroll>
Controller
$scope.appendPosts = function() {
$scope.postsFeedPage = $scope.postsFeedPage + 1;
Home.loadPosts($scope.postsFeedPage);
};
$scope.$watch(function(){
return Home.getPosts();
}, function () {
$scope.posts = Home.getPosts();
});
Service
var posts = [];
this.getPosts = function() {
return posts;
};
this.loadPosts = function(page) {
return $http({
url: Server.url + '/api/posts',
method: 'GET',
params: {page: page, token: $rootScope.user.authentication_token }
}).success(function (data) {
posts = posts.concat(JSON.parse(data.posts));
});
};
Any idea what is the problem and how I can fix this issue? If the problem is in Angular's performance, maybe I should use somehow RequireJS to optimize rendering process?
You have a problem of performance and there are some solution you can try :
One-time binding : One time binding increase performanc, but in the case of infinite scroll, i didn't tested if this work/better. Try the following code:
<div ng-repeat="post in ::posts">
<div ng-include src="'js/::post/::post.html'"></div>
</div>
Track by method: Track by methode use a unique identifier and this can increase performance. Try this :
<div ng-repeat="post in posts track by post.id">
<div ng-include src="'js/post/post.html'"></div>
</div>
Collection-repeat: Ionic made a directive which allows an app to show huge lists of items much more performantly than ng-repeat. (Edit: this is the solution for this case).
<div collection-repeat="post in posts">
<div ng-include src="'js/post/post.html'"></div>
</div>
Related
This is my first post/question here on StackOverflow so if you see any improvement I can made - please give me an advice :).
Now let me dive into the issue.
For the sake of simplicity I removed any irrelevant portions of code and presented only necessary files.
//app.modules.js
if (typeof window.app == "undefined") {
window.app = angular.module("AppModule", []);
}
//app.services.js
window.app
.service("settingsOverlaySvc", function($rootScope) {
this.broadcastToggle = function() {
$rootScope.$broadcast("toggle-conf");
};
});
//settings-ctrl.js
window.app.controller("SettingsController", ["$scope", "$window", "$sce", "settingsOverlaySvc",
function($scope, $window, $sce, settingsOverlaySvc) {
$scope.visible = false;
$scope.open = false;
$scope.toggleSettings = function() {
$scope.open = !$scope.open;
};
$scope.broadcastToggle = function() {
settingsOverlaySvc.broadcastToggle();
};
$scope.$on("toggle-conf", function() {
console.log("toggle-conf received");
$scope.visible = !$scope.visible;
});
}
]);
angular.bootstrap($("div[ng-controller='SettingsController']").parent(":not(.ng-scope)"), ["AppModule"]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- Appended by JS - control1.html -->
<div>
<div ng-controller="SettingsController" ng-init="visible=true">
<span class="glyphicon glyphicon-cog" ng-class="{'settings-open': open}" ng-click="broadcastToggle();toggleSettings()">COG</span>
</div>
</div>
<!-- Appended by JS - control2.html-->
<div>
<div ng-controller="SettingsController" ng-cloak>
<div ng-if="visible">
<span class="glyphicon glyphicon-cog" ng-class="{'settings-open': open}" ng-click="toggleSettings()">COG</span>
<div ng-if="open">
<div class="divControl_2">Content_2</div>
</div>
</div>
</div>
</div>
The above snippet works as I expected - the $broadcast is called, all controllers receive the message and reacts. In my application the broadcast is received only by controller sending it. I think the problem is caused by dynamic HTML insertions.
The requirement is to render controls dynamically on page load. I'm using the following approach to generate content.
AddWidgets: function () {
var controlContainer1 = $("<section>", {
class: "sidebar-section",
id: "ctrl1-container"
});
var controlContainer2 = $("<section>", {
class: "sidebar-section",
id: "ctrl2-container"
});
$("aside.sidebar").append(controlContainer1);
$("aside.sidebar").append(controlContainer2);
$("#ctrl1-container").load("..\\assets\\ctrls\\control1.html");
$("#ctrl2-container").load("..\\assets\\ctrls\\control2.html");
}
I'm using the same controller because it shares the same logic for all controls.
I've read a lot materials about $broadcast and $emit functionality, guides on creating controllers, defining modules and services (that one gives me idea about creating service with $rootScope injected).
Now I'm thinking that generating angular content outside angular framework (AddWidgets function) can cause the problem (#stackoverflow/a/15676135/6710729).
What raised my spider sense alarm is when I've checked the JSFiddle example for the similar situation (http://jsfiddle.net/XqDxG/2342/) - no parent-child relation of controllers. When I peek at angulat scope of the controllers I can see that $$nextSibling and $$prevSibling properties are filled. In my case these are nulled [here].
Can you give me some guidelines how can I resolve my issue? I'm fairly new to AngularJS and learning as I'm developing the application.
I'm working on a site, and I started building it before I realized I needed some dynamic framework. After learning about AngularJS, I decided to use it, where I needed (not the whole site).
I have a very long script in JS, and I want to be able to get and set the variables from within AngularJS directives and controllers.
I found this answer, and it was quite good - I was able to get the variable from within the function. But when the variable changed outside the function, AngularJS' variable won't update.
My code looked something like this:
JS:
var app = angular.module('someName', []);
var currentPage = 'Menu';
app.controller('PageController', ['$window','$scope', function($window,$scope){
this.currentPage = $window.currentPage;
this.isPage = function(page){
return (page == this.currentPage);
};
}]);
function button1onClick(){
currentPage = 'Game';
}
HTML:
<div ng-controller="PageController">
<div id="Game" ng-show="page.isPage('Game')">
...
</div>
<div id="Menu" ng-show="page.isPage('Menu')">
...
</div>
</div>
(button1onClick was called when I clicked some button on the page)
The idea is that I have two dives I want to switch between, using a globle variable. 'Menu' page was visible at first but upon clicking I was supposed to see only the 'Game' div.
The variable inside the controller didn't upadte, but was only given the initial value of currentPage.
I decided to use the $window service inside the isPage function, but this didn't work either. Only when I called a function that tested the $window.currentPage variable, the pages switched - like I wanted:
JS:
var app = angular.module('someName', []);
var currentPage = 'Menu';
app.controller('PageController', ['$window','$scope', function($window,$scope){
this.isPage = function(page){
return (page == $window.currentPage);
};
this.button2onClick = function() {
$window.alert($window.currentPage);
}
}]);
function button1onClick(){
currentPage = 'Game';
}
HTML:
<button onclick="button1onClick()">CLICK ME</button> //Button 1
<div ng-controller="PageController">
<button ng-click="page.button2onClick">CLICK ME</button> //Button 2
<div id="Game" ng-show="page.isPage('Game')">
...
</div>
<div id="Menu" ng-show="page.isPage('Menu')">
...
</div>
</div>
So the only way I was able to update the pages is to call a function that tests the variable, thus updating the variable in AngularJS.
Is there a way to access a global variable without needing to test it to update it?
Am I doing something wrong? I don't want to convert my whole site to AngularJS-style, I like the code the way it is. Is AngularJS not the framework for me?
EDIT:
some things to clear out:
I'm new to AngularJS, so if you could explain what your answer does it would be great.
The whole reason why I do this instead of redirecting to another page is not to shut down socket.io 's connection
OP, take a look at UI Router.
var app = angular.module("app", ['ui.router']);
app.config(['$urlRouterProvider', '$stateProvider', function($urlRouterProvider, $stateProvider) {
$urlRouterProvider.otherwise('/main');
$stateProvider.state('main', {
controller: 'MainCtrl',
templateUrl: 'main.html',
url: '/main/'
}).state('game', {
controller: 'GameCtrl',
url: '/game/',
templateUrl: 'game.html'
});
}]);
HTML links:
<a ui-sref="main">Go to Main</a>
<a ui-sref="game">Go to Game</a>
View injection
<div ui-view="">
</div>
You should not use $window as a map object.
You should probably create a PageService:
angular.module('someName')
.factory('Page', [function(){
var currentPage = 'Menu';
return {
getPage: function() {
return currentPage;
},
isPage: function(page) {
return page === currentPage;
},
setPage: function(page) {
currentPage = page;
}
}
}]);
app.controller('PageController', ['Page','$scope', function(Page,$scope){
this.currentPage = Page.getPage();
this.isPage = Page.isPage;
this.button10Click = function(){
Page.setPage('Game');
}
}]);
HTML
<div class="button" ng-click="page.button10Click()">Game</div>
After reading malix's answer and KKKKKKKK's answer, and after researching a bit, I was able to solve my problem, and even write a better looking code.
To switch divs as I wanted in the example, I used ui-router, almost exactly the way KKKKKKKK did. The only difference is that I change state programmaticly - $state.go('menu')
To access global variables in other places in my code, I had to re-structure my whole code to fit AngularJS's structure, and used a Service, similarly to malix's answer:
app.factory('Data', function(){
var Data = {};
//DEFINE DATA
Data.stateChange = function(){};
Data.menuData = {};
Data.randomColors = ['#35cd96', '#6bcbef', '#E8E1DA', '#91ab01'];
/RETURN DATA
return Data;
});
It can be done using $rootScope. Variable initialize once can be accessible in other controllers.
function Ctrl1($scope, $rootScope) {
$rootScope.GlobalJSVariableA= window.GlobalJSVariable; }
Any controller & any view can access it now
function CtrlN($scope, $rootScope) {
$scope.GlobalJSVariableA= $rootScope.GlobalJSVariableA;
}
I work with angularjs and bootstrap (I'm a beginner with angular). I have a lot of list-group in my app and I need to display a badge on each of them if the user hasn't click on the item yet. Once clicked for the first time the badge is removed (and should appear again even if the user refresh the page).
Since it's really recurrent in the app, I don't know of to properly do this...
I don't want to work with tons of boolean or session storage to check if the user has click or not. Do you see a pretty way to do it with angular?
FYI, this is my HTML right now:
<div class="list-group">
<a ng-repeat="item in itemList"
ng-click="select(item)"
ng-class="{active: selected === item}"
class="list-group-item">
{{item.name}} <span class="badge">{{item.badge}}</span>
</a>
</div>
And my controller:
app.controller('myCtrl', ['$scope',
function ($scope) {
$scope.itemList = [{
name: 'Lorem',
badge: 'New'
}, {
name: 'Ipsum',
badge: 'New'
}];
$scope.select = function (item) {
$scope.selected = item;
};
}
]);
The property badge should became empty at the first click and stay empty even if the user leave the app.
Its recommended you do this via some database or some third party sessions provider that complements your web application.
Alternatively, if you are stuck to implementing this on the client browser you can utilize the browser's local storage.
I am only showing here the angular service implemented that stores the stuff to local storage. The rest of the code is in the plunk below:
.service('todosvc', function($window) {
var stg = $window.localStorage.getItem('todos');
if(!stg || (typeof stg === 'string' && stg.length === 0)) {
todos = []
}
else {
todos = angular.fromJson(stg);
}
this.get = function() {
return todos;
};
this.update = function() {
$window.localStorage.setItem('todos', angular.toJson(todos));
};
})
Now if you happen to refresh the page, you will find that the same items remain.
Plunk: http://plnkr.co/edit/nvDmA9ipCNhL2DD14AGz?p=preview
We've decided to use Mean.io in order to get a quick MEAN stack installation, but we're finding some troubles to get things done.
I'm trying to show a picture from my header js controller. But it doesn't show up. In fact, what i see when i open the inspector is just:
<img ng-src>
This is my HTML code, located in header.html:
<div class="page-header" data-ng-controller="HeaderController">
<div class="logo pull-left">
<a class="navbar-brand" ui-sref="home"><img ng-src="{{image.logo}}"/></a>
</div>
</div>
As you may see, i've put "ng-src" and the var taken from the js controller.
This is the HeaderController:
'use strict';
angular.module('mean.system').controller('HeaderController', ['$scope', '$rootScope', 'Global', 'Menus',
function($scope, $rootScope, Global, Menus) {
$scope.global = Global;
$scope.menus = {};
$scope.image = {
logo: 'assets/img/logo.png'
};
// Default hard coded menu items for main menu
var defaultMainMenu = [];
// Query menus added by modules. Only returns menus that user is allowed to see.
function queryMenu(name, defaultMenu) {
Menus.query({
name: name,
defaultMenu: defaultMenu
}, function(menu) {
$scope.menus[name] = menu;
});
}
// Query server for menus and check permissions
queryMenu('main', defaultMainMenu);
$scope.isCollapsed = false;
$rootScope.$on('loggedin', function() {
queryMenu('main', defaultMainMenu);
$scope.global = {
authenticated: !! $rootScope.user,
user: $rootScope.user
};
});
}
]);
The template var is working correctly because if i put {{image.logo}} elsewhere it prints "assets/img/logo.png".
Suggestions? What am i missing?
Thanks in advance!
I had the similar problem, I did the same as you have done and was getting 404 error for the PNG. When reviewed the other requests that are made, I came up with,
you must pass the "package name" in the parameter logo as well.
Just as;
$scope.image = {
logo: 'system/assets/img/logo.png'
};
Than the logo is displayed as desired
I'm implementing cast.js in Meteor and I'm trying to render a larger photo after clicking on a thumbnail using Iron Router, however I'm having trouble rendering the correct path. The thumbnails render just fine, however when the photo is clicked I'm getting an error of No route found for path:.
The template that I'm using to render each thumbnail photo is
var renderTemplate = function(obj){
return "<a href='{{pathFor photo}}'><img class='img-rounded' src='" + obj.picture + "'></a>"
};
Template.userPhotos.rendered = function(){
var el = this.find('#cast');
var mycast = cast(el);
mycast.draw(renderTemplate);
this.handle = Meteor.autorun(function(){
var picture = Photos.find().fetch();
mycast
.data(picture , '_id')
.dynamic(150, 150, 10, 10);
});
}
And is placed within the cast id in this template
<template name="userPhotos">
<div class="container">
<div class="photos">
<div id='cast'></div>
</div>
</div>
</template>
The problem is coming from the href that is rendered. I'm trying to pass the photo _id and render a larger photo in the below template, which is called "source" in mongoDB.
<template name="photo">
<div class="well">
<img class="img-rounded" src="{{source}}">
</div>
</template>
Currently, my router is set up as follows:
this.route('photo', {
path: '/photo/:_id',
waitOn: function() {
return [
Meteor.subscribe('picture', this.params._id),
];
},
data: function() { return Photos.findOne(this.params._id); }
});
Once a photo is clicked, it sends me to this path and throws this error Oh no! No route found for path: "/%7B%7BpathFor". %7B is URL speak for { so it looks like handlebars or Iron Router isn't translating the template when it is passed through as a string.
Any thoughts on how to improve this code?
Thanks in advance!
Your renderTemplate function is just returning a string, not a rendered template. Rewrite the template you were trying to assemble there as a Handlebars template using helpers:
<template name="photoLink">
<a href="{{getPathForPhoto}}">
<img class="img-rounded" src="{{getThumbnailSrc}}">
</a>
</template>
and (you'll have to adapt the below because I don't know what the this variables in your context have as properties; basically, get the path (or the components of the path, like the _id) and the thumbnail path/src into this function via this or via a passed parameter):
Template.photoLink.getPathForPhoto = function() {
return "/photo/" + this._id;
}
Template.photoLink.getThumbnailSrc = function() {
return "/thumbnail/" + this._id;
}
Again you'll need to rework the above functions to get them to work for your app; and you'll need to create a route for the thumbnail paths too.
Is it annoying to create tiny JavaScript functions for every little thing that requires the slightest bit of logic in Handlebars? Yes. But that's just the way it's designed, sorry. You could use other functions to render the template from your assembled string, but this capability is going away in the next version of Meteor.