Showing and hiding header nav in Mean.js app - angularjs

I am building a blog with the Yeoman Mean.js generator. So far I am really enjoying working with Mean.js, however I am relatively new to Angular development, so some things just don't click for me yet.
I would like to hide the header on all pages of my app, and only show it if I am logged in. I'll be pulling signup out of the header, and logging in from a single location to manage my blog.
I tried using ng-show="topbarActive" on the <header> element present in the /app/views/layout.server.view.html:
<header ng-show="topbarActive" data-ng-include="'/modules/core/views/header.client.view.html'" class="navbar navbar-fixed-top navbar-default"></header>
I then tried explicitly setting this variable to false in /public/modules/core/controllers/home.client.controller.js:
$scope.topbarActive = false;
I set this value to true in /public/modules/users/controllers/authentication.client.controller.js, in the hopes that I could manually ping http://localhost:3000/#!/signup and see my header bar.
After this set up I do not see the header anywhere. Seeing as how I am new to 'The Angular Way', what steps am I missing in order to achieve the behavior I am looking for. Am I confused in how Mean.js builds it's dependencies?

One of the key concepts in AngularJS is scoping. In this particular case your controller HomeController is assigned to the first div under the header. That makes HomeController scope unavailable to it's parent (header) which actually doesn't have any controller at all.
To make your configuration work, please add a new controller, for example:
/* /public/modules/core/controllers/body.client.controller.js */
'use strict';
angular.module('core').controller('BodyController', ['$scope', 'Authentication', 'Menus',
function($scope, Authentication, Menus) {
$scope.topbarActive = true;
}]);
And then add this controller to the body in layout.server.view.html:
<body ng-cloak ng-controller="BodyController">
Alternatively you can create a controller for your tag only, depends on where you're going from here.

Related

Preserve traditional anchor behavior with ng-include

I am not building a single-page application, but rather a "traditional" site that uses AngularJS in places. I've hit the following problem (using 1.3.0-beta.6):
Standard, working anchor links:
Link text
... [page content]
<a id="foo"></a>
<h1>Headline</h1>
[more content]
That works fine. Now I introduce a template partial somewhere:
<script type="text/ng-template" id="test-include.html">
<p>This text is in a separate partial and inlcuded via ng-include.</p>
</script>
which is invoked via:
<div ng-include="'test-include.html'"></div>
The partial is included properly, but the anchor link no longer works. Clicking on "Link text" now changes the displayed URL to /#/foo rather than /#foo and the page position does not change.
My understanding is that using ng-include implicitly tells Angular that I want to use the routes system and overrides the browser's native anchor link behavior. I've seen recommendations to work around this by changing my html anchor links to #/#foo, but I can't do that for other reasons.
I don't intend to use the routes system - I just want to use ng-include without it messing with browser behavior. Is this possible?
The reason is that angular overrides the behavior of standard HTML tags which include <a> also. I'm not sure when this change happened because angular v1.0.1 works fine with this.
You should replace the href attribute with ngClick as:
<a ng-click="scroll()">Link text</a>
And in a controller so:
function MyCtrl($scope, $location, $anchorScroll) {
$scope.scroll = function() {
$location.hash('foo');
$anchorScroll();
};
};
Demo: http://jsfiddle.net/HB7LU/3261/show/
Or simply use double hash as:
<a href='##foo'>Link text</a>
Demo: http://jsfiddle.net/HB7LU/3262/show/
Update: I did not know that you want no modification in HREF. But you can still achieve the desired result by overriding the existing a directive as:
myApp.directive('a', function() {
return {
restrict: 'E',
link: function(scope, element) {
element.attr('href', '#' + element.attr('href'));
}
};
});
Demo: http://jsfiddle.net/HB7LU/3263/
My understanding is that using ng-include implicitly tells Angular
that I want to use the routes system and overrides the browser's
native anchor link behavior. I've seen recommendations to work around
this by changing my html anchor links to #/#foo, but I can't do that
for other reasons.
Routing system is defined in a separate module ngRoute, so if you did not injected it on your own - and I am pretty sure you did not - it is not accessible at all.
The issue is somehow different here.
ng-include depends on: $http, $templateCache, $anchorScroll, $animate, $sce. So make use of ng-include initiate all these services.
The most natural candidate to investigate would be $anchorScroll. The code of $anchorScroll does not seem to do any harm, but the service depends on $window, $location, $rootScope. The line 616 of $location says:
baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
So basically the base href is set to '', if it was no set before.
Now look HERE - from BalusC answer :
As to using named anchors like , with the tag
you're basically declaring all relative links relative to it,
including named anchors. None of the relative links are relative to
the current request URI anymore (as would happen without the
tag).
How to mitigate the issue?
I do not have much time today, so cannot test it myself, but what I would try to check as the first option is to hook up to '$locationChangeStart' event and if the new url is of #xxxxxx type just prevent the default behaviour and scroll with $anchorScroll native methods instead.
Update
I think this code should do the work:
$scope.$on("$locationChangeStart", function (event, next, current) {
var el, elId;
if (next.indexOf("#")>-1) {
elId = next.split("#")[1];
el = document.getElementById(elId);
if(el){
// el.scrollIntoView(); do not think we need it
window.location.hash = "#" + elId;
event.preventDefault();
}
}
});
This is the best solution, and works in recent versions of Angular:
Turn off URL manipulation in AngularJS
A lot late to the party but I found that adding a simple target="_self" fixes it.
Link
Rather than applying the angular application to the entire page, you can isolate the application to just the places you want to perform an ng-include. This will allow links outside the scope of the application to retain their normal functionality, while allowing links within the application to be handled as desired.
See this plunkr:
http://plnkr.co/edit/hOB7ixRM39YZEhaz0tfr?p=preview
The plunkr shows a link outside the app that functions as normal, and a link within the app that is handled using an overriding a directive to restore normal functionality. HTML5 mode is enabled to retain 'standard' URLs (rather than 'hashbang' [without the bang!] URLs).
You could equally run the whole of the page within the app, but I thought it would be worth demonstrating how to isolate angular to certain parts of the page in any case.

Partial page updates with AngularJS

I am building an AngularJS application that has a sidebar and a main content area. Both are populated by separate invocations of the $http service to retrieve some JSON data. The stuff in the sidebar is basically a table of contents and the intent is that clicking on one of the sidebar items will cause the main area to be updated.
My initial stab at this involved putting the sidebar and main area into one partial and then associating it with a controller that does both retrievals. The application has a route that associates the controller to a URL, and the links in the sidebar access these URL with the appropriate parameter that will cause the desired content to appear in the main area. All this works, but it does cause the whole page to refresh. The "partial" really is a "total".
What I'd like to do is somehow cause a click on the sidebar links to trigger a refresh of the main content area only. One thought is to somehow split it into two controller/partial pairs and figure out a way to cause sidebar clicks to request an update. I'm not sure about the mechanics of doing this, though. Another approach is to keep the stuff in one controller and use some kind of shared state that would trigger this update. I attempted to do this by setting an ng-click directive on the links, but this did not update a scope variable, as I had hoped.
Is there a recommended way of structuring an application to achieve this kind of AJAX-driven partial page update? It seems like a fairly common case, but I haven't mastered enough of AngularJS to get a solution.
This is what I'm doing:
I have 2 services, 1 for the sidemenu and the other for the main content. They are both injected into the controller.
To handle cross service calls I use $broadcast to send events.
Works really well and is very clean code.
additional info on using services based on comment
For the sidemenu i have a shared menu service, that way all controllers can use the same menu.
The maincontent service doesnt have to be shared, but i use them because services don't loose their data when the controller goes out of scope. If I didn't the controller would have to use some other mechanism to repopulate its data. For me a service handles it without having to code anything
To show the different views i use the following main layout html
<div >
<!-- left menu -->
<div id="left" ng-include src="menu.view" ></div>
<!-- main content -->
<div id="main" ng-include src="maincontent.view"></div>
</div>
the controller
function myControllerCtrl($scope, $rootScope, menuService, maincontentService) {
$scope.menu = menuService;
$scope.maincontent = mainContentService
}
the menu service
app.factory('menuService', ['$rootScope', function ($rootScope) {
var service = {
view: 'leftmenuview.html',
MenuItemClicked: function (data) {
$rootScope.$broadcast('menuitemclicked', data);
}
};
return service;
}]);
the main content service
app.factory('maincontentService', ['$rootScope', function ($rootScope) {
var service = {
view: 'maincontentview.html',
MenuItemClicked: function(data){
//handle updating of model based on data here
}
};
$rootScope.$on('menuitemclicked', function (event, data) { service.MenuItemClicked(data) });
return service;
}]);

Basic behaviour and structure of an angularjs app

I am trying to introduce myself into angularjs.
Although i have worked through the tutorial and watched the basic building videos, i am still struggling with the behaviour and architecture of a more-or-less large scale application.
My application has a menubar that includes an add-button. If the user clicks the button, i want a dialog to pop-up. That dialog is not part of the menu:
<!-- The menu -->
<header class="mod modHeader" ng-controller="HeaderCtrl">
<div class="modHeader__addProject" ng-click="openAddDialog()">
<i class="icon-plus icon-2x"></i>
</div>
</header>
<!-- the dialog -->
<div class="modNewProject" ng-show="properties.AddDialogVisibility" ng-controller="HeaderCtrl">
<!-- content stripped out -->
</div>
My intention was to create a properties object inside the scope of my HeaderCtrl controller, then change a boolean value on click of the said button.
// HeaderCtrl
function HeaderCtrl($scope){
$scope.properties = {
"AddDialogVisibility": false
};
$scope.openAddDialog = function () {
$scope.properties.AddDialogVisibility = true;
};
}
Now, there are multiple issues and questions:
I have to apply HeaderCtrl to my dialog in order to get access to the properties object. This is nasty to me, HeaderCtrl should control only my header module, shouldn't it?
The dialog won't show up on click. I found out that this is because the property gets checked only once, on page load, and that i would have to use a function. What is a proper way to achieve my goal?
Conclusion:
I would say that i can summarize my question to:
I use a Controller for each section of my page. How do they communicate?
In the sample code you provided, two HeaderCtrls will be created. Each use of ng-controller will create a controller.
To share data in Angular, use a service. Inject that service where needed – into controllers, directives, etc.
When designing an Angular app, try to think in terms of models (which are often contained in services, and those services then expose model APIs to the rest of the application) and views. A controller is just the glue that allows us to project the relevant parts of our models into a view.
Dialogs are a special/unique case, since they don't take up a specific place in the rest of the application. Listen to a few minutes of Misko regarding this subject.
I would also recommend looking at how the Angular-UI team implemented dialogs: http://angular-ui.github.io/bootstrap/#dialog
angular structure
app->
assets->
css -> all css file
js -> all js file
partials-> all html files
vender -> third party file (like angular.js , jquery.js)
router.js
services.js
filter.js
directives.js
controllers.js
index.html

