On my site I have a navbar component that I want to customize for each and every ng-view I end up loading. Currently I'm doing this as follows. I have a NavCtrl for the navbar itself and my ng-view directive sits outside the scope of this controller. And I use a navbar service to change/override functionality in the navbar e.g each of my views need to override the click handler for a save button my navbar. The NavbarService has hookes to set the save function. And in the NavCtrl the $scope.save = NavbarService.save
var app = angular.module('myApp', ['ngRoute', 'ngResource']);
app.config(function($routeProvider) {
$routeProvider.when('/world', {templateUrl : 'world.html', controller : 'WorldCtrl'}).otherwise({templateUrl : 'hello.html', controller : 'HelloCtrl'});
});
app.service('NavbarService', function() {
var message = 'Save button not clicked yet',
saveFunction = function() {
this.setMessage('Default save called');
};
this.save = function() {
_saveFunction();
};
this.setSaveFunction = function(funct) {
_saveFunction = funct;
};
this.setMessage = function(newMessage) {
message = newMessage;
};
this.getMessage = function() {
return message;
}
});
app.controller('NavCtrl', function($scope, $location, NavbarService) {
$scope.message = NavbarService.getMessage();
$scope.save = NavbarService.save;
$scope.world = function() {
$location.path('/world');
};
$scope.hello = function() {
$location.path('/hello');
};
$scope.$watch(NavbarService.getMessage, function(newValue) {
$scope.message = newValue;
});
});
app.controller('HelloCtrl', function($scope, NavbarService) {
$scope.init = function() {
NavbarService.setSaveFunction(function() {
NavbarService.setMessage('Save method called from the HelloCtrl');
});
};
});
app.controller('WorldCtrl', function($scope, NavbarService) {
$scope.init = function() {
NavbarService.setSaveFunction(function() {
NavbarService.setMessage('Save method called from the WorldCtrl');
});
};
});
<html lang="en">
<head>
<title>My App</title>
</head>
<body ng-app="myApp">
<nav ng-controller="NavCtrl">
<button ng-click="save()">Save</button>
<button ng-click="world()">Go to world</button>
<button ng-click="hello()">Go to hello</button>
<pre>{{message}}</pre>
</nav>
<div ng-view onload="init()"></div>
<script type="text/ng-template" id="hello.html">
<h2>Active view is Hello</h2>
</script>
<script type="text/ng-template" id="world.html">
<h2>Active view is World</h2>
</script>
<script src="https://code.jquery.com/jquery-2.1.4.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular-route.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular-resource.js"></script>
</body>
</html>
I'm wondering if I'm making this too complicated. I think the same thing can be achieved by just nesting the ng-view directive within the NavCtrl's scope. That way I can override $scope.save in each view controller.
But most the documentation states that services are the preferred way to share resources across controllers. Is one way better than the other? Why?
Please advise. Thanks.
However I will have to repeat the <button ng-click="save()">Save</button> element in every template if since all my views need that button.
This comment clears up a few things, and in that case I do not necessarily recommend the dynamic/declarative approach. Instead, this particular UX can be considered more of a static UX and I'd do something a little differently that still avoids any service or scope inheritance. I suggest using the following:
app level
Your app level nav can still borrow the other answer's suggestion, however let's disregard the dynamic/declarative content. So the nav looks like:
<div class="navbar navbar-submenu2">
<ul class="nav navbar-nav navbar-submenu2">
<!-- container for dynamic view-specific menu content -->
<li nx-view-menu-container class="nav navbar-nav">
</li>
<!-- static nav items -->
<li class="nav navbar-nav">
<a ng-click="saveRqst()">
<span class="current-del-name">Save</span>
</a>
</li>
The app-level controller would have this:
.controller 'appController', [
'$scope'
($scope)->
$scope.saveRqst = ->
$scope.$root.$broadcast 'saveRqst', <addt. data if applicable>
]
per view level controller
This makes a few assumptions:
only on of these types of views are ever present (meaning it's one view/controller per application state, otherwise you might have 2 controllers trying to process a save rqst)
your views reside as a child within your application template
your views adhere to the normal view controller paradigm (meaning they're not special directives with isolate scopes)
.controller 'someController', [
'$scope'
($scope)->
$scope.$on 'saveRqst`, (data)-> #do something for this view
]
Now I know this looks kinda like the scope inheritance bit, but it's not. You have to define your save logic per view controller logic anyway, there's no getting around that if I've understood what you've presented in the question and other answer's comment. The one nice bit about this event bus approach is it's easy enough to track the logic. If you notate your code, then another developer who's had any experience working with Java or Flex or whatever, will easily be able to get up to speed on implementing additional views w. save logic.
So I've presented 2 approaches to this. One is the more dynamic, declarative approach and this one is the more static, yet unique-per-view approach.
preface & some history
Let me propose something that utilizes neither a service, nor scope inheritance. Here is a similar issue I was trying to tackle. I have views that get loaded via ui.router's ui-view (we're basically talking application states here right?). I have my normal application nav & subnav, something like this...
['findings', 'tasks', 'reports', 'blah']
...that are common to all views. But I also have some specific UI per view that I want wired to that view's $scope/controller. In other words, I may have a dropdown specific to the current view, and a typeahead for another view. I elected to tackle this declaratively with a directive rather than trying to force some inheritance override paradigm.
directives
nxViewMenuContainer
First off I made a directive that was simply a container for housing the declared elements specific the views' nav elements.
.directive 'nxViewMenuContainer', [
()->
dir =
restrict: 'EAC'
link: ($scope, elem, attrs)->
if !elem.attr 'id'
elem.attr 'id', 'nx-view-menu-container'
$scope.$root.$on '$stateChangeSuccess', ->
elem.empty()
]
This is implemented like so:
<div class="navbar navbar-submenu2">
<ul class="nav navbar-nav navbar-submenu2">
<!-- container for dynamic view-specific menu content -->
<li nx-view-menu-container class="nav navbar-nav">
</li>
<!-- static nav items -->
<li class="nav navbar-nav" ng-repeat="nav in app.subnav">
<a><span class="current-del-name">{{nav.label}}</span></a>
</li>
nxViewMenu
This directive is declared on each view and serves as the view-specific elements container that gets moved to the directive above.
.directive 'nxViewMenu', [
()->
dir =
restrict: 'EAC'
link: ($scope, elem, attrs)->
elem.ready ->
name = attrs.nxViewMenu or '#nx-view-menu-container'
target = $(name)
if !target.length
return
target.empty()
target.append elem
]
And inside each view where I might want a dynamic menu to appear in else where (in this case, in the app-level's nav container) I declare it inside my view templates.
view 1
<div class="nx-view-menu">
<a class="btn btn-default">
<i class="fa fa-lg fa-bars nx-clickout-filter"></i>
<!--<label name="panel-title" style="color:#58585b; padding-left:5px;" class="nx-clickout-filter">Quick Links</label>-->
</a>
</div>
view 2
<div class="nx-view-menu">
<input typeahead="...">
</div>
view 3
<div class="nx-view-menu">
<date-picker>...</date-picker>
</div>
closing arguments
Firstly you are doing things declaratively so it's easy to follow the logic once you understand the directives.
It keeps the top level controllers ignorant of the specific views' logic while still displaying the DOM in a consistent way
It bypasses the need for special services or some hacky message bus
It simplifies your overall DOM structure because you don't need to set up some dual-view containers (in my case that was the other option in using ui.router) or parallel states.
Related
A few months ago I've be created the topic: Try to render two templates in same View (Ui-Router), where I asked about how to render multiple views in the same page. My objective with this was created a web app as an desktop one, with views to minimize, maximize, close and stuff like that.
Well, my app is ready but I'm getting a problem, when I up my app to the production some computers are taking a long time to render all the Views. In the image bellow we can se a lot of requisitions that server take to return my templatesURL's.
There is a way to avoid this ? I was looking for an lazy load to templateURL but I Didn't find any. :(
This plunkr was the approach what I used. I have only one state for all my Views (My current app.config has 103 Views):
routerApp.config(function($stateProvider) {
$stateProvider.state('mainState', {
views: {
'CompanyView': {
templateUrl: 'Company.html'
},
'PeopleView': {
templateUrl: 'People.html'
},
.....
....
}
})
});
Introduction
The way you approached the solution is the cause of the problem you're facing, because you have too many views for a single state, it'll end up having to load all of them in order to set that state, so every time you access your state, ui-router has to load every template in order to set the views. It might not cause problem for a few number of templates, but, for larger numbers like yours it is definitely an issue.
Ng-Templates
You can try to cache your templates in your page using <script type="text/ng-template"... in order to prevent the loading time, it's a good practice by the way. Usually it's part of the production build optimization, load all templates in the template cache, so that the application load time decreases significantly provided that you don't have to wait for an http call to load a page. It will indeed increase the performance in your case, but I don't have a benchmark that ensure if it'd be enough for your scenario.
Component Based Solution
Anyhow, you can always implement interface components to behave the way you want, optimized in such a way that it doesn't have to load one hundred templates to show a single panel for the user.
My suggestion is, instead of using ui-router, use a component based solution, create a directive component to hold the panel content of each window and its behavior; and use a controller to manage the state of opened and closed panels, holding and managing each opened panel in a list and so on. For example:
<nav>
<button ng-click="openPanel({title: 'My Panel Title', templateUrl: 'myPanel.html'>">
Open myPanel
</button>
<nav>
<main>
<panel ng-repeat="panel in openedPanels"></panel>
</main>
The following snippet implements this approach using bootstrap 4 css, each panel is a bootstrap card, and it has a list of panels it can open and on click of a nav list it adds the respective panel to the opened panels list where angularjs can render it on the html using ng-repeat. This way, only the opened window will be rendered, therefore, only the opened window template will be loaded.
Disclaimer: This is a very simple example implemented not using the best practices available out there. If you intend to use this approach you should implement it based on your application to fit better the needs of your architecture, this one is not a complete functional component, it's just an example for the sake of the demonstration.
angular.module('app', [])
.controller('PanelsCtrl', function($scope) {
// available windows to be opened
$scope.panels = [
{ title: 'Window 1', templateUrl: 'window1.html' },
{ title: 'Window 2', templateUrl: 'window2.html' }];
// all currently opened panels
$scope.openedPanels = [];
// opens a panel (a.k.a, adds a panel
// to the opened panels list)
$scope.openPanel = function(panel) {
if ($scope.openedPanels.indexOf(panel) === -1)
$scope.openedPanels.push(panel);
};
// close a panel (a.k.a, removes a panel
// from the opened panels list)
$scope.onClosePanel = function(panel) {
$scope.openedPanels.splice($scope.openedPanels.indexOf(panel), 1);
};
})
.directive('window', function($templateRequest, $templateCache, $compile) {
return {
restrict: 'E',
scope: {
panel: '=',
onClosePanel: '&'
},
template: `
<div class="card">
<h4 class="card-header">
<span>{{ panel.title }}</span>
<button
ng-click="onClosePanel(panel)"
type="button"
class="close"
data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</h4>
<div class="card-body">
<ng-include src="panel.templateUrl"></ng-include>
</div>
</div>
`
}
})
// example controlelr to be used with ng-controller
.controller('Window1Ctrl', function($scope) {
$scope.window1Prop = 'This is a property from Window1Ctrl'
})
#import 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css'
<div ng-app="app">
<div class="container" ng-controller="PanelsCtrl">
<div class="row">
<div class="col-sm-3">
<ul class="nav flex-column">
<li class="nav-item" ng-repeat="panel in panels">
<a class="nav-link active" href="#" ng-click="openPanel(panel)">
{{ panel.title }}
</a>
</li>
</ul>
</div>
<div class="col-sm-9">
<window ng-repeat="panel in openedPanels" panel="panel" on-close-panel="onClosePanel(panel)">
</window>
</div>
</div>
</div>
<!-- NG-TEMPLATES -->
<script type="text/ng-template" id="window1.html">
<div ng-controller="Window1Ctrl">
<b>{{panel.title}}</b>
<h5>window1Prop: {{ window1Prop }}</p>
</div>
</script>
<script type="text/ng-template" id="window2.html">
<em>{{panel.title}}</em>
</script>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular.js"></script>
I am using electron with angular 1.6.4.
I have a controller, in which I dynamically generate li. I want to bind a double click event on this list items, but I could not succeed.
function TheController($http, $scope, KeyService) {
$scope.openItem = function(id) {
console.log(id);
}
var key = KeyService.getLastKey();
connectToBackend($http,key);
}
function connectToBackend($http, key) {
$http.get(ENDPOINT).then(
function(result) {
//do some work
document.getElementById("list").innerHTML += `<li draggable="true" ondragstart="itemDrag(event)" id=${theID} ng-dblclick="openItem(this.id)"><i class="fa fa-folder-open"></i> ${result}</li><hr>`;
},
function(e) {
//error
}
);
}
If I double-click here, absolutely nothing happens - not even an exception.
If I use ondblclick, it works if I define openItem in renderer.js. But I'd rather like to have it defined inside TheController, to keep some order and to be able to access injected services.
Is this possible? Is the drag stuff maybe interfering?
The directly appended html wouldn't work until you compile it. You should manually compile it before injecting it into DOM tree.
document.getElementById("list")
.appendChild($compile(`
<li draggable="true" ondragstart="itemDrag(event)"
id=${theID} ng-dblclick="openItem(this.id)">
<i class="fa fa-folder-open"></i> ${result}
</li>
<hr>`)($scope);
Generally doing DOM manipulation from directly controller is anti-pattern as it makes your controller code to more tightly coupled with view/html.
Rather I'd suggest you to use ng-inlcude directive and place custom template in ng-template script. So that it will available any time inside $templateCache of angular.
<script id="myCustom.html" type="text/ng-template">
<li draggable="true" ondragstart="itemDrag(event)"
id="{{theId}}" ng-dblclick="openItem(id)">
<i class="fa fa-folder-open"></i>
<div ng-include="ENDPOINT"></div>
</li>
</script>
and then your html will look like below.
Html
<div id="list">
... Your content ..
</div>
<div ng-include="'myCustom.html'"></div>
If you noted, I directly used ENDPOINT directly inside ng-include for the same to work, you have to do some additional setting
angular.module('myApp').config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
// Allow loading from outer templates domain.
'http://somedomain.com/templates/**' //ENDPOINT domain should white listed here
]);
});
Also ondragstart wouldn't call your controllers method, until you patch it up with angular wrapper directive. There are third-party library available out there, you could use any one of them.
I am using Kendo tabstrip with angular ng-repeat and ui-router. Now the tabstrip can have different combination and different number of tabs based on the input. I am able to give different ui-sref and ui-view names to each tab while adding tabs dynamically using angular ng-repeat.
<div class="demo-section k-content">
<div kendo-tab-strip="detailsTabStrip" k-ng-delay="Tabs">
<ul>
<li ng-repeat="tab in Tabs" ng-class="getClass($first,tab)" ui-sref="{{tab.sref}}">
{{tab.name}}
</li>
</ul>
<div ng-repeat="tab in Tabs" ui-view="{{tab.view}}"></div>
</div>
</div>
So, this code gives an individual ui-view to each tab. There can 150 different types of views possible in tabs. So, I cannot hard code the view names in the router file. I need the router to accept a parameter for view name or at least in some way I can let the router know which view to populate with the html.
$stateProvider
.state('details.tab', {
url:'/tab/:tabName',
views:{
// the view name should be able to either read the state param and
// accordingly assign template or should at least be able to read the state
// and accordingly assign the template or if views can be a function.
"details.tabName": {
templateUrl: 'resources/pages/grid.html',
controller: 'gridController'
}
}
});
Is it possible to make the views names dynamic in any way. There are other parts in the application which have absolute ui-view defined and work perfectly. The changes made to make views names dynamic for this part should not affect the other parts of application. Can I use relative views to achieve this in any way? Thanks for all the help.
Also, if I am adding tabs to kendo-tab-strip dynamically using ng-repeat, can i specify a single ui-view="details" for the tabstrip. Like for all the tab states, populate details view.
<div kendo-tab-strip="tabStrip">
<ul>
<li ng-repeat="tab in Tabs" ng-class="getClass($first,tab)" ui-sref="{{tab.sref}}">
{{tab.name}}
</li>
</ul>
<div ui-view="details"></div>
</div>
If I do this, all the tab loads up fine first time but if I try to deep dive into tabs, for ex: if I try to deep dive into fifth tab, then the content for fifth tab loads up fine but its not shown. Even though the tab is shown as active(I am using ng-class as a function to decide which tab should be shown active by sending k-state-active to the appropriate tab while adding it).
And if I define different views for different tabs dynamically like below,
<div kendo-tab-strip="detailsTabStrip" k-ng-delay="Tabs">
<ul>
<li ng-repeat="tab in Tabs" ng-class="getClass($first,tab)" ui-sref="{{tab.sref}}">
{{tab.name}}
</li>
</ul>
<div ng-repeat="tab in Tabs" ui-view="{{tab.view}}"></div>
</div>
then dont know, how to tell state router which view should be populated when a particular tab is clicked. Like when third tab is clicked, the corresponding ui-view specified is ui-view="third",when fourth tab is clicked, the corresponding ui-view specified is ui-view="fourth" and so on.How to configure the views dynamically in ui-router?
Thanks a lot for all the help.
Make a common view, use that view name on your state provider.
In that view include your desiered view as
<ng-include ng-src="'resources/pages' + tabname + '.html'" />
A cleaner solution is provided here:
app.js:
app.config(function ($locationProvider, $urlRouterProvider, $stateProvider) {
$urlRouterProviderRef = $urlRouterProvider;
$locationProvider.html5Mode(false);
$stateProviderRef = $stateProvider;
});
app.run(['$q', '$rootScope', '$state', '$http',
function ($q, $rootScope, $state, $http)
{
$http.get("myJson.json")
.success(function(data)
{
angular.forEach(data, function (value, key)
{
var state = {
"url": value.url,
"parent" : value.parent,
"abstract": value.abstract,
"views": {}
};
angular.forEach(value.views, function (view)
{
state.views[view.name] = {
templateUrl : view.templateUrl,
};
});
$stateProviderRef.state(value.name, state);
});
$state.go("home");
});
}]);
Reference from AngularJS - UI-router - How to configure dynamic views
I am new to web programming and especially to AngularJS.
So maybe my question will seem naive to some of you.
I'm developing a single page application using angular-ui-router.
I have created a multi-step form that contains 3 states:
(function () {
"use strict";
angular.module("sensorManagement", ["ui.router", "ngAnimate", "ngResource", "toaster"])
.config(["$stateProvider", "$urlRouterProvider", function ($stateProvider, $urlRouterPorvider) {
$urlRouterPorvider.otherwise("/Sensor/Home");
$stateProvider
.state("MultiStepForm", {
url: "/Sensor/MuiltyStepForm",
templateUrl: "/app/MultiStepForm/MuiltyStepForm.html",
})
.state('MultiStepForm.step1', {
url: '/step1',
templateUrl: '/app/MultiStepForm/FormStep1.html'
})
.state('MultiStepForm.step2', {
url: '/step2',
templateUrl: '/app/MultiStepForm/FormStep2.html'
})
.state('MultiStepForm.step3', {
url: '/step3',
templateUrl: '/app/MultiStepForm/FormStep3.html'
});
}]);
})();
Here is the HTML code:
<!-- form.html -->
<div class="row">
<div class="col-sm-8 col-sm-offset-2">
<div id="form-multiple-step">
<div class="page-header text-center">
<!-- the links to our nested states using relative paths -->
<!-- add the active class if the state matches our ui-sref -->
<div id="status-buttons" class="text-center">
<a ui-sref-active="active" ui-sref=".step1"><span>STEP1</span></a>
<a ui-sref-active="active" ui-sref=".step2"><span>STEP2</span></a>
<a ui-sref-active="active" ui-sref=".step3"><span>STEP3</span></a>
</div>
</div>
<form id="single-form">
<!-- our nested state views will be injected here -->
<div id="form-views" ui-view></div>
</form>
</div>
</div>
</div>
As you can see I have 3 states and each state has it's own view. The views have multiple elements (textboxes and checkboxes).
When the user enters some data for the STEP1 view and moves to the next step (STEP2 or STEP3) then at some point goes back to STEP1 all data is deleted. I checked with fiddler and can see that when I move from one state to another a call is made to the server and a new view generated.
My question is how can I prevent the lose of data when I move from one state to another? Do I have to use caching? Or maybe there is another way to prevent server calls and keep the data alive until the submit button clicked.
When you feel you have to save data across your controllers in your app. you should use a service.
app.factory('sharedDataService', function ($rootScope) {
console.log('sharedDataService service loaded.');
// do something here that can be shared in other controller
});
//pass this service in your controller to use it there
app.controller('Controller2', ['$scope', 'sharedDataService', function ($scope, sharedData) {
console.log('Controller2 controller loaded.');
//get data from shared service
}]);
find a useful Fiddle here
Cheers if it helps!
I think what you need to do is share you $scope between the parent and child stats. here is the stackoverflow post with good example. https://stackoverflow.com/a/27699798/284225
AngNoob here. I have some global navigation that uses the routeProvider to swap out external html pages inside the view. Within the view i set up a list type sub navigation (created with ng-repeat) that switches out divs in the external html file. I can get it to load up the page if I set it manually in the appCtrl:
//Here I set the initial value
$scope.page = 'Comfort Homes of Athens';
But when I click on the span that has the ng-click. I get nothing. I started to think it was a scope issue but when i put just an ng-click='alert()' it does nothing either.
I have read around other posts but most seem to be putting a ng-click inside of an ng-switch rather than the reverse. and aren't using routing in their examples either. Still new to angular so maybe its something I haven't come across yet.
App HTML:
<body ng-app="app">
<header ng-include="header.url" ng-controller="nav"></header>
<article ng-view></article>
<footer ng-include="footer.url" ng-controller="nav"></footer>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular-route.js"></script>
<script type="text/javascript" src="js/data.js"></script>
<script type="text/javascript" src="js/model.js"></script>
</body>
External HTML File:
<div id="web" class="wrapper">
<aside class="boxModel">
<div id="controller" class="container">
<div class="topBox bluebg subNavBar"><h1 class="white">Projects</h1></div>
<div ng-controller="nav" id="controls" class="botBox whitebg">
<span ng-repeat='item in webProjects' ng-click="page='{{item.name}}'">{{item.name}}</span>
</div>
</div>
</aside><section ng-switch on="page" class="boxModel">
<div ng-switch-when="Comfort Homes of Athens" id="sandbox" class="container round box whitebg">
<h1>Here is link 1</h1>
</div>
<div ng-switch-when="Sealpak Incorporated" id="sandbox" class="container round box whitebg">
<h1>here is Link 2</h1>
</div>
</section>
</div>
JS:
var app = angular.module("app", ["ngRoute"]);
function nav($scope) {
$scope.templates = templates;
$scope.header = $scope.templates[0];
$scope.footer = $scope.templates[1];
$scope.mainNav = mainNav;
$scope.footNav = footNav;
}
app.config(function($routeProvider) {
$routeProvider.when('/',{
templateUrl: "templates/home.html",
controller: "AppCtrl"
}).when('/templates/web.html',{
templateUrl: "templates/web.html",
controller: "AppCtrl"
}).when('/templates/seo.html',{
templateUrl: "templates/seo.html",
controller: "AppCtrl"
}).otherwise({
template: "This doesn't exist!"
});
});
app.controller("AppCtrl", function($scope) {
$scope.webProjects = webProjects;
$scope.seoProjects = seoProjects;
//Here I set the initial value
$scope.page = 'Comfort Homes of Athens';
});
Unfortunately for you, ng-repeat creates child scopes which are siblings with each other and children of your parent controller (ng-controller="nav") while your <section> where ng-switch is on is not child scope of your ng-controller="nav", but AppCtrl.
You could try ng-click="$parent.$parent.page=item.name" just to understand scopes in angular.
<div id="web" class="wrapper">
<aside class="boxModel">
<div id="controller" class="container">
<div class="topBox bluebg subNavBar"><h1 class="white">Projects</h1></div>
<div ng-controller="nav" id="controls" class="botBox whitebg">
<span ng-repeat='item in webProjects' ng-click="$parent.$parent.page=item.name">{{item.name}}</span>
</div>
</div>
</aside><section ng-switch on="page" class="boxModel">
<div ng-switch-when="Comfort Homes of Athens" id="sandbox" class="container round box whitebg">
<h1>Here is link 1</h1>
</div>
<div ng-switch-when="Sealpak Incorporated" id="sandbox" class="container round box whitebg">
<h1>here is Link 2</h1>
</div>
</section>
I don't recommend using this solution as it's quite ugly. The solution of #link64 is better, but I think the inheritance of model is so implicit and creates a tightly-coupled code. Here I propose another solution which I hope is better by emitting an event:
<span ng-repeat='item in webProjects' ng-click="$emit('pageChange',item.name)">{{item.name}}</span>
I'm not sure if angular is able to resolve $emit('pageChange',item.name) expression in the template. If you run into any problems, you could write inside your controller:
<span ng-repeat='item in webProjects' ng-click="setPageChange(item.name)">{{item.name}}</span>
In your nav controller:
$scope.setPageChange = function (pageName) {
$scope.$emit("pageChange",pageName);
}
In your AppCtrl, listen to the event and update the page.
app.controller("AppCtrl", function($scope) {
$scope.webProjects = webProjects;
$scope.seoProjects = seoProjects;
//Here I set the initial value
$scope.page = 'Comfort Homes of Athens';
$scope.$on("pageChange", function (event, newPage){
$scope.page = newPage;
}
});
In addition to #KhanhTo's answer, I wanted to point you toward another tool to use instead of ngRoute; UI-Router. This is not the answer to your original question, but it is a better solution that avoids your issue entirely.
UI-Router enhances the page routing of ngRoute and is more centered around states. You transition to states that have templates and optional controllers. It emits its own events such as $stateChangeStart or $stateChangeSuccess. You can invoke these state transitions with the function command $state.go(stateName) or by a directive ui-sref="my.state({name: item.name})
UI-Router is a very powerful tool and I cannot go into all the details here but the documentation and community is great.
A simple rewrite of your code could look like the following.
Template for web.html
<div class="wrapper">
<aside class="boxModel">
<div id="controller" class="container">
<div class="topBox bluebg subNavBar"><h1 class="white">Projects</h1></div>
<div ng-controller="nav" id="controls" class="botBox whitebg">
<span ng-repeat='item in webProjects' ui-sref="app.web.page({name: {{item.name}})">
{{item.name}}
</span>
</div>
</div>
</aside>
<section class="boxModel">
<div ui-view class="container round box whitebg">
<!-- Page content will go here -->
</div>
</section>
</div>
JavaScript
app.config(function($stateProvider) {
$stateProvider
.state('app', {
abstract: true,
template: '<div ui-view></div>', //Basic template
controller: "AppCtrl",
}).state('app.home', {
templateUrl: "templates/home.html",
url: '/home'
}).state('app.web',{
templateUrl: "templates/web.html",
url: '/web'
}).state('app.web.page',{
templateUrl: "templates/page.web.html",
url: '/web/page/:name' //Note here the ':' means name will be a parameter in the url
}).state('app.seo',{
templateUrl: "templates/seo.html",
url: '/seo'
});
});
app.controller('AppCtrl', function($scope){
$scope.webProjects = webProjects;
$scope.seoProjects = seoProjects;
$scope.$on("$stateChangeStart", function (event, toState, toParams, fromState, fromParams){
if(newState.name == 'app.web.page'){
var pageName = newStateParams.name; //Variable name matches
$scope.linkText = fetchPageContent(pageName);
}
});
});
Template for page.web.html
<h1>{{linkText}}</h1>
With these changes you will be able to reuse the same instance of your controller. In addition to allowing your paging content to be more scalable.
Notes on $scopes
Every $scope has a parent except for the $rootScope. When you ask for an object in the view, it will look at its $scope to find the reference. If it does not have the reference, it will traverse up to its parent scope and look again. This occurs until you get to the $rootScope.
If you assign something to the $scope in the view, it will assign it to the current $scope as opposed to searching up the $scope chain for an existing property. That is why ng-click="model.page = ..." works; it looks up the $scope chaing for model and then assigns to the page property whereas ng-click="page = ..." assigns directly to the current $scope.
Notes on Controller re-use
To my knowledge, ngRoute does not support nested views. When you go to a new route, it will destroy the current view and controller as specified in the $routeProvider and then instantiate a new controller for the new view. UI-Router supports nested states (i.e. child states with child $scopes). This allows us to create a parent controller that can be re-used amongst all the child states.
I think this may be related to some misunderstanding of how scope works.
ng-repeat creates its own scope. When attempting to set page, angular creates it on the scope of the ng-repeat.
In your AppCtrl, create an object on the scope as follows:
$scope.model = {};
$scope.model.page = 'Comfort Homes of Athens';//Default value
On your ng-click, refer to model.page instead of just page. Angular will then traverse up the scope to find model.page instead of just create a property on the local scope of the ng-repeat.
<span ng-repeat='item in webProjects' ng-click="model.page='{{item.name}}'">{{item.name}}</span>
Also, your AppCtrl is going to be recreated every time you change pages. You should probably use a service to persist the state between page changes