I have the following code (see below) in whichI use ng-include. The ng-model="numLines" creates a binding of the value in the box and the function changeNumLines() specified in ng-change is called every time the value inside the input box changes. The value of the $scope.numLines inside the changeNumLines() is supposed to be the new changed value. However, it is not. The value that I output to the console is always "1" no matter what input is typed in the box.
However, if I do not use ng-include and just copy paste the partial html file: structCustomRows.htm into index.html instead of ng-include line everything works fine.
So, why is this happening with ng-include and how can I get around it?
Thanks.
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<base href="/">
</head>
<script>
var app = angular.module('parserUI', []);
app.controller('CustomRowsCtrl', ['$scope', function($scope) {
$scope.numLines = 1;
$scope.changeNumLines = function() {
console.log("DEBUG: changeNumLines()");
console.log(String($scope.numLines));
}
}]);
</script>
<body ng-app="parserUI">
<h2>Header</h2>
<div ng-controller="CustomRowsCtrl">
<div ng-include src="'app/partials/structCustomRows.htm'"></div>
</div> <!-- ng-controller="CustomRowsCtrl" -->
</body>
</html>
structCustomRows.htm
<input type="text" ng-model="numLines" ng-change="changeNumLines()">
Looking at the docs, it says that "This directive creates new scope". Meaning the $scope.numLines in your controller is actually in the parent scope of your ngInclude directive.
The simplest way to fix this is to put the controller definition in the ngInclude to make it all the same scope. Otherwise, you will have to access the parent scope using something like $scope.$parent.numLines.
When you use ng-include, Angular creates a new scope, causing your variable to be overwritten to the new scope, instead of the one you wanted. As the new scope is an instance of the original scope (the one you want), you can create an object on CustomRowsCtrl; the objects are copied to the new scope as a reference, enabling you to share the same object in between child and parent scope.
Obs.: You can also use $parent, as suggested on the link below, but using pointers is a lot cleaner and if you use multiple nesting, you won't need to use $parent.$parent and so on.
Example:
On the controller:
$scope.selection = {}
On the HTML:
ng-model="selection.numLines"
More about ng-include scope:
AngularJS - losing scope when using ng-include
It's all about scope creation in Angular.
The directive ng-include create a new scope and you've defined the numLines in the parent scope, i.e the scope of the controller.
If you want to see the changes in your parent scope, you'll have either to write ng-model="$parent.numLines" in the included Html template or simply use the onload attribute of the ng-include directive : onload="numLines = numLines"
ng-include directive creates its new scope, There is so many work round for tackle this problem.
Create your own directive of static include which does not creates its own scope. for example.
app.directive('staticInclude', ['$http', '$templateCache', '$compile', function ($http, $templateCache, $compile) {
return function (scope, element, attrs) {
var templatePath = attrs.staticInclude;
$http.get(templatePath, { cache: $templateCache }).success(function (response) {
var contents = $('<div/>').html(response).contents();
element.html(contents);
$compile(contents)(scope);
});
};
}]);
so you can use this directive, its not create own scope.
Your html looks like
<body ng-app="parserUI">
<h2>Header</h2>
<div ng-controller="CustomRowsCtrl">
<div static-include=="app/partials/structCustomRows.htm"></div>
</div> <!-- ng-controller="CustomRowsCtrl" -->
</body>
You include controller in template means your file 'app/partials/structCustomRows.htm' has main div which initialize its relevant controller.
Related
In angularJS 1.3.14
var app = angular.module('myApp',[]);
app.controller('myController',['$scope',function($scope){
$scope.name = 'world';
}]);
//here i created directive of name helloWorld
app.directive('helloWorld',function(){
return {
replace:true,
restrict:'AE',
template :'<h3>Hello world<h3/>'
}
});
<html ng-app='myApp'>
<body ng-controller = "myController">
<hello-world/>
</body>
</html>
Error is :
Error: [$compile:tplrt] Template for directive 'helloWorld' must have
exactly one root element.
How to solve this error?
Quick fix
Root cause(replace: true)
<hello-world></hello-world>
change directive template to close h3 tag properly template :'<h3>Hello world</h3>'
Explanation
There are two problem in your code.
You should close your directive custom element like <hello-world><hello-world/>. If you do not close the tag, first occurrence will work fine but after that rest of the thing will not work. See here.
Other thing is your template of directive has wrong template
Directive template
<h3>Hello world<h3/>
should be
<h3>Hello world</h3>
You have template in directive like <h3>Hello world<h3/> which has not closing the h3 tag properly.
So that gets rendered on page like below, which has two h3 element.
<h3>Hello world</h3>
<h3></h3>
So the render html has two elements which are individual. So while passing them to $compile service to compile the content of template, it is throwing [$compile:tplrt] which means your template should have single root element, so the angular will compile that element.
comment the replace: true.
var app = angular.module('myApp',[]);
app.controller('myController',['$scope',function($scope){
$scope.name = 'world';
}]);
//**here i created directive of name helloWorld**
app.directive('helloWorld',function(){
return {
restrict:'AE',
//replace:true,
template :'<h3>Hello world<h3/>'
};
});
or
//**here i created directive of name helloWorld**
app.directive('helloWorld',function(){
return {
restrict:'AE',
replace:true,
template :'<div><h3>Hello world<h3/></div>'
};
});
You are getting the error because in your directive you are using replace:true and the template is enclosed in h3 tag winch is not closed properly (you should close h3 tag using </h3> not <h3/>).
You should enclose the template in a root tag properly like <h3>Hello world</h3>.
When a directive is declared with template (or templateUrl) and
replace mode on, the template must have exactly one root element. That
is, the text of the template property or the content referenced by the
templateUrl must be contained within a single html element. For
example, <p>blah <em>blah</em> blah</p> instead of simply blah <em>blah</em> blah. Otherwise, the replacement operation would result
in a single element (the directive) being replaced with multiple
elements or nodes, which is unsupported and not commonly needed in
practice.
Reference : Angular Docs
I have a little widget I'd like to use over and over on a single page. It has its own controller. Problem is it needs a piece of data to operate (basically a key), and each key is contained in the parent controller.
Here is an example (which is obviously wrong)
http://plnkr.co/edit/VajgOr1LqpLDnbEJcvor?p=preview
script:
angular.module('myApp', [])
.controller('ParentCtrl', ['$scope',
function($scope) {
$scope.keyForChartABC = "somekey1";
$scope.keyForChartXYZ = "somekey2";
$scope.keyForChartLALA = "somekey3";
}
])
.controller('ChartCtrl', ['$scope',
function($scope) {
//todo: have $scope.key assigned from parent somehow
//not shown: use $scope.key to pull data and format chart data
}
])
index:
<!-- ng-init like this is quite wrong -->
<div ng-init="key = keyForChartABC"
ng-include="'chartwidget.html'"></div>
<hr>
<div ng-init="key = keyForChartXYZ"
ng-include="'chartwidget.html'"></div>
<hr>
<div ng-init="key = keyForChartLALA"
ng-include="'chartwidget.html'"></div>
chartwidget:
<div ng-controller="ChartCtrl">
<p>Drawing chart for data: {{key}}</p>
<p>some chart directive here</p>
</div>
As you can see in the plunker, what I tried here with ng-init doesn't work - key for all the sub-controllers end up with the same value.
I've gotten this to work with ng-repeat and an array of data in the parent, somehow $index gets set in each child to the right index and stays that one value. But I'd like to avoid using ng-repeat in this case so I can have more control of the layout.
Creating re-usable widgets is exactly the purpose of Directives. You can create a directive which handles the output of your widget quite easily.
I forked your plunker and modified it to change it to use a directive.
Here are a few highlights:
First, your template no longer needs the controller defined within it.
<div>
<p>Drawing chart for data: {{key}}</p>
<p>some chart directive here</p>
</div>
Next, the directive is defined, with an isolate scope which is unique to each instance of the directive:
.directive('chartWidget', function(){
return {
restrict: 'E',
scope: {
key: '='
},
templateUrl : 'chartwidget.html'
}
})
Lastly, the directive is declared in the HTML. Note the camel-case name of the directive in the JavaScript, but the hyphenated name in the HTML:
<div>
<chart-widget key="keyForChartABC"></chart-widget>
<hr>
<chart-widget key="keyForChartXYZ"></chart-widget>
<hr>
<chart-widget key="keyForChartLALA"></chart-widget>
</div>
Edit
I updated the plunker to show binding the directive property to an inner controller. This method uses the ControllerAs syntax to define the controller, and binds the directive's scope to the controller scope.
Relevant changes:
.directive('chartWidget', function(){
return {
restrict: 'E',
scope: {
key: '='
},
templateUrl : 'chartwidget.html',
controller: 'chartWidgetController',
controllerAs: 'ctrl',
bindToController: true
}
})
.controller('chartWidgetController', function(){
console.log(this.key);
})
And a small change to the template to support ControllerAs:
<div>
<p>Drawing chart for data: {{ctrl.key}}</p>
<p>some chart directive here</p>
</div>
Note that trying to use ng-controller= in the template will cause the template to have a different scope object from the scope object created for the directive, and the controller would not have access to the properties defined on the directive.
Also note, bindToController is a feature of angular 1.3.x or higher. in angular 1.2.x or earlier, your only option was to use $scope.$watch to monitor the isolate scope for changes.
I am using ng-template in angularjs
<script type="text/ng-template" id="ng-wig/views/ng-wig.html">
<div class="ng-wig">
........
</div>
<script>
and i have a textarea using this ng-template.
<textarea ng-wig="content" ng-model="contents"></textarea>
but this ng-model cannot be accessed inside the controller.
please help.
As you are loading your textarea template inside ng-include, if you look at ng-include directive you will see that it does create a new scope which is prototypically inherited from the parent scope.
If you use any scope variable which are declared in controller won't be accessible inside the include div. In order to get the controller variable accessible inside ng-include the you must declare it as object like $scope.model= {} then you should declare the properties in it. like ng-mode="model.contents"
Markup
<textarea ng-wig="model.content" ng-model="model.contents"></textarea>
Controller
$scope.model = {};
Here you can find similar answer
There are several approaches apart from above by which you could solve this issue.
The other way around would be, you could use controller as approach in that way you can avoid. In that you need to use this inside a controller & use controller alias while showing variable on html like vm.contents here
Makrup
<div ng-controller="myCtrl as vm">
<div ng-include="'ng-wig/views/ng-wig.html'"></div>
</div>
Textarea
<textarea ng-wig="content" ng-model="vm.contents"></textarea>
Also you could point to the parent scope of ng-include which is nothing but controller scope just by doing $parent in your ng-model, this case your ng-model would be $parent.contents
Textarea
<textarea ng-wig="$parent.content" ng-model="$parent.contents"></textarea>
Note
Don't use this approach, use the 1st one which is more preferable.
Why can't I do this in Angular.js:
document.getElementById('divid').value = 'Change Text to This';
And what is the "right" (Angular) way of doing it?
In Angular you use the ng-model directive in order to create a two-way data binding between your model (i.e. a JS variable) and the view (i.e. the <div>). Basically, this means that whenever you change the data in the view (through an input, for instance), the change will be reflected in your JS variable. See below:
HTML:
<html ng-app="myApp">
<div ng-controller="MyCtrl">
<div>{{myVariable}}</div>
<input type="text" ng-model="myVariable" />
</div>
</html>
JS:
/* App global module */
var myApp = angular.module('myApp');
/* Define controller to process data from your view */
myApp.controller('MyCtrl', function($scope) {
$scope.myVariable = "initialValue"; // this is the variable that corresponds to text inserted in your input
});
Here, myVariable will have "initialValue" as an initial value. Any further changes to the input will overwrite the value of this variable.
Also notice that {{myVariable}} injects the variable the data into your div. When you change the value in the input, the div text will be changed as well.
You'd have to bind a controller to your mark up and initialise your Angular app.
<div ng-app="myApp">
<div id="divid" ng-controller="valueController">{{value}}</div>
</div>
Then you can simply define a scope variable in your controller
myApp.controller("valueController", function($scope) {
$scope.value = "Hi!";
});
It is considered best to use the ng-app directive in the HTML tag of the page
jsFiddle
So, I can change a model value from a child controller, but when the child controller is in ng-switch then it doesn't work, why? I created an example to demonstrate it.
One way to avoid this is to use the . in the model name, like bunnies.kills. Is this a bug or this is a feature ?
Using Angular 1.0.6
Using your code structure, in your child controllers you would need to change:
$scope.$parent.kills++;
to
$scope.$parent.$parent.kills++;
Explanation: MainCtrl's scope is the parent scope of SimpleParentCtrl, but the grandparent of Step1Ctrl and Step2Ctrl. As some others pointed out, ng-switch creates its own scope, and then your Step1Ctrl and Step2Ctrl each created a child scope of the ng-switch.
Note: Each time the 1 or 2 button is clicked, both the ng-switch and it's currently matched child controller get a new scope.
Also: In case you happen to be looking in the Angular source and wondering how the ng-switch directive creates its own scope without a scope property, the answer is that it does so manually in its link method via scope.$new(). The directives ng-include, ng-switch, ng-repeat, and ng-view all create new scope this way, either in the link method or the compile method's returned link function.
Resources:
https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance
http://www.youtube.com/watch?v=ZhfUv0spHCY&feature=youtu.be&t=30m
ng-switch creates its own child scope, which is why #sh0ber's answer is one way to get it to work. In general, models should be referenced in controller scopes (hence reference objects), and not be not primitives. So using a . is a "best practice".
This is not a bug, but it is not a feature either. This is the way JavaScript prototypal inheritance works with primitives.
I would take a slightly different approach to this problem.
Rather than use $scope.$parent, I would recommend you move all of your bunny killing logic into a shared service/model.
Also, I would try to avoid referencing parent views/controllers. Referencing the parent can make it difficult to reuse your code and can be painful to debug as the project grows. It is okay for a parent to know about it's children but a child should know little to nothing about it's parent.
Here is an updated Plunk: http://plnkr.co/edit/PLDbfU8Fu7m59A42qdR6?p=preview
HTML
<body ng-controller="MainCtrl">
<p>
Dead bunnies: <strong>{{Elmer.deadWabbits}}</strong>
</p>
<div ng-controller="SimpleParentCtrl">
<button ng-click="Elmer.killTheWabbit()">Kill from simple parent gun</button>
</div>
<hr>
<div ng-switch="" on="step">
<div ng-switch-when="first" ng-controller="Step1Ctrl">
<button ng-click="Elmer.killTheWabbit()">Kill from 1 tab gun</button>
</div>
<div ng-switch-when="second">
<div ng-controller="Step2Ctrl">
<button ng-click="Elmer.killTheWabbit()">Kill from 2 tab gun</button>
</div>
</div>
</div>
<hr>
<p>
<button ng-click="changeStep('first')">1</button> <button ng-click="changeStep('second')">2</button>
</p>
</body>
JS
angular.module('plunker', []).
service("Elmer", [function() {
this.deadWabbits = 0;
this.killTheWabbit = function() {
this.deadWabbits++;
};
}]).
controller('MainCtrl', function($scope, Elmer) {
$scope.Elmer = Elmer;
$scope.step = 'first';
$scope.changeStep = function(name){
$scope.step = name;
};
}).
controller('SimpleParentCtrl', function() {}).
controller('Step1Ctrl', function() {}).
controller('Step2Ctrl', function() {});
One way to avoid this is to use the . in model name, like bunnies.kills. Is this a bug or this is a feature ?
This has been explained numberous times : https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance
and in mhevery's video