<div id="header">
Header
</div>
<div id="wrapper">
<div id="slider" ng-view>
Slider
</div>
<div id="mainWindow" ng-view>
Main window
</div>
</div>
http://jsfiddle.net/abehnaz/k9Y4f/
I am working with a webapp that has two major views. The first is a main window (standard stuff), and the second is a "slider". The slider is more than a slide out menu (sometimes called a "shelf"). It has routing and secondary information associated with it.
In what seems to be the normal Angular paradigm, I would use a single ng-view in the main window, and that would populate the main window with the results of the $routeProvider's view template.
However, here I want to be able to ALSO have the slider have its own route. Angular does not seem to support this. I was thinking that I could potentially hack the functionality in by making a client side URL of the form:
www.app.com/MAINWINDOW/someView1/someView2/SLIDER/someViewa
Am I on the right track with this, or is there another functionality of Angular that I can use?
I would not recommend two routeParameters. Make your sidebar a service and put a controller on your wrapper, slider, and mainWindow. By having the ng-controller on #wrapper as well as the children, you can share data between the controllers (and in turn, inject the service into those controllers. I have done this and it was very successful. See an example below. In my code, my ng-view and routing returned everything within the <div ng-app="myApp">. You could easily put the ng-view in the MainCtrl and trigger routes from the sidebar.
In your template:
<div ng-app="myApp">
<div ng-view>
//Produced by routing
<div id="wrapper" ng-controller="RootCtrl">
<div id="Sidebar" ng-controller="SidebarCtrl">
...
<div ng-click="setUnit(mod.number, unit.number)">Unit 1</div>
...
</div>
<div id="mainWindow" ng-controller="MainCtrl">
</div>
</div>
</div>
</div>
In your javascript:
A service:
myApp.service('questionsSvc', function(Modules) {
var QuestionsSrc = {};
QuestionsSrc.questions = [];
var getQuestionsForModAndUnitFn = function(mod, unit) {
...bunch of code to have it populate QuestionSrc with questions
};
return {
getQuestionsForModAndUnit: getQuestionsForModAndUnitFn,
Questions: QuestionsSrc
};
});
For Controllers:
function RootCtrl($scope) {
$scope.data = {};
}
function SidebarCtrl($scope, Modules, questionsSvc) {
$scope.setUnit = function (mod, unit) {
questionsSvc.getQuestionsForModAndUnit(mod, unit);
$scope.data.questions = questionsSvc.Questions.questions;
//$scope.data.questions was used in the MainCtrl window to do an ng-repeat on the json array of questions bound to this model.
}
};
function MainCtrl($scope){
...whatever you need to manipulate the code...
}
With this example, I am sharing information across Controllers. The magic is with the $scope.data = {}; from the RootCtrl. It allows me to attach questions to the $scope based on an action in the Sidebar (clicking a label) and use that same $scope in the MainCtrl to display the questions in a pretty format. With what I just showed, I did not have to use $routeParameters to pass variables to go to another page (such as module and quiz) but I could have done so as you asked but would have had the sidebar ng-click change routes.
Hope this helps.
One solution is to use Angular UI router:
AngularUI Router is a routing framework for AngularJS, which allows
you to organize the parts of your interface into a state machine.
Unlike the $route service in Angular core, which is organized around
URL routes, UI-Router is organized around states, which may optionally
have routes, as well as other behavior, attached.
States are bound to named, nested and parallel views, allowing you to
powerfully manage your application's interface.
Just to give a flavor for how you could use AngularUI Router- It supports multiple named views (which you can read more about under "Multiple & Named Views" in their docs). So for instance you can use ui-view with names:
<div id="wrapper">
<div id="slider" ui-view="slider">
Slider
</div>
<div id="mainWindow" ui-view="main">
Main window
</div>
</div>
Then within config you can attach states to various routes and specify what each view should display for that state.
myapp.config(function($stateProvider, $urlRouterProvider){
$stateProvider
.state('index', {
url: "/index",
views: {
"Slider": {
templateUrl:"route1.viewA.html"
},
"Main": {
templateUrl:"main1.viewA.html"
}
}
})
.state('slider2', {
url: "/slider2",
views: {
"Slider": {
templateUrl:"route2.viewA.html"
},
"Main": {
templateUrl:"main1.viewA.html"
}
}
})
There's a variety of other ways you could use AngularUI Router though. For instance, you may be able to get away with just using nested routes- which they recommend trying first.
Here's a couple more good reference material you might check out:
http://joelhooks.com/blog/2013/07/22/the-basics-of-using-ui-router-with-angularjs/
http://www.ng-newsletter.com/posts/angular-ui-router.html
Related
I am using Angular 1.5.7 and am trying to see if I can push the value of an attribute within a directive used on several different pages to an array that lives in the controller.
I am pretty sure that I need to used transclusion in order to do this but I am stuck. Below is a simplified version of what I have so far:
about.html
<div ng-controller="MainCtrl as ctrl"
<div cd-header mypage="About">
<div>About Page</div>
</div>
</div>
contact.html
<div ng-controller="MainCtrl as ctrl"
<div cd-header mypage="Contact">
<div>Contact Page</div>
</div>
</div>
header.html
<div>{{mypage}}
<div ng-transclude></div>
</div>
cd-header.js
var cdHeader = function() {
return {
scope: {
mypage: "#"
},
transclude: true,
templateUrl: 'header.html',
link: function(scope) {
// Not sure but I think I might need a function here
}
}
}
module.exports = cdHeader;
MainCtrl.js
var MainCtrl = function($scope) {
var nav = [];
// Not sure how items that are pushed to the nav get to this point
}
module.exports = MainCtrl;
main.js
var app = angular.module("myapp", [
'about',
'contact',
])
.controller('MainCtrl', MainCtrl)
.directive('cdHeader', cdHeader)
I am able to get the value of the mypage attribute in the directive as well as its transcluded <div> to appear in the header but only for the current page in view.
The part I am missing is how to get all of the mypage values from each page into the header regardless of the current page in view. I am somewhat new to Angular and have read a lot but have not come across anything that explains how this can be done. Maybe this is achieved with a service? If so, I'm not sure how to go about it.
To clarify with a visual. This is what I see:
But this is what I want to see:
As you rightly pointed out, there are several ways to do it.
Perhaps you can pass the array from the MainCtrl as an attribute to the directive. For instance, nav-array="nav". However, before that, you need to set the array nav as such
var $scope = this;
this.nav = [];
The second option is to consume a service. Create an angular service, pass it as a dependency in the directive, and consume it.
Lets create an array in MainCtrl as $scope.headers = ['about':'About','contact':'Contact','home':'Home'] or create a factory/service to share the headers data and in header.html use ng-repeat to display the header name according to myPage value like below
<div ng-repeat="page in headers[myPage]">{{page}}
<div ng-transclude></div>
</div>
I am a beginner in angular Js and need some help / pointers. I want to create an application (SPA). Every page of an application has a header bar and in that header bar, I have a form (like search bar) where a user can add data and search. But I am not being able to approach.
How should my approach be so that this header is present in any page of my app and I must be able to search in any page. How can this be done without code repetition? I mean I dont want to create a directive and call it in every page. I want to know if there is a proper way to do it?
The app should be like Quora where the input field for question is present in any page.
Request you to not downvote it as I am naive in Angular and need some good help.
Thank you in advance.
Search for ui-router and define some states for your application, basically you have a main states which hold header and footer, and sub states that are children of your main state.
your markup would be like this:
<header></header>
<ui-view></ui-view>
<footer></footer>
and the header can contain the search form, and all sub states are loaded in mains <ui-view>
Have you checked out ui-router?
You could define a layout/root state whose template defines multiple named ui-views. The layout/root state could load the persisting elements (ex. the search input field) into one view, and child states could load the unique page content into another view.
ex. layout.html
<div class="layout">
<div class="search" ui-view="search"></div>
<div class="content" ui-view="content"></div>
</div>
ex. state configuration (in a module's config() function)
$stateProvider.state('root', {
url: '',
views: {
'main': {
templateUrl: 'layout.html'
},
'search#root': {
template: '<input type="text" ng-model="search.input" />',
controller: ['$scope', function($scope){
$scope.search = {
input: ''
};
}]
}
}
});
$stateProvider.state('root.page1', {
views: {
'content#root': {
template: '<p>This is the first page.</p>',
}
}
})
.state('root.page2', {
views: {
'content#root': {
template: '<p>This is the second page.</p>'
}
}
});
With that config, you would need to have in your main index.html:
<div ui-view="main"></div>
Plunker Demo
So first off, I'm working on this for a project at work, but none of us have any idea how to do it, so it might be kind of vague.
Here is the template of how it is going to look: Template
So View A & B are going to have 3 states in them that will change the content of the view based on which one is selected
The problem I'm having is that only 1 view ever shows up and it is a test template for now because I don't have those views built but none of the sub views of View A ever show up.
HTML
<div id="main">
<div ui-view="viewa" class="col-sm-7">
<!--Content of ViewA supposed to be here-->
</div>
<div ui-view="viewb" class="col-sm-5">
<!--Content of ViewB supposed to be here-->
</div>
</div>
States:
$stateProvider.state("main", {
url: "/main",
views: {
"viewa#": {
abstract: true,
template: "<div ui-view></div>"
},
"viewb#": {
templateUrl: "btemps/default.html"
}
}
}).state("bobtheView", {
parent: "viewa",
//This is default for viewa
url: "/",
templateUrl: "atemps/bob.html",
controller: "bobController"
}).state("billtheview", {
parent: "viewa",
url: "/bill",
templateUrl: "atemps/bill.html",
controller: "billController"
}).state("joetheview", {
parent: "viewa",
url: "/joe",
templateUrl: "atemps/joe.html",
controller: "joeController"
});
//Supposed to route to viewa showing bobtheview and viewb showing the template
$urlRouterProvider.otherwise("/main/");
So when I go to the page and go to the root it redirects to the otherwise but nothing shows up, upon just going to main, only the viewb template shows up.
Any ideas? Any way I can format it better too? Is it better to go with "viewa.bobtheview" over having the parent attribute in the mix?
UPDATE: So I found a work around, I loaded each of the bobtheview, joetheview and billtheview in html partials, then I refactored it so the view state of viewa and viewb are controlled within a main template that includes the "ng-include" function to load the different templates, and since all of the data that is stored in those views is given via JSON rest requests, there is no change in the data bindings. The problem I'm facing now, is updating that "ng-include" on button click, I haven't done extensive research on it but I plan on doing so and I'll report back when/if I find something. If you have any ideas on this let me know! :D.
So I found a viable answer to the question at hand, after extensive research and asking around, I went with the option of having 1 Controller and configuration state
$stateProvider.state("main", {
url: "/",
controller: "mainController",
templateUrl: "temps/primary.html"
});
$urlRouterProvider.otherwise("/");
That went into the configuration settings, then my controller looked a little like this:
app.controller("mainController", ["$scope", "$state", "$stateParams", "$http", function($scope, $state, $stateParams, $http) {
$scope.viewatemp = $stateParams.at; //Numeric value to represent template url for viewa
$scope.viewbtemp = $stateParams.bt; //Numeric value to represent template url for viewb
//Do some other stuff here
});
Then the HTML of "temps/primary.html" looked a little something like this:
<div ui-view="viewa" class="col-sm-5" ng-include="viewatemp"></div>
<div ui-view="viewb" class="col-sm-7" ng-include="viewbtemp"></div>
I did a little manipulation of the numeric value of viewatemp and viewbtemp to get the actual URL, those are being loaded from a JSON request from my ASP.net WebApi 2 Restful service, but all in all, it is quick, rather simple and still gets the job done and allows for further enlargement of the project.
And that there in solved my problem, cool thing about this, I can have as many as these as I want because they are all separate states with nested "views"
If you do have a better answer, let me know! This is only what I found and what worked for me.
Here is the mock up of my app:
"Left side bar" is for directories, while the "main content" is for files. The files are enumerated from the selected directory ([list mode]).
The "left side bar" is to be reused for 2 other purposes:
display form to create new directory ([create mode])
display form to edit selected directory ([edit mode])
Similarly, the main content could also be in [list mode], [edit mode], and [create mode]. So, in total there would be 3 x 3 possible combinations.
Using ng-switch, this one can be modeled quite easily.
<div class="left-bar">
<div ng-switch on="directory.mode">
<div ng-switch-when="list"></div>
<div ng-switch-when="create"></div>
<div ng-switch-when="edit"></div>
</div>
</div>
<div class="main-content">
<div ng-switch on="files.mode">
<div ng-switch-when="list"></div>
<div ng-switch-when="create"></div>
<div ng-switch-when="edit"></div>
</div>
</div>
However, I wish to model this using angular-ui router. I am new to angular-ui, and the state model I could think of now is something like:
.state('main.folder-list.file-list', views: {'left-sidebar':{templateUrl:'directory-list.html'}, 'main-content':{templateUrl:'file-list.html'}})
.state('main.folder-list.file-edit', ...)
.state('main.folder-list.file-create', ...)
.state('main.folder-edit.file-list', ...)
.state('main.folder-edit.file-edit', ...)
.state('main.folder-edit.file-create', ...)
.state('main.folder-create.file-list', ...)
.state('main.folder-create.file-edit', ...)
.state('main.folder-create.file-create', ...)
One important requirement: when the mode of "left side bar" is switched, the content of "main content" shouldn't be changed (it should still be in the same mode as before), and vice versa.
How to simplify that?
First think first, remember that angular-ui router able to handle nested state. I usually use .state('root'), .state('root.app'), .state('root.app.specific'), etc. With that characteristics of angular-ui router, we can simply set $scope at root state like $scope.data = { status: 'ok' } and in root's child, we still able to call that $scope.data.
Also if your create, list, edit is a generic "class" or singleton. You can also make it simple using angular controller inheritance in your controller type $controller('[parent-controller]', {$scope: $scope});. So your parent-controller's $scope like method that they have etc. will be able to called inside your new controller's $scope. Angular-ui router's nested state is similar to this. For example:
Application.controller('AppCtrl', ['$scope', '$controller', function($scope, $controller) {
$controller('AdminCtrl', {$scope: $scope});
}]);
In that example my AppCtrl will have the AdminCtrl's $scope.
I'm trying to dynamically include a template into my index.html. The general structure of index.html is:
<body>
<header ng-controller="Main">
<section>
<!-- global stuff -->
</section>
<section ng-include="moduleName + '/views/menubar.html'">
<!-- module-based stuff -->
</section>
</header>
<div id="view" ng-view></div>
</body>
Sample URL
example.com/<app_name>/index.html#/<module_name>[/method_name]
I can't figure out how to update $scope.moduleName when the route changes. My trouble is two-fold:
The header's controller is Main, not the controller associated with the view, so I can't? update $scope.moduleName from the view's controller (because Main and the view's controller are siblings).
In Main, I tried setting a $scope.$on('$routeChangeSuccess',…), but apparently it is not notified of route changes.
I've thought of setting up a $rootScope.$on listener (as described in SO#15355346) for the route change and broadcasting down to children, who then emit back up their route, which is broadcasted back down so it is available to Main. But that seems heinous.
And I would really prefer to keep the header outside of ng-view.
EDIT I noticed that $route.current.scope has an object named with module_name (possibly because the name of the controller associated with the route's module_name is the same). I'm wondering if I might be able to somehow use the name of that object…
It's hard to say what's wrong in your code without the full picture. Things you show look fine to me.
Please see this plunk I've created to display the ability to do it. Take note that you also can extend route objects with custom properties, like moduleName here:
$routeProvider.when('/page1', {
template: 'one',
controller: 'one',
moduleName: 'firstModule'
});