I'm creating my first Angular app and ran into a couple things that I just can't figure out. Whenever I include this:
<button ng-hide="results.length === projects.length" ng-click="limit = limit +3; gotoBottom()">Show More</button>
Inside of my template the app refuses to load but if I paste it anywhere outside of the template it works fine. I'd like to keep the button inside the template if at all possible so what on earth am I doing wrong?
Also, I'd like that button to also scroll to the #footer div and the ng-click doesn't seem to run this bit code:
$scope.gotoBottom = function() {
$location.hash('footer');
$anchorScroll();
};
I've created a Plunker of my code that can be found here:
https://plnkr.co/edit/MP4Pp4WLcn5EFb3pTEXx
By "template" if you are talking about projects template. Here is what you need to do.
Explanation:
The projects template need to have only one root element, so I added a div to wrap your project listing and show more button.
<div>
<div class="cards" ng-init="limit = 3">
<div class="card" ng-repeat="project in projects | limitTo: limit as results">
<div class="card-image">
<img src="{{project.img}}" alt="{{project.name}}" />
</div>
<div class="card-copy">
<h2>{{project.name}}</h2>
<p>{{project.desc}}</p>
<p><i class="fa fa-location-arrow"></i></p>
</div>
</div>
</div>
<button ng-hide="results.length === projects.length" ng-click="limit = limit +3; gotoBottom()">Show More</button>
<div id="footer" name="footer"></div>
</div>
For auto scroll: inject $timeout service
Explanation:
You did not had any div named footer so I added one just below the show more button and added a 100ms timeout, so that after your 3 projects load, it will scroll to the footer div. $timeout is very necessary because need to first render your projects and then scroll.
$scope.gotoBottom = function() {
$timeout(function() {
$location.hash('footer');
$anchorScroll();
}, 100);
};
Working Plunker: https://plnkr.co/edit/U3DDH57nh0Mqlpp2Txi4?p=preview
Hope this helps!
change the below code in projects.js
angular.module('portfolioApp')
.directive('projects', function() {
return {
templateUrl: 'projects.html',
controller: 'mainCtrl',
replace: true // remove directive tags
};
});
to
replace: false
it should do the trick. Plunker Link here
Related
I'm new in Angularjs and I have an app, with some "projects", which have a local menu displayed on some pages. Index.html contains the main navbar with footer :
<body ng-app="ysi-app" ng-controller="MainController">
<div class="page-content">
<div class="row">
<div class="col-md-2">
<div class="sidebar content-box" style="display: block;">
<ul class="nav">
<!-- Main menu -->
<li ng-if="isAuthenticated()">{{name}}</li>
<li class="current">Dashboard</li>
<li>Projects</li>
</ul>
</div>
<div ng-if="isAuthenticated() && displayProjectMenu == true" ng-include="'views/localMenu.html'" ng-controller="LocalMenuController">
</div>
</div>
<div ng-view></div>
</div>
So I have a nested controller LocalMenuController for the local menu and a main controller. The project controller sets the datas :
angular.module('ProjectCtrl',[]).controller('ProjectController',function($scope,$location, ProjectService,$route, AuthenticationService, $rootScope){
$scope.setProjectDatas = function(projectName, projectId){
ProjectService.setName(projectName);
$rootScope.projectId = projectId;
};});
I set the id of one project to the $rootScope for testing (I have a Service which will do that better) and get it in the LocalMenuController :
angular.module('LocalMenuCtrl',[]).controller('LocalMenuController', function($scope, ProjectService, $rootScope) {
$scope.projectId = '';
$scope.projectId = $rootScope.projectId;
});
I display projects in a table and when I clicked on one of it, the function setProjectDatas(name,id) is called. The problem is when I clicked on one project, the id of the project is correct and set but when I go previous and clicked on another project, the id is the old id of the project previously clicked. The datas are not updating. I googled my problem but found nothing on it.
I think the LocalMenuController is called only one time but not after.
What am I doing wrong ?
Thank you
UPDATE
I've created a Directive which displays the template but it's still not updating the partial view localMenu.
LocalMenu Directive :
angular.module('LocalMenuCtrl',[]).controller('LocalMenuController', function($scope, ProjectService, $rootScope) {
console.log('-> LocalMenu controller');
})
.directive('localMenu', function($rootScope){
return {
templateUrl: '/YSI-Dev/public/views/partials/localMenu.html',
link: function(scope){
scope.projectId = $rootScope.projectId;
}
};
});
A part of index.html
<div ng-if="isAuthenticated() && displayProjectMenu == true" ng-controller="LocalMenuController">
<div local-menu></div>
</div>
Partial view localMenu :
<div class="sidebar content-box" style="display: block;">
<ul class="nav">
<li><i class="glyphicon glyphicon-list-alt"></i> Backlog</li>
<li><i class="glyphicon glyphicon-user"></i> My team </li>
</ul>
</div>
I'm trying to get the projectId from the $rootScope and inject it in the <a href="#/project/{{projectId}}" but I have some troubles. What's the way to do that ?
First of all, try using directives instead of ng-controller. You can encapsulate your code and template into a unit. You can also try creating a component. Pass some data to the directive/component and Angular will take care of updating the template and running whatever needs to run within the directive/component. (Given that you used two-way data-bindings)
From the code above, I cannot see what would trigger LocalMenuController to run again.
I'm having trouble figuring out how to target elements in our single-page application.
I'm testing mostly authenticated pages, so the data is being mocked for protractor using: ngMockE2E and $httpBackend
Let's say there are 4 pages/views: Home | About | Settings | Sign out
The Settings page structure is roughly:
<div ng-view class="ng-scope">
<div class="row ng-scope" ng-controller="SettingsCtrl as ctrl">
<h1>Settings Main Page</h1>
<div ng-if="show.more">
More Info
[...]
</div>
</div>
</div>
If you click the more info link, the view will change to that page where the only major difference is in the ng-controller.
<div ng-view class="ng-scope">
<div class="row ng-scope" ng-controller="SettingsCtrl">
<h2>Settings More Info Page</h2>
[...]
</div>
</div>
In protractor I cannot figure out how to access the elements within the more info page - but I can locate the elements on the main settings page just fine.
For example:
var main_title = element(by.tagName('h1')).isPresent();
expect(main_title).toBe(true); // Will return true
var moreInfo_title = element(by.tagName('h2')).isPresent();
expect(moreInfo_title).toBe(true); // Will return false
Why can't I access elements within a view?
You might need to wait for the presence of the h2 element:
// click "More info"
var EC = protractor.ExpectedConditions;
var moreInfo_title = element(by.tagName('h2'));
browser.wait(EC.presenceOf(moreInfo_title), 5000);
expect(moreInfo_title.isPresent()).toBe(true);
You should try clicking the link then check to see if the more info title exists.
var main_title = element(by.tagName('h1')).isPresent();
expect(main_title).toBe(true);
var link = element(by.linkText('More Info'));
link.click().then(function() {
var moreInfo_title = element(by.tagName('h2')).isPresent();
expect(moreInfo_title).toBe(true);
});
I use the structure provided by the yeoman with angular-generator.
The ng-click does not work in my directive, of a slider show, when I put the html directly in main.html (It only works when I put in the directive an templateurl, linked to the main.html , but this causes delay to load).
Html, that is inserted directly into main.html
<div images="images" class="slider" id="mauseOnOut">
<div class="slide" ng-repeat="image in images" ng-show="image.visible">
<a ng-href="{{image.url}}"><img ng-src="{{image.src}}" width="444" height="250"/>
<p class="texto">{{image.texto}}</p>
</a>
</div>
<ul class="minimagem" ng-show="images.length">
<li ng-repeat="image in images"><a ng-click="returner($index)"><img ng-src="{{image.src}}" width="70" height="56"/></a></li>
</ul>
<div class="arrows">
<img src="http://s5.postimg.org/qkfwdwi7n/right_arrow.png"/>
</div>
</div>
Main part of the directive (in jsFiddle have it complete)
myApp.directive('images', function ($timeout) {
return {
restrict: 'AE',
scope:{
images: '='
},
link: function (scope) {
scope.currentIndex=0;
scope.returner = function(index){
scope.currentIndex = index;
};
scope.next=function(){
scope.currentIndex<scope.images.length-1?scope.currentIndex++:scope.currentIndex=0;
};
scope.prev=function(){
scope.currentIndex>0?scope.currentIndex--:scope.currentIndex=scope.images.length-1;
};
scope.$watch('currentIndex',function(){
scope.images. forEach(function(image){
image.visible=false;
});
scope.images[scope.currentIndex].visible=true;
});
},
};
});
Put an example in jsFiddle ; when use angular 1.1, on jsFiddle, operate normally, with 1.2 or higher does not work. In my application I use the angular 1.3.10 .
How could make it work in my application? It could be to ' compile ' or in some other way , the important thing is the click staying active in the image thumbnails and arrows .
Edited: I came back with the best known directive , best to understand.
I am trying to set up custom themeing on my app, so what I am doing is letting the user choose certain themes and it will change the apps theme holistically. I have a service which sends a piece of json and listens for it changing inside the controller of each view. Now this works fine within the view itself - for reference here's some snippets of the working code.
my factory controlling the theme -
angular.module('demoApp')
.factory('templatingFactory', function () {
var meaningOfLife =
{
'h1': '#ea6060',
'bg': '#ffffff'
};
return {
setTheme: function(theme) {
meaningOfLife = theme;
},
getTheme: function() {
return meaningOfLife;
}
};
});
One of my example controllers showing and changing the theme (and listening for changes)
$scope.themeStore = templatingFactory.getTheme();
console.log($scope.themeStore);
//send new themes
$scope.themeOne = function () {
var newT1 = { 'h1': '#8A6516',
'bg': '#000000'};
templatingFactory.setTheme(newT1);
};
$scope.themeTwo = function () {
var newT2 = { 'h1': '#ffffff',
'bg': '#ea6060'};
templatingFactory.setTheme(newT2);
};
$scope.themeThree = function () {
var newT3 = { 'h1': '#ea6060',
'bg': '#ffffff'};
templatingFactory.setTheme(newT3);
};
//listen for new themes
$scope.watchThemes = templatingFactory.getTheme();
$scope.$watch(templatingFactory.getTheme, function (newTheme) {
$scope.themeStore = newTheme;
});
and then on the template/view itself i do something like this -
<h3 ng-style="{ 'color' : themeStore.h1 }">Title</h3>
So my issue is that this works fine inside the view. However the ng-view tag is inside the body and outside of it are the body containers, as well as the header and footer menus that I would like to be able to hook onto with this theme object. So my quesiton is, is there any way to use that scope outside of the ng-view? I don't think it's possible but I'm not sure how else I could access and put a ng-style on the header footer and body to change some css on it with this method I am using.
So for a simple reference it looks like this -
<body ng-app="myApp">
<div class="container">
<div class="header" ng-style="{ 'background-color' : themeStore.bg }">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<i class="fa fa-bars"></i>
</button>
<div class="headerLogo"></div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
</ul>
</div>
</div>
<div ng-view class="velocity-opposites-transition-slideUpIn" data-velocity-opts="{ duration: 500 }" ng-style="{ 'background-color' : themeStore.bg }"> </div>
<div class="footer">
<p></p>
</div>
</div>
</body>
So as you can see - I'm trying to hook onto the header to change the background color, which does not work like this. What I noticed though, is if I put it on the ng-view div itself, it works alright.
I would much appreciate any input as I've been stuck on this for a while. Thank you for taking the time to read!
The DOM elements outside of your ng-view must have controllers of their own, with templatingFactory injected as a dependency.
First I would modify the html like so:
<div class="header" ng-controller="headerController" ng-style="{ 'background-color' : themeStore.bg }">
Then add headerController to your module:
angular.module('demoApp').controller('headerController', function($scope, templatingFactory){
$scope.themeStore = templatingFactory.getTheme();
$scope.$watch(templatingFactory.getTheme, function (newTheme) {
$scope.themeStore = newTheme;
});
});
A more reusable solution would be to create a directive that adds this controller functionality to whatever DOM element it is applied to, but the above is a little more straight forward.
I think the best way to have angular functions and variables outside ui-view or ng-view is to use a global service. in this case you should do your theming logic inside 'templatingFactory'. Then inject this service not in your controllers, but in your module.
angular.module('demoApp').run(['$rootScope', 'templatingFactory', function($rootScope, templatingFactory){
$rootScope.templatingService = templatingFactory;
}]);
So your service will be avaible in the $rootScope. now you can use it this way.
<body ng-app="myApp">
<div class="container">
<div class="header" ng-style="{ 'background-color' : templatingService.getTheme().bg }"> </div>
</div>
</div>
ps: I'm relative new in angular too, so I don't know nothing about good/wrong practices!
For the directive approach, a simple example might look something like this:
demoApp.directive('themeHeader', function (templatingFactory) {
return {
restrict: 'A',
link : function (scope, element, attrs) {
scope.$watch(templatingFactory.getTheme, function () {
element.css('background-color', newTheme.bg);
});
}
}
});
and the html would look like this:
<div theme-header>
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"><i class="fa fa-bars"></i></button>
<div class="headerLogo"></div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right"></ul>
</div>
I am making a directory app that comprises roughly 200 list items (employees). The app worked as intended using ng-repeat, however, it was sluggish to load. I switched to Collection-Repeat to take advantage of the speed boost but I am getting bizarre behaviors that I can't figure out.
The list items are rendering correctly, alphabetically with the category titles added successfully. The problem is, each list item has a ng-click attribute that opens an $ionicModal. The modal for each item opens, but the loaded data is incorrect.
When the modal opens, it starts at the bottom of the page - I can see the contents for half a second before it animates to the middle of the screen. To start, the loaded data is correct. As it animates, it switches to another employees data. I can't seem to figure out why. I'm new to angular/ionic so any pointers would be great. Thanks!
EDIT - Out of curiousity, I added a second ng-controller="ModalCtrl" ng-click="openModal();" to each element as a button. Clicking on the element does the usual - opens the modal with the wrong employee. Clicking on the newly created button however creates TWO modals (stacked on eachother) BOTH with the correct employee. Removing either instance to the ng-controller or ng-click puts me back at square one with only one modal of incorrect data. Why is this? Why does adding a second ng-click correct the problem (despite having two modals)?
EDIT - Here is a link to a codepen sample (dumbed down, but proves my issue: http://codepen.io/anon/pen/zijFv?editors=101
My HTML looks like this:
<div class="list">
<a class="item my-item"
collection-repeat="row in contacts"
collection-item-height="getItemHeight(row)"
collection-item-width="'100%'"
ng-class="{'item-divider': row.isLetter}">
<!-- ADDED BUTTON SEE EDIT COMMENT ABOVE -->
<button ng-if="!row.isLetter" ng-controller="ModalCtrl" ng-click="openModal();">Click</button>
<img ng-controller="ModalCtrl" ng-click="modal.show()" ng-if="!row.isLetter" ng-src="data:image/jpeg;base64,{{row.image}}">
<h2>{{row.title || (row.firstname+' '+row.lastname)}}</h2>
<p ng-if="!row.isLetter"><em>{{row.jobtitle}}</em></p>
</a>
</div>
My Modal HTML is this:
<header class="bar bar-header bar-lsi">
<h1 class="title">Contact Information</h1>
<div class="button button-clear" ng-click="closeModal()">
<span class="icon ion-close"></span>
</div>
</header>
<ion-content has-header="true" style="margin-top: 0px !important;">
<div class="list card" style="border-radius: 0px !important;">
<div class="item item-avatar item-text-wrap">
<img ng-src="data:image/jpeg;base64,{{row.image}}">
<h2>{{row.firstname}} {{row.lastname}}</h2>
<p>{{row.jobtitle}}</p>
</div>
<a href="tel:{{row.phone}}" class="item item-icon-left">
<i class="icon ion-iphone"></i>
{{row.phone}}
</a>
<a href="mailto:{{row.email}}" class="item item-icon-left">
<i class="icon ion-email"></i>
{{row.email}}
</a>
</div>
</ion-content>
And then I have my basic controller:
.controller('ModalCtrl', function($scope, $ionicModal) {
$ionicModal.fromTemplateUrl('my-modal.html', {
scope: $scope,
animation: 'slide-in-up'
}).then(function(modal) {
$scope.modal = modal;
});
$scope.openModal = function() {
$scope.modal.show();
};
$scope.closeModal = function() {
$scope.modal.hide();
};
$scope.$on('$destroy', function() {
$scope.modal.remove();
});
})
I think the problem is that you aren't passing to the modal template any value. It's getting residual values. I see too that you are using too much ng-controller and ng-click in items list and what is inside it. I mean, if you use ng-click for A.item, you don't need to use ng-click for the image inside it.
Let's see some code:
<a class="item my-item"
collection-repeat="row in contacts"
collection-item-height="getItemHeight(row)"
collection-item-width="'100%'"
ng-class="{'item-divider': row.isLetter}"
ng-controller="ModalCtrl" ng-click="openModal(row);">
<img ng-if="!row.isLetter" ng-src="http://placehold.it/65x65">
<h2>{{row.title || (row.firstname+' '+row.lastname)}}</h2>
<p ng-if="!row.isLetter"><em>{{row.jobtitle}}</em></p>
</a>
As you can see, I've removed all ng-click and ng-controller inside A tag, and I've left only what is attributes of A tag. You can notice too that I pass the object row to the openmModal() function.
In controller, I've made next changes:
$scope.openModal = function(item) {
$scope.modal.row = item;
$scope.modal.show();
};
And in the modal template I've used modal.row as variable with the data from the item list touched. So in template I use it like this:
<div class="item item-avatar item-text-wrap">
<img ng-src="http://placehold.it/65x65">
<h2>{{modal.row.firstname}} {{modal.row.lastname}}</h2>
<p>{{modal.row.jobtitle}}</p>
</div>
<a href="tel:{{modal.row.phone}}" class="item item-icon-left">
<i class="icon ion-iphone"></i>
{{modal.row.phone}}
</a>
<a href="mailto:{{modal.row.email}}" class="item item-icon-left">
<i class="icon ion-email"></i>
{{modal.row.email}}
</a>
I've test it in your codepen and it works. Try it and tell me if it works for you.