How to run two separate Angular js apps in the same page

New to Angular. I feel like I'm missing something obvious: Shouldn't I easily be able to run to separate AngularJs apps (modules) in the same html page? Something like this:
<section ng-app="HelloWorldApp" ng-controller="HelloWorldController">
Hello {{name}}!
</section>
<br />
<section ng-app="MyNameIsApp" ng-controller="MyNameIsController">
My Name is {{FirstName}} {{LastName}}!
</section>
Javascript:
var HelloWorldApp = angular.module('HelloWorldApp', []);
HelloWorldApp.controller('HelloWorldController', function($scope) {
$scope.name = 'World';
});
var MyNameIsApp = angular.module('MyNameIsApp', []);
MyNameIsApp.controller('MyNameIsController', function($scope) {
$scope.FirstName = 'John';
$scope.LastName = 'Smith';
});
This only runs the first module, while the second doesn't appear to do anything. I want to do this so that I can build reusable, encapsulated directives for multiple pages that don't have to name their modules the same thing.
Live Example: http://plnkr.co/edit/cE6i3ouKz8SeQeA5h3VJ
We ended up building small hierarchy of modules, however my original question can done, with just a bit of work (see below).
It is possible, but it requires a little bit coding by hand. You need to bootstrap the angular apps on your own. Don't worry, it is not that complicated
Do not add ng-app attributes in your HTML
Make sure you can fetch the DOM elements holding the app
When DOM is loaded you need to start the apps on your own: angular.bootstrap( domElement, ['AppName']);
Fork of you plunker which works: http://plnkr.co/edit/c5zWOMoah2jHYhR5Cktp
According to the Angular docs for ngApp:
Use this directive to auto-bootstrap an application. Only one
directive can be used per HTML document. The directive designates the
root of the application and is typically placed at the root of the
page.
Seems it's by design.
You can specify any nested apps in the module def of the main one.
angular.module("myapp", ['statusapp', 'tickerapp']).controller(....
and in a separate file, you have the other apps defined. We're using a template engine which hides some of this, but you'll end up with HTML that contains nested ng-apps and javascript for each one that defines the module/controller. The code above is the trick to getting more than one bootstrapped.

angularjs controller executes twice

A similar question was asked here but it did not help me.
I am learning angularjs and I noticed the controller is executed twice.
I have a very simple fiddle example that shows the behavior here
I built the example as I was learning about services and at first I thought it was the injecting of the services into the controller but I commented all the code related to the services and still the controller is executed twice.
My example works but I am afraid I am doing something wrong.
<div ng-app="MyApp">
<div ng-controller="MyCtrl">
{{data1}}
</div>
</div>
var app = angular.module('MyApp', [])
app.service('Service1', function(){
return {
ajxResponse1: 'dataFromService1'
};
});
function MyCtrl($scope, Service1){
alert('Entering MyCtrl');
$scope.data1 = Service1.ajxResponse1;
alert('Exiting MyCtrl');
}
One possible source is this: if you are using Routing and specify the controller in routes - you must not specify it in template that the route uses. We had that problem when this was overlooked.
Your controller was running twice in the fiddle because angular is referenced twice (once via the Frameworks & Extensions drop down and another as an External Resource).
See this updated fiddle where I removed the External Resource and the alerts only show up once.
The code remains unchanged:
function MyCtrl($scope, Service1, Service2, Service3){
alert('Entering MyCtrl');
$scope.data1 = Service1.ajxResponse1;
$scope.data2 = Service2.ajxResponse2;
$scope.data3 = Service3.ajxResponse3;
alert('Exiting MyCtrl');
}
I had a similar problem and it was due to slashes in my routing.
I had something like /post{slug:[a-z0-9-]*/} for my route and when visiting the page at domain.com/post it would redirect to the version with a slash on the end.
Took me ages to work it out!
Edit:
Actually, just took a more detailed look at your code and noticed there is no routing in there, so this is probably not the cause in your case.
Might be useful for people like me who were looking for a different solution though.
For all the people using rails and angularjs:
The rails framework that maps URLS to views and loads them clashes with the angularjs $route even when you have a single-view application.
To prevent the double-loading of your controller:
go to application.js and remove "//= require turbolinks
You're welcome.

Resources