I have this html:
<div ng-controller="MyCtrl">
<div ng-view></div>
</div>
<script type="text/ng-template" id="/a">
// SomeHtml with Angular templates
</script>
<script type="text/ng-template" id="/b">
// SomeHtml with Angular templates
</script>
And:
angular.module('ngView', [], function($routeProvider, $locationProvider) {
$routeProvider.when('/a', {
templateUrl: '/a',
controller: MyCtrl
});
$routeProvider.when('/b', {
templateUrl: '/b',
controller: MyCtrl
});
});
The controller "MyCtrl" has some bootstrap code that is invoked when the html is first loaded, this bootstrap code sets up some state that should be used by both "/a" and "/b" template. Templates "/a" and "/b" will present the data obtained during the bootstrap to render in different ways.
I'd like to not have a controller and still be able to access MyCtrl scope from my templates.
I would remove the wrapping controller, and have my routes each have their own controller. If these controllers need shared data then I would add a dedicated object that holds these data to the controllers' dependency lists. Here is an example: https://stackoverflow.com/a/9407953/410102
Beside the Angular website says that you should point some controller you are not required to do it, and if the tag with the ng-view attribute is wrapped into another tag that has a ng-controller then the template rendered will be able to access the parent scope as usual.
Your template controller will have a parent controller (your so called wrapping controller), which it inherits. So you can execute functions and access properties from your wrapping controller.
function TemplateAController($scope) {
...
}
function WrappingController($scope) {
$scope.execute = function() {
...
}
...
}
In your template:
<a ng-click="execute()">Execute</a>
Related
If I have two list items
<li></li>
<li></li>
and based on which is clicked, to use ng-include to render in a div on the current page?
<div ng-controller="main-panel" class="main-panel">
<ng-include src="'clickedElement'"></ng-include>
</div>
I am confused as to how to use routes to render an html inside a div, which is decided by which element you click?
main.config(function ($routeProvider, $locationProvider, $httpProvider) {
$routeProvider
.when('/', {
controller: 'side-menu'
})
.when('/signup', {
templateUrl : 'signup.html',
controller: 'main-panel'
});
$locationProvider.html5Mode(true);
});
HTML
<li ng-repeat="oucampus in secondaryLinks.oucampus">
<a ng-href='{{oucampus.href}}'> {{oucampus.title}} </a>
</li>
<div class="main-panel" ng-view></div>
CONTROLLER FUNCTION
oucampus: [
{title: "Requests", href:"signup.html"},
],
Plunker
If you are trying to render HTML content based on routes, you would want to use a routing service such as ngRoute or ui-router. ng-include isn't the best option for implementing routing within your angular application.
With ngRoute, you use a directive ng-view to have angular load html/controllers/etc based on route specified/configured in your applications config() method into some DOM element. This is triggered when you click on an <a> that has an ng-href with a corresponding path or programatically in something like a controller using the $location service path() method.
Route Configuration:
app.config(function($routeProvider) {
$routeProvider
.when('/foo', {
templateUrl: 'foo.html',
controller: 'FooController'
})
.when('/bar', {
templateUrl: 'bar.html',
controller: 'BarController'
});
});
HTML:
<ul>
<li><a ng-href="#/foo">foo</a></li>
<li><a ng-href="#/bar">bar</a></li>
</ul>
<div ng-view></div>
Here is a plunker demonstrating the functionality of basic routing including loading specific controllers and HTML templates based on a specific route.
ng-include
If you absolutely need to use ng-include, you can using a function executed via ng-click attached to $scope or controllerAs to update the src property of ng-include to load a template based on a click element. I've updated the plunker.
Hopefully this helps!
This is rather a conceptual than a strictly technical question.
I have the following index.html:
<div class="container"><div ng-view=""></div></div>
In my app.js, I have the following route configuration:
$routeProvider
.when('/questions', {
templateUrl: 'views/questions.html',
controller: 'QuestionsCtrl'
})
.when('/result', {
templateUrl: 'views/result.html',
controller: 'ResultCtrl'
})
.otherwise({
redirectTo: '/questions'
});
Which means that, based on the URL, different views are loaded in <div ng-view="">. Now, in order to have those views correctly rendered, I need to set style attributes on the enclosing <div class="container"> (I use Leaflet.js in one of those views and thus I need to temporarily set the width and height of the container to 100%, for a full screen map).
How would I do this best, i.e. "The Angular Way"? I looked at the $viewContentLoaded event of the ngView directive, but it doesn't seem to be the right thing as it seems to be only fired when the respective view is completely loaded and not at the initialization of the view (and thus the map, which needs a correctly styled container from beginning on). Should I use a controller that is defined on the body tag, for example? Or a service? I am completely clueless and want to make it right.
Use a controller that listens to $routeChangeSuccess on the $rootScope.
<body ng-app="X" ng-controller="app">
<div class="container" ng-class="containerClass">
<div ng-view=""></div>
</div>
</body>
angular.module('X').controller('app', function($rootScope, $route) {
$rootScope.$on('$routeChangeSuccess', function(){
$rootScope.containerClass = angular.lowercase(($route.current.controller || '').replace(/Ctrl$/, ''));
});
});
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
I am trying to create directive component for modal windows, which take care about modal behaviour such as opening, closing, taking care of zIndex etc.
Content of modal components is controlled by Controller.
So far idea is good, but when i try to have
<modal ng-controller="MyController">
Some content which will be transcluded with dynamic {{value}} from MyController
</modal>
It failed and does not render {{value}}
I have to wrap modal into controller
<div ng-controller="MyController">
<modal>
Some content which will be transcluded with dynamic {{value}} from MyController
</modal>
</div>
Is there any way, how to make first example works, or it is impossible and why angular do it that way?
There is full example with plunker at the end.
var app = angular.module('plunker', []);
app.directive("modal", function() {
return {
restrict:'E',
replace:true,
transclude:true,
scope: false,
template:'<div class="modal">Modal scope {{$id}}<div ng-transclude></div></div>',
link: function($scope) {
console.log("directive scope ", $scope.$id)
}
}
})
app.controller('DetailControl', function($scope, $location) {
console.log("controller scope ", $scope.$id)
$scope.name = 'World';
});
and this HTML
<body>
main scope {{$id}}
Controller on same element as modal<br>
<modal ng-controller="DetailControl">
<div>
content scope (transclude) {{$id}}<br>
Some content of modal window. The name is {{name || '-unknown-'}}
</div>
</modal>
Controller outside modal
<div ng-controller="DetailControl">
Controller scope {{$id}}
<modal>
<div>
content scope (transclude) {{$id}}<br>
Some content of modal window. The name is {{name || '-unknown-'}}
</div>
</modal>
</div>
<body>
here is plunker http://plnkr.co/edit/WOgZKB3e0bQUASMhFVOp?p=preview
The issue is the ngController directive creates it's own scope. When you do <modal ng-controller="MyController"> the ngController scope is a sibling to modal so modal can't see over (sideways in a sense) into that controller.
It works when ngController is a parent because you're using scope: false which causes your directive to inherit it's scope from its parent.
Rather than use a separate ngController directive you can attach a controller to your directive:
app.directive("modal", function() {
return {
controller: function($scope, $location) {
console.log("controller scope ", $scope.$id)
$scope.name = 'World';
}
}
This approach will give your directive good encapsulation as it no longer will depend on an external controller- which is good. One plus is you no longer need to coordinate multiple scopes.
If you need multiple directives to communicate you can use require to allow multiple directives to all share access to one parent directive's controller. This is the approach Angular internally takes (for instance in 'ng-switch`)
try use transclude with passing scope :
http://docs.angularjs.org/api/ng.$compile
transcludeFn -> scope
"transcludeFn - A transclude linking function pre-bound to the correct transclusion scope. The scope can be overridden by an optional first argument. This is the same as the $transclude parameter of directive controllers. function([scope], cloneLinkingFn)."
I have some markup and loaded controllers.
Then I load some modal window contents by ajax, which is using one of controllers I have defined before. But looks like this controller isn't being used, because he is not required until modal loaded.
Question: How to make controller work when modal loaded? I tryied $scope.$digest(), got error "digest in progress".
index.html
<html data-ng-app="foo">
<head>
<script src="/js/app.js"></script>
</head>
<body>
<div id="modal"></div>
</body>
</html>
js/app.js
!(function(){
function FormCtrl($scope) {
console.log($scope); // never fired
$scope.Submit = function() {
console.log('submit'); // never fired too :C
}
}
angular.module('foo', []).controller('FormCtrl', FormCtrl);
})();
html content loaded by ajax and inserted to #modal
<div data-ng-controller="FormCtrl">
<form name="signup" data-ng-submit="Submit()">
<!-- form data -->
</form>
</div>
SOLUTION:
$.modal().open({
onOpen: function($e) {
$http.get('/views/' + url).success(function(data) {
$compile(data)($scope, function(clonedElem) {
$e.html(clonedElem);
});
// $e.html(data); was used instead of statement above
});
}
});
If you want to inject new DOM elements into existing Anuglar app. You options are to use
ng-include: This has a src property that takes the url from which partial content has to be loaded. AngularJS would internally compile it. One important thing here is that angular will download the template as soon it encounter ng-include in html.
Download and compile DOM manually using the $compile service which is a more involved process.
If your AJAX content contains a controller defined in ng-controller, AngularJS would create it for you.
But in any case, keep in mind the controller script should have been already wired at the initialization\setup phase.