I have been trying to implement the header / footer in an Angular JS App. I was thinking of adding these as ng-include in the main index.html. However this would have worked if the header and footer are static pages. My case is slightly different... In Login page no header / footer is shown. Other pages depending on whether you are logged in or not, you have to show "Welcome user [ logout] " or "Welcome guest [ login ]".
I save the login information and isLoggedIn in a service - then use HeaderCtrl controller to copy that to $scope. The biggest problem seems to be that the whole ng-include is not refreshed on a logoff. Hence divs with ng-show hide directives will not hide/show on change. Somebody suggested using ng-switch - it also behaves the same way.
If I move the header code inside individual views then everything is fine.
A similar question is here: Refresh header page in angularjs
AngularJS - Handle repeated fragments like Header and Footer
I have asked this question earlier but the answers did not help me when I moved the logged variable from the rootScope. The problem is my HeaderCtrl is only executing on a full page refresh. Not executing on Angular page navigation etc.
Here is the code:
index.html:
<body ng-app="greenhornApp">
<div id="navbar" ng-controller="HeaderCtrl"><div ng-include src="'views/partials/header.html'"></div>
</div>
<div class="title-spacer visible-desktop"></div>
<!-- Add your site or application content here -->
<div class="wrapper" ng-view></div>
</body>
header.js:
angular.module('greenhornApp')
.controller('HeaderCtrl', ['$scope', 'LoginService', function ($scope, loginService) {
$scope.isLoggedIn = loginService.isLoggedIn();
$scope.session = loginService.getSession();
}]);
header.html
<div>
<div ng-show="isLoggedIn">
<div class="navbar navbar-inverse navbar-fixed-top">
More stuff here....
</div>
</div>
</div>
I am using ng-route and angular 1.2.0-rc3. I put a breakpoint in HeaderCtrl... it gets executed only when I do a full page refresh. What am I doing wrong here?
If your loginService.isLoggedIn() is bound to a primitive value instead of an object, your HeaderCtrl needs to use $watch to monitor the variable and get the new value as it changes.
For it to work without a $watch you need to make sure an object reference is passed from your loginService to your HeaderCtrl.
I made two quick Plunkers that hopefully replicates your situation well enough to give some guidance:
With primitive and $watch: http://plnkr.co/edit/LvtVEE?p=preview
With object reference and no $watch: http://plnkr.co/edit/2u2ZDE?p=preview
EDIT: My original reasoning was wrong. Check tasseKATT's answer for that.
Here's is another way of communicating between controllers using angularJS broacasting mechanism
Here's a jsfiddle just to show the basic idea.
var module = angular.module("demo", []);
function Header ($scope){
$scope.userIsLoggedIn = true
$scope.$on('DO_LOGOUT',function(event){
alert('Header is updating log-status')
$scope.userIsLoggedIn=false
})
}
function Dynamic($scope){
$scope.logout =function(){
$scope.$emit('LOGOUT_REQUESTED')
}
}
function Root($scope) {
$scope.$on('LOGOUT_REQUESTED',function(event){
alert('Rootscope got logout event')
$scope.$broadcast('DO_LOGOUT')
})
}
Related
I have searched through a lot of questions regarding $route.reload() and I think I still have a fundamental misunderstanding of how it and Angular are actually working. I really want to understand what is actually going on (not just a quick fix) so I apologize if this question gets rather verbose.
I am trying to create an app that will allow the user to input information into a form and then use ng-view and a controller LivePreviewController to display that form data in a "live preview" of the form which has some unique styling and other information associated with the form that the user does not input themselves.
This is very simple to do if the "live preview" is occurring on the same page as the user is inputting information using something like this
<input type="text" ng-model="form_name" />
<div ng-bind="form_name"></div>
What I would like to do is something like this.
<!--Location is: localhost:8888/form_page.html -->
<input type="text" ng-model="form_name" />
<!-- ng view is displaying the partial view: styled_form.html -->
<div ng-view></div>
Then I have another page styled_form.html which is the partial view that gets displayed by the ng-view div.
<!-- partial view page is styled_form.html -->
<div ng-bind="form_name"></div>
Both of these pages are controlled by the same controller LivePreviewController.
There are a lot of things I imagine that I am probably doing incorrectly. First thing could be the routing. I had some issues with the routing as app is only used on one page of a multi-page website my routes are setup as follows.
var previewApp = angular.module('previewApp', ['ngRoute']);
previewApp.config(['$routeProvider', function($routeProvider){
$routeProvider
.otherwise({
redirectTo:'/styled_view.html',
controller: 'LivePreviewController'
});
}]);
//Controller Code
previewApp.controller('LivePreviewController', [$scope, $route, function($scope, $route){
$scope.form_name = 'temporary name';
}]);
All of this works as expected when the form_page.html is originally loaded, but when the user changes the name in the text input the ng-view displaying my styled_form.html page is not changed. This also makes sense because the styled_form.html needs to be reloaded with the new information.
Here is where the noob starts. My first inclination was to create a refresh button and add a function in my LivePreviewController to refresh the page using $route.reload()
previewApp.controller('LivePreviewController', [$scope, $route, function($scope, $route){
$scope.form_name = 'temporary name';
$scope.refreshPage = function() {
$route.reload();
console.log($scope.form_name) //to ensure scope is updating
};
}]);
And then updated my html to include the button as follows
<!--Location is: localhost:8888/form_page.html -->
<input type="text" ng-model="form_name" />
<button ng-click="refreshPage()"> Update the Page </button>
<!-- ng view is displaying the partial view: styled_form.html -->
<div ng-view></div>
The function is working just fine when the button is clicked and the updated scope variable is displaying in the console, but my live preview is not updating. After looking through the Angular documentation I have a hunch as to what is happening. It states...
Causes $route service to reload the current route even if $location hasn't changed.
As a result of that, ngView creates new scope and re-instantiates the controller.
So if I understand the docs correctly, which I obviously don't, what is actually happening on $route.reload() is that when styled_form.html is getting reloaded the updated $scope.form_name is getting wiped out and the original $scope value is getting shown on the page. So...
Question 1: am I understanding this correctly i.e. is $route.reload() wiping out my $scope and reloading the old $scope variable, or is my $route.reload() just not reloading the page at all?
Now if I am interpreting my issue correctly my guess (seen suggested on here in other topics) is that the solution would be to create some type of service to store my data and then call that service inside of the controller. This workflow would look like
User input --> Data stored in some Service(factory) --> Data Passed to $scope --> >$scope value displayed in partial view (styled_form.html)
But I am lost as to how to dynamically store the user input inside of a service. Ideally this would all occur in realtime (as with the simple first example) as I would like to avoid having a refresh button. Could this be accomplished by creating a function in my controller that updates the values inside my service every few seconds? I need someone with better ideas than myself to point me in the right direction.
Question 2: Is this the correct workflow assuming Q1 is correct and if so what is the correct way to set up my service to handle the user input and pass it to another html page dynamically?
All help much appreciated
Having a service to store data and same controller to serve both view is fine.
Data saving could be done each time, when controller variables are changing.
When new controller is initialized, it my read the date from the service. A separate controller instance will be created for each view.
You may also check this blog to see, how data should be defined within a service, so that changing such data will auto-trigger controller to update view:
http://viralpatel.net/blogs/angularjs-service-factory-tutorial/
Notice: that service returns a reference to the array and controller saves this reference, when it is created.
Ok so I realized my problem was due to the fact that I was misunderstanding how $scope works in Angular not how $route.reload() was working. This topic
$scope variable not changing in controller when changed using directive
and this link in that question
https://github.com/angular/angular.js/wiki/Understanding-Scopes
helped me understand what I was doing wrong. $route.reload() and $templateCache.remove() were working correctly. The problem was that my $scope variable wasn't updating properly because each view was creating a different scope. Paraphrasing one of the links
The $scope is not the model.
The function of the controller is to write from the model to the scope
The function of the view is to display the $scope and write to the model if needed
I solved the problem using the controllerAS syntax discussed in the answers like this,
.controller('LivePreviewController',['$scope', function($scope) {
var vm = this;
vm.form_name = 'temporary name';
}]);
<!-- form_page.html -->
<div ng-controller="LivePreviewController as ctrl">
<input type="text" ng-model="ctrl.form_name>
And in my styled form partial view
<!-- styled_form.html -->
<div> {{ctrl.form_name}} </div>
This causes everything to work as expected
I have two controllers allocated to two views:
[ResultsView ng-controller="ResultsCtrl"]
[SearchView ng-controller="SearchCtrl"]
The Search View has many complex filters/options and is filled in by the user, then he/she can press "Search" on SearchView and Results should be populated into a Grid.
Now I can send information between two either by a Service or by using $rootScope.$broadcast.
Heres the problem I've run into:
[ResultsView ng-controller="ResultsCtrl"][SearchView ng-controller="SearchCtrl"]
[ResultsView ng-controller="ResultsCtrl"][SearchView ng-controller="SearchCtrl"]
[ResultsView ng-controller="ResultsCtrl"][SearchView ng-controller="SearchCtrl"]
If I were to have multiple Result-Search sections on the same page, how can I ensure they each act independently from each other? Using the Service approach, the ResultsCtrl and SearchCtrl both have the defined service
.controller("searchCtrl", ["$scope", "$searchHttp", function ($scope, $searchHttp) {
.controller("resultsCtrl", ["$scope", "$searchHttp", function ($scope, $searchHttp) {
So I can't change how each instance of the controller behaves regarding the service. Soon as one SearchCtrl calls the service, it will modify every ResultsCtrl instance.
Likewise using broadcasts $rootScope.$broadcast("searchResults"... will be picked up by every ResultsCtrl instance.
So whats the best way around this? I want to reuse the Results and Search View code since its basically the same. But I need to render each pair independently on the same page a few times.
I think the HTML structure you need is something like this.
<!--First-->
<div ng-controller="SearchCtrl">
<div ng-controller="ResultsCtrl">
</div>
</div>
<!--Second-->
<div ng-controller="SearchCtrl">
<div ng-controller="ResultsCtrl">
</div>
</div>
This HTML structure would help you to use independently the search results one's parent SearchCtrl created in ResultsCtrl.
jsfiddle is here.
I hope this would help you. :)
Suppose that I've written a web page that requires the user to key in data into forms. Suppose that there are two parts of the page that are completely separate in the DOM, yet are logically related. I need to bind them to a single model.
The first (bad) solution I can think of is to completely restructure the page so that everything to be bound to the model is contained in a single <div> element (or perhaps even put the controller on the <body> element).
Another solution might be to bind the inputs to a object that could be made a member of two different controllers, but that sounds like a bit of a kludge.
Suppose the HTML looks like this:
<input ng-model='data1' />
<div>
<!-- something complicated -->
</div>
<input ng-model='data2' />
You can create a shared service and inject into both controllers. This can be achieved by either the factory or service pattern. See SO post angular.service vs angular.factory for some insight on the difference/similarity between the two.
This is the Angular way to do this. See the AngularJS services docs for more information. A simple example may include...
<div ng-app="app">
<div ng-controller="ctrlA"></div>
<div ng-controller="ctrlB"></div>
</div>
app.service('sharedService', [function () {
this.someValue = 'yo!'
}]);
app.controller('ctrlB', ['$scope', 'sharedService', function($scope, sharedService) {
console.log(sharedService.someValue) // yo!
}]);
app.controller('ctrlA', ['$scope', 'sharedService', function($scope, sharedService) {
console.log(sharedService.someValue) // yo!
}]);
JSFiddle Link
I have some articles of the same category and I'm routing like this:
app.config(function($routeProvider) {
$routeProvider
.when('/chapter/:title', {
templateUrl: 'article.html',
controller: 'article'
});
});
article.html:
<h1>{{title}}</h1>
<p>{{content}}</p>
<button>Editor</button>
But now I want to get a route from every article of this chapter to an editor version to change the content. This editor-version could look like this:
editor_article.html:
<input type="text" value="{{title}}">
<textarea>{{content}}</textarea>
So what is the best way for routing the button of an article to the editor_article.html-template and load it with the same data?
There is no "best-way" here, and it all depends on your context.
Do all end-users have equal access to this (or put another way, should anybody who can open the dev-console be allowed to access this page)?
If not, you need to solve the login problem before you load most of the code for your SPA.
If login isn't a problem, or already taken care of, why do you need separate routes?
Do you need to cache these pages separately in-browser?
Are there other considerations beyond that?
Totally legitimate question; from an artistic perspective, perhaps you're hoping for page transitions which match other transitions you have for when you do change routes, or you are relying on routeParams for some logic that we don't know about.
But chances are good that all of the above things aside, you could simply have a button and a couple of ng-if statements, with directives.
<body >
<main ng-view></main>
</body>
<!-- template -->
<section >
<button
ng-click="article.toggleMode()">{{
article.editMode ? "View": "Edit"
}}</button>
<article-view
content="article.content"
ng-if="!article.editMode"
></article-view>
<article-edit
content="article.content"
onsave="article.save(content)"
ng-if="article.editMode"
></article-edit>
</section>
Using directives to define the two templates, using controllerAs:"article" in the example above.
Even that might be complicating it.
On Durandal, I have a login page that's styled different from my other pages, so taking the advice I saw on some of the posts here, I've set up main.js to do a:
app.setRoot('viewmodels/login');
And on the login page, I provide the below method in login.js that resets the root:
loginRedirect: function() {
app.setRoot('viewmodels/shell');
}
The idea is that the user should go to the login page by default, and once logged in, I will invoke the method loginRedirect, which sets shell.html to be the root and thus reloads the content, and he should be able to navigate other pages from there.
Here's my login.html:
<div class="login">
<div data-bind="compose:'header-nav-plain.html'"></div>
<div class="container">
<!-- Content here-->
</div>
</div>
<div data-bind="compose:'viewmodels/footer'"></div>
<a class="go-inner-pages" data-bind="click: loginRedirect" href="#">Test link to go to 'inner' pages</a>
And here's my shell.html, that is the doorway to all other pages:
<div>
<div data-bind="compose:'viewmodels/header-nav'"></div>
<div class="container">
<div data-bind="compose:'viewmodels/site-nav'"></div>
<div data-bind="router"></div>
</div>
<div data-bind="compose:'viewmodels/footer'"></div>
</div>
You can see that shell.html is slightly different in structure from login.html, in that it binds to a different header-nav.html, and also a site-nav.html. (They share the same footer.html.) So on the login page, when I click on the test link that calls the loginRedirect method, it sets the root of the app to be shell.html, which will load the default page based on the parameters passed to router.map, as defined in shell.js:
activate: function(){
//Initializing router
router.map([
{ route: '', moduleId: 'viewmodels/dashboard', title: 'Dashboard', nav: true }
])
.buildNavigationModel();
//More code
return router.activate();
But, when I click on the test link and invoke loginRedirect, the new dashboard content is loaded, but the new header-nav and site-nav data binding didn't happen. I checked the inspector, and see that the structure has indeed changed to that of shell.html (as opposed to login.html), only data binding for header-nav and site-nav didn't happen. I.e. here's the new html after invoking loginRedirect:
<div>
<div data-bind="compose:'viewmodels/header-nav'"></div><!--header nav content not bound-->
<div class="container">
<div data-bind="compose:'viewmodels/site-nav'"></div><!--site nav content not bound-->
<div data-bind="router"><div class="durandal-wrapper" data-view="views/dashboard" data-active-view="true">
<!-- Dashboard content successfully loaded -->
</div><!--end data-bind="router"-->
</div><!--end .container-->
<div data-bind="compose:'viewmodels/footer'">
<!--footer content loaded-->
</div>
And from the Console (see below), one can see that while the shell and dashboard are bound, (even footer is bound a second time), header-nav and site-nav are not.
Does anyone have any idea why this is happening and what I might be doing wrong? Thanks in advance for your attention, hope to hear from someone soon. Cheers.
For view-only bindings, you would bind this way (we do the same as you, so I'm copying and pasting our code):
compose: {view: 'shellLeftFooter.html'}"
For views bound to viewModels, you bind this way:
compose: {model: 'viewmodels/navigation/shellNavigation'}
The default viewLocator will use the following convention:
If a view is specified with no model, then the view must be identified with the .html extension, as I have done in the former example, and it will not be bound to a viewModel;
If a model is specified with no view, as I have done in the latter example, then the model file is assumed to have a .js extension, and it is also assumed that a view exists in a folder named views under the app directory, with the same name as the model, but with an .html extension.
These aren't the only two conventions, but they are the ones germane to this discussion. To put the above in different words, make sure you have both a viewmodels and a views directory that are siblings of each other under the app directory. Make sure you give the viewModel and the view identical names, differing only in their extensions (.js for the former and .html for the latter).
I'm seeing all of your .html and .js files in the code you posted, so I'm sure what your intention is in each case (view only or model-view-viewModel). Also, your compose binding is not quite to standard